观察 Entity Framework Core Generic Repository。这个话题会让一些人感到不舒服。他们根本不愿意讨论这个问题。另一方面,其他人则喜欢它,并且仅仅提到通用存储库模式就感到头晕目眩。
通用存储库模式与其他任何模式一样具有优点和缺点。它是否适用于您的项目取决于您。您不必完全致力于使用通用存储库方法,您始终可以将其用于应用程序的一部分。
拥有通用 CRUD 存储库的好处是允许您传递其实体类型,从中继承,并为任何类型的实体提供 CRUD 存储库,而无需编写任何类型的代码。
注意:我们不会构建能够永远满足您所有需求的东西。相反,我们将努力为通用存储库建立框架,您可以使用该框架快速简单地创建 CRUD 操作,然后对其进行修改以满足您的需求。因此,这将仅解决支持 CRUD 的通用存储库。您可以扩展 Repository 类并继承它以执行更多内容。这只是一个概念证明。您不希望 Web 层在更大的实际应用程序中了解数据库层。因此,您的控制器不会对它们进行任何存储库注入。
同样,它仅适用于应用程序的某些方面。您不需要将其用作满足所有数据库要求的唯一选项。
我将创建一个新的空 Asp.Net Core Web API 项目。
我们需要将下面提到的包安装到应用程序中。
下面是 Emp.cs 类的代码。
public class Emp
{
public Guid Id { get; set; }
public string Name { get; set; }
public EmployeeType Type { get; set; }
public string Mno { get; set; }
public decimal Salary { get; set; }
}
下面是 EmpDBContext.cs 类的代码。
public class EmpDBContext : DbContext
{
public EmpDBContext(DbContextOptions<EmpDBContext> dbContextOptions)
: base(dbContextOptions)
{
}
public DbSet<Emp> Emps { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
如您所见,我们在数据库中有一个匹配的表,由 Emps DbSet 表示。
我们正在开发一个构造函数,该构造函数采用一个名为 DbContextOptions 的参数。这将允许我们将选项传递给 Startup 类。
builder.Services.AddDbContext<EmpDBContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServer")));
我们将向 appsettings.json 中添加以下连接字符串,以便我可以与数据库进行通信。
"ConnectionStrings": {
"SqlServer": "Data Source=server_name;Initial Catalog=EmpDB;Integrated Security=True;TrustServerCertificate=True"
}
与 Entity Framework 6 类似,DbContext 在 EF Core 中用于查询数据库并聚合要一起写回存储区的更改。
DbContext 类的奇妙之处在于它支持我们将用来与数据库通信的方法的泛型,因为它是泛型的。
下面是 FindOptions.cs 类的代码。
public class FindOptions
{
public bool IsIgnoreAutoIncludes { get; set; }
public bool IsAsNoTracking { get; set; }
}
下面是 IRepository.cs 类的代码。
public interface IRepository<TEntity> where TEntity : class
{
IQueryable<TEntity> GetAll(FindOptions? findOptions = null);
TEntity FindOne(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null);
IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null);
void Add(TEntity entity);
void AddMany(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Delete(TEntity entity);
void DeleteMany(Expression<Func<TEntity, bool>> predicate);
bool Any(Expression<Func<TEntity, bool>> predicate);
int Count(Expression<Func<TEntity, bool>> predicate);
}
泛型 TEntity 类型(对应于数据库中的实体类型)将是您首先注意到的内容(Emp、Department、User、Role 等)。
下面是实现 IRepository 接口的代码。
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly EmpDBContext _empDBContext;
public Repository(EmpDBContext empDBContext)
{
_empDBContext = empDBContext;
}
public void Add(TEntity entity)
{
_empDBContext.Set<TEntity>().Add(entity);
_empDBContext.SaveChanges();
}
public void AddMany(IEnumerable<TEntity> entities)
{
_empDBContext.Set<TEntity>().AddRange(entities);
_empDBContext.SaveChanges();
}
public void Delete(TEntity entity)
{
_empDBContext.Set<TEntity>().Remove(entity);
_empDBContext.SaveChanges();
}
public void DeleteMany(Expression<Func<TEntity, bool>> predicate)
{
var entities = Find(predicate);
_empDBContext.Set<TEntity>().RemoveRange(entities);
_empDBContext.SaveChanges();
}
public TEntity FindOne(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null)
{
return Get(findOptions).FirstOrDefault(predicate)!;
}
public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, FindOptions? findOptions = null)
{
return Get(findOptions).Where(predicate);
}
public IQueryable<TEntity> GetAll(FindOptions? findOptions = null)
{
return Get(findOptions);
}
public void Update(TEntity entity)
{
_empDBContext.Set<TEntity>().Update(entity);
_empDBContext.SaveChanges();
}
public bool Any(Expression<Func<TEntity, bool>> predicate)
{
return _empDBContext.Set<TEntity>().Any(predicate);
}
public int Count(Expression<Func<TEntity, bool>> predicate)
{
return _empDBContext.Set<TEntity>().Count(predicate);
}
private DbSet<TEntity> Get(FindOptions? findOptions = null)
{
findOptions ??= new FindOptions();
var entity = _empDBContext.Set<TEntity>();
if (findOptions.IsAsNoTracking && findOptions.IsIgnoreAutoIncludes)
{
entity.IgnoreAutoIncludes().AsNoTracking();
}
else if (findOptions.IsIgnoreAutoIncludes)
{
entity.IgnoreAutoIncludes();
}
else if (findOptions.IsAsNoTracking)
{
entity.AsNoTracking();
}
return entity;
}
}
如果您观察到 GetAll、Find 和 FindOne 方法使用了 find options 类(基本上基于提供的配置),则系统应提取数据。
我们来开发一下 Emp 仓库接口。
public interface IEmpRepository : IRepository<Emp>
{
//**TODO:** Write here custom methods that are required for specific requirements.
}
由于这继承自 IRepository 接口,因此必须实现所有这些方法。
但是,如果我们构建一个派生自 Repository 的 EmpRepository,那么 IRepository 中的所有这些方法都将被涵盖。此外,我们必须实现 IEmpRepository。
这是它的显示方式。
public class EmpRepository : Repository<Emp>, IEmpRepository
{
public EmpRepository(EmpDBContext empDBContext)
: base(empDBContext)
{
}
//**TODO:** Write here custom methods that are required for specific requirements.
}
我们必须在 DI 容器中注册 Repository 和 EmpRepository,以便在我们的应用程序中使用;否则,它将无法解决这些依赖项并在运行时中给出错误。
下面是在 DI 容器中注册依赖项的代码。
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<IEmpRepository, EmpRepository>();
这是 EmpController.cs 类的代码,其中注入 IEmpRepository 接口,解决构造函数端的依赖性,并在我们的应用程序中使用它。
[Route("api/[controller]")]
[ApiController]
public class EmpController : ControllerBase
{
private readonly IEmpRepository _empRepository;
public EmpController(IEmpRepository empRepository)
{
_empRepository = empRepository;
}
[HttpGet]
public IActionResult Get()
{
IEnumerable<Emp> employees = _empRepository.GetAll();
return Ok(employees);
}
[HttpGet("{id}", Name = "Get")]
public IActionResult Get(Guid id)
{
Emp employee = _empRepository.FindOne(x => x.Id == id);
if (employee == null)
{
return NotFound("The Employee record couldn't be found.");
}
return Ok(employee);
}
[HttpPost]
public IActionResult Post([FromBody] Emp employee)
{
if (employee == null)
{
return BadRequest("Employee is null.");
}
employee.Id = Guid.NewGuid();
_empRepository.Add(employee);
return CreatedAtRoute(
"Get",
new { Id = employee.Id },
employee);
}
[HttpPut("{id}")]
public IActionResult Put(Guid id, [FromBody] Emp employee)
{
if (employee == null)
{
return BadRequest("Employee is null.");
}
Emp employeeToUpdate = _empRepository.FindOne(x => x.Id == id);
if (employeeToUpdate == null)
{
return NotFound("The Employee record couldn't be found.");
}
employeeToUpdate.Mno = employee.Mno;
employeeToUpdate.Salary = employee.Salary;
employeeToUpdate.Name = employee.Name;
employeeToUpdate.Type = employee.Type;
_empRepository.Update(employeeToUpdate);
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult Delete(Guid id)
{
Emp employee = _empRepository.FindOne(x => x.Id == id);
if (employee == null)
{
return NotFound("The Employee record couldn't be found.");
}
_empRepository.Delete(employee);
return NoContent();
}
}
让我们运行应用程序,我们将执行 get-all-employees 操作。
您需要执行所有操作(POST、PUT、DELETE、GET),然后需要下载示例代码,解压缩并在 Visual Studio 中打开它。
更改数据库连接字符串,运行应用程序,并在此应用程序中进行操作。
此示例代码的唯一要求是,它必须使用 .NET 8 版本编写,就像示例应用程序一样。如果您还没有,我希望您在尝试使用它之前先在计算机上安装 .NET 8 版本。