在 .NET Core 8 中使用 EF Core 的通用存储库

作者:微信公众号:【架构师老卢】
7-5 14:21
37

概述:介绍观察 Entity Framework Core Generic Repository。这个话题会让一些人感到不舒服。他们根本不愿意讨论这个问题。另一方面,其他人则喜欢它,并且仅仅提到通用存储库模式就感到头晕目眩。通用存储库模式与其他任何模式一样具有优点和缺点。它是否适用于您的项目取决于您。您不必完全致力于使用通用存储库方法,您始终可以将其用于应用程序的一部分。拥有通用 CRUD 存储库的好处是允许您传递其实体类型,从中继承,并为任何类型的实体提供 CRUD 存储库,而无需编写任何类型的代码。注意:我们不会构建能够永远满足您所有需求的东西。相反,我们将努力为通用存储库建立框架,您可以使用该

介绍

观察 Entity Framework Core Generic Repository。这个话题会让一些人感到不舒服。他们根本不愿意讨论这个问题。另一方面,其他人则喜欢它,并且仅仅提到通用存储库模式就感到头晕目眩。

通用存储库模式与其他任何模式一样具有优点和缺点。它是否适用于您的项目取决于您。您不必完全致力于使用通用存储库方法,您始终可以将其用于应用程序的一部分。

拥有通用 CRUD 存储库的好处是允许您传递其实体类型,从中继承,并为任何类型的实体提供 CRUD 存储库,而无需编写任何类型的代码。

注意:我们不会构建能够永远满足您所有需求的东西。相反,我们将努力为通用存储库建立框架,您可以使用该框架快速简单地创建 CRUD 操作,然后对其进行修改以满足您的需求。因此,这将仅解决支持 CRUD 的通用存储库。您可以扩展 Repository 类并继承它以执行更多内容。这只是一个概念证明。您不希望 Web 层在更大的实际应用程序中了解数据库层。因此,您的控制器不会对它们进行任何存储库注入。

同样,它仅适用于应用程序的某些方面。您不需要将其用作满足所有数据库要求的唯一选项。

举个例子

我将创建一个新的空 Asp.Net Core Web API 项目。

先决条件

我们需要将下面提到的包安装到应用程序中。

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer

设置 DbContext

下面是 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 版本。

阅读排行