在软件开发中,数据访问层(Data Access Layer, DAL)是一个重要的组成部分,它负责与数据库进行交互。为了提高代码的可维护性和可测试性,通常会采用一种设计模式来封装数据访问逻辑,这就是所谓的“仓库模式”(Repository Pattern)。仓库模式的目的是隐藏数据访问的细节,使得应用程序的其他部分可以轻松地查询数据对象,而无需了解如何提供诸如连接字符串之类的细节。此外,仓库模式还在应用程序的数据和领域层之间增加了一个抽象层,使得数据访问部分的代码更加容易进行测试。
首先,定义了一个仓库接口,它包含了针对特定实体类(如Employee类)的CRUD(创建、读取、更新、删除)操作。
public interface IEmployeeRepository
{
Task Get(Guid? id);
Task Save(Employee employee);
Task Delete(Employee employee);
Task Update(Employee employee);
Task> FindAll();
}
这个接口是针对Employee类设计的,包含了基本的CRUD操作。接下来,实现了一个EmployeeRepository类,它使用DbContext来与数据库交互。
public class EmployeeRepository : IEmployeeRepository
{
private EmployeeContext _employeeContext;
public EmployeeRepository()
{
_employeeContext = new EmployeeContext();
}
public async Task Get(Guid? id)
{
return await _employeeContext.Employees.FirstOrDefaultAsync(x => x.Id == id);
}
public async Task Save(Employee employee)
{
_employeeContext.Employees.Add(employee);
await _employeeContext.SaveChangesAsync();
}
public async Task Delete(Employee employee)
{
_employeeContext.Employees.Remove(employee);
await _employeeContext.SaveChangesAsync();
}
public async Task Update(Employee employee)
{
_employeeContext.Employees.Update(employee);
await _employeeContext.SaveChangesAsync();
}
public async Task> FindAll()
{
return await _employeeContext.Employees.ToListAsync();
}
}
EmployeeContext类继承自DbContext,并定义了一个DbSet
public class EmployeeContext : DbContext
{
private static bool _created = false;
public EmployeeContext()
{
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
public DbSet Employees { get; set; }
protected override void OnConfiguring(EntityOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryStore();
}
}
然而,EmployeeRepository的实现存在两个问题:首先,它只使用了Employee这一个模型类,如果有多个模型类,就需要复制大量的代码;其次,它不容易进行测试。为了解决这些问题,可以将仓库接口泛化,并注入上下文对象。
泛化仓库接口IGenericRepository
public interface IGenericRepository where T : class, IEntity, new()
{
Task Get(Guid? id);
Task Save(T entity);
Task Delete(T entity);
Task Update(T entity);
Task> FindAll();
}
IEntity接口定义如下:
public interface IEntity
{
Guid Id { get; set; }
}
接下来,实现了GenericRepository类:
public class GenericRepository : IGenericRepository where T : class, IEntity, new()
{
private DbContext _dbContext;
public GenericRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Delete(T entity)
{
_dbContext.Set().Remove(entity);
await _dbContext.SaveChangesAsync();
}
public async Task> FindAll()
{
return await _dbContext.Set().ToListAsync();
}
public async Task Get(Guid? id)
{
return await _dbContext.Set().FirstOrDefaultAsync(x => x.Id == id);
}
public async Task Save(T entity)
{
_dbContext.Set().Add(entity);
await _dbContext.SaveChangesAsync();
}
public async Task Update(T entity)
{
_dbContext.Set().Update(entity);
await _dbContext.SaveChangesAsync();
}
}
在GenericDbContext类中,使用反射来动态地为DbContext添加所有实现了IEntity接口的类型。
public class GenericDbContext : DbContext
{
private static bool _created = false;
public GenericDbContext()
{
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase(true);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var types = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => typeof(IEntity).IsAssignableFrom(type) && type.IsClass);
var method = typeof(ModelBuilder).GetMethods().First(m => m.Name == "Entity"
&& m.IsGenericMethodDefinition
&& m.GetParameters().Length == 0);
foreach (var type in types)
{
method = method.MakeGenericMethod(type);
method.Invoke(modelBuilder, null);
}
base.OnModelCreating(modelBuilder);
}
}
在ASP.NET 5中,可以使用内置的依赖注入功能来注入仓库和上下文。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped();
services.AddScoped, GenericRepository>();
}