如何在AspNetCore中使用EF Core实现通用仓储模式?本文从零讲解IRepository泛型接口设计、RepositoryBase实现、DbContext配置、依赖注入注册,以及如何扩展具体仓储类处理业务逻辑,完整示例代码可直接运行。

在AspNetCore中设计通用仓储接口

对于一个项目来说,数据库的增删改查操作是必不可少的,而对于一个项目来说,数据库的增删改查操作是必不可少的,
所以一个合理的仓储接口设计是必不可少的。这里我使用的ORM框架是EF Core,所以我这里的仓储接口设计是基于EF Core的。

需要安装的包

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools

配置数据库连接字符串——参考之前的文章AspNetCore读取配置文件的方案四

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "SqlServerConfig": {
    "ConnectionString": "Server = (localdb)\\mssqllocaldb;DataBase = APIEFcTYCCJK02x02Db01;Trusted_Connection = true;TrustServerCertificate = true;"
  },
  "AllowedHosts": "*"
}

为其创建配置类

namespace APIEFcTYCCJK02x02
{
    public class SqlServerConfig
    {
        public string ConnectionString { get; set; }
    }
}

在Program.cs中添加配置,这里需要注意的是,这里的配置类需要在服务容器中注册,否则无法使用IOptionsMonitor

builder.Services.AddOptions<SqlServerConfig>();
builder.Services.Configure<SqlServerConfig>(builder.Configuration.GetSection("SqlServerConfig"));

创建实体模型类

using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace APIEFcTYCCJK02x02
{
    public class TestDataTable01
    {        
        [Key]//主键
        [DatabaseGenerated(DatabaseGeneratedOption.None)]//自定义主键
        [MaxLength(50)]
        public string Id { get; set; }
        public string Name { get; set; }
        public string Password { get; set; }
        public string Description { get; set; }
    }
}

创建数据库上下文类


using Microsoft.EntityFrameworkCore;

namespace APIEFcTYCCJK02x02
{
    public class DataContext:DbContext
    {
        public DataContext(DbContextOptions<DataContext> options) : base(options) { }
        public DbSet<TestDataTable01> TestDataTable01s { get; set; }
    }
}

在Program.cs中添加服务,这里需要注意的是,这里的数据库上下文类需要在服务容器中注册,否则无法使用IOptionsMonitor

这里需从服务容器中获取IOptionsMonitor,所以这一步必须在数据库字符串配置类的注册之后,否则会报错。

builder.Services.AddDbContext<DataContext>((serviceProvider, options) =>
{   
    var sqlServerConfig = serviceProvider.GetRequiredService<IOptionsMonitor<SqlServerConfig>>().CurrentValue;
    options.UseSqlServer(sqlServerConfig.ConnectionString);
});

创建通用仓储接口IRepositoryBase

namespace APIEFcTYCCJK02x02
{
    public interface IRepositoryBase<T>
    {
        Task<IEnumerable<T>> GetAllAsync();//这个方法其实我不想写
        Task<T> GetByIdAsync(string id);
        Task AddAsync(T entity);
        Task UpdateByIdAsync(string id, T entity);
        Task DeleteByIdAsync(string id);
        Task<bool> IsEmptyAsync(string id);
        Task<bool> SaveChangesAsync();
    }
}

创建仓储实现类RepositoryBase

这里的泛型T是实体模型类,所以需要在构造函数中注入数据库上下文类DataContext,注意是DataContext,不是DbContext,
因为DbContext是抽象类,DataContext是派生类,所以需要注入DataContext,而不是DbContext。

using Microsoft.EntityFrameworkCore;
namespace APIEFcTYCCJK02x02
{
    public class RepositoryBase<T>: IRepositoryBase<T> where T : class
    {
        private readonly DataContext _context;
        public RepositoryBase(DataContext context)
        {
            _context = context;
        }

        public async Task AddAsync(T entity)
        {
            await _context.Set<T>().AddAsync(entity);
        }

        public async Task<IEnumerable<T>> GetAllAsync()
        {
            return await _context.Set<T>().ToListAsync();
        }

        public async Task<T> GetByIdAsync(string id)
        {
            return await _context.Set<T>().FindAsync(id);
        }
        public async Task<bool> SaveChangesAsync()
        {
            return (await _context.SaveChangesAsync() > 0);
        }

        public async Task UpdateByIdAsync(string id, T entity)
        {
           var entityUpdate = await _context.Set<T>().FindAsync(id);
            if (entityUpdate != null)
            {
                _context.Entry(entityUpdate).CurrentValues.SetValues(entity);
            }
        }
        public async Task DeleteByIdAsync(string id)
        {
            var entity = await _context.Set<T>().FindAsync(id);
            if (entity != null)
            {
                _context.Set<T>().Remove(entity);
            }
        }
        public async Task<bool> IsEmptyAsync(string id)
        {
            var entity = await _context.Set<T>().FindAsync(id);
            if (entity == null)
            {
                return true;
            }
            return false;
        }
    }
}

在Program.cs中添加仓储实现类

builder.Services.AddScoped(typeof(IRepositoryBase<>), typeof(RepositoryBase<>));

再为TestDataTable01创建一个具体的,不通用的仓储实现类,来解决一些特殊的,通用仓储接口无法实现的需求

如对其密码的验证,密码修改等,这种需求可以通过继承通用仓储实现类来解决。先创建具体的仓储接口,然后再创建具体的仓储实现类,
然后在具体的仓储实现类中实现具体的需求。

namespace APIEFcTYCCJK02x02
{
    public interface ITestDataTableRepository:IRepositoryBase<TestDataTable01>
    {
        Task<bool> VerifyPasswordAsync(string id, string password);
        Task<bool> ChangePasswordAsync(string id, string oldPassword, string newPassword);
        //使用MD5加密,不可逆,加密不属于通用需求,所以不放在IRepositoryBase,应该单独注册一个服务而不是接口,然后在VerifyPasswordAsync中调用
        //在VerifyPasswordAsync中直接对比加密后的密码
    }
}
  • 注意在具体的仓储实现类中,需要注入数据库上下文类DataContext,因为需要使用到数据库上下文类DataContext,
  • 注意是DataContext,不是DbContext,
using APIEFcTYCCJK02x02;
namespace APIEFcTYCCJK02x02
{
    public class TestDataTableRepository: RepositoryBase<TestDataTable01>,ITestDataTableRepository
    {
        public DataContext _context { get; set; }
        public TestDataTableRepository(DataContext context):base(context) { _context = context; }
        //使用MD5加密,不可逆,加密不属于通用需求,所以不放在IRepositoryBase,应该单独注册一个服务而不是接口,然后在VerifyPasswordAsync中调用
        //在VerifyPasswordAsync中直接对比加密后的密码2025.9.24
        public async Task<bool> VerifyPasswordAsync(string id, string password) 
        {
        var entity = await _context.Set<TestDataTable01>().FindAsync(id);
        if (entity == null)
        {
            return false;
        }
        return entity.Password == password;
        }
        public async Task<bool> ChangePasswordAsync(string id, string oldPassword, string newPassword) 
        {
        var entity = await _context.Set<TestDataTable01>().FindAsync(id);
        if (entity == null)
        {
            return false;
        }
        if (entity.Password != oldPassword)
        {
            return false;
        }
        entity.Password = newPassword;
        await _context.SaveChangesAsync();
 
        return true;
        }
    }
}

这个具体的仓储接口同样需要在Program.cs中注册

builder.Services.AddScoped<ITestDataTableRepository, TestDataTableRepository>();

在控制器中使用

通用仓储接口IRepositoryBase,具体的仓储接口ITestDataTableRepository,都是在Program.cs中注册的,
所以在控制器中可以直接注入,不需要再在控制器中注入具体的仓储实现类,因为具体的仓储实现类已经在Program.cs中注册了。

注意:

  • 一次传多个参数使用[FromForm],一次传一个参数使用[FromBody]。
  • IActionResult不包含返回值的类型信息,所以这里使用ActionResult。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace APIEFcTYCCJK02x02.Controllers
{
    [Route("[controller]/[action]")]//RPC风格的路由
    [ApiController]
    public class RepositoryBaseTestController : ControllerBase
    {
        private readonly IRepositoryBase<TestDataTable01> _repositoryBase;
        private readonly ITestDataTableRepository _testDataTableRepository;

        public RepositoryBaseTestController(IRepositoryBase<TestDataTable01> repositoryBase, ITestDataTableRepository testDataTableRepository)
        {
            _repositoryBase = repositoryBase;
            _testDataTableRepository = testDataTableRepository;
        }

        [HttpPost]
        public async Task<IEnumerable<TestDataTable01>> TestDataTable01_GetAllAsync()
        {
            return await _repositoryBase.GetAllAsync();
        }
        [HttpPost]
        public async Task<ActionResult<string>> TestDataTable01_AddAsync([FromBody] TestDataTable01 testDataTable01)
        {
            if (await _repositoryBase.IsEmptyAsync(testDataTable01.Id.ToString()) == false)
            {   
                return BadRequest("ID已存在");
            }
            
            await _repositoryBase.AddAsync(testDataTable01);
            await _repositoryBase.SaveChangesAsync();
            
            return Ok("成功添加");
        }

        [HttpPost]
        public async Task<ActionResult<string>> TestDataTable01_UpdateByIdAsync([FromBody] TestDataTable01 testDataTable01)
        {
            if (await _repositoryBase.IsEmptyAsync(testDataTable01.Id.ToString()) == true)
            {
                return BadRequest("ID不存在");
            }
         
            if (await _testDataTableRepository.VerifyPasswordAsync(testDataTable01.Id.ToString(), testDataTable01.Password) == false)
            { 
                return BadRequest("密码错误");
            }
            await _repositoryBase.UpdateByIdAsync(testDataTable01.Id.ToString(),testDataTable01);
            await _repositoryBase.SaveChangesAsync();
            return Ok("成功更新");
        }
        [HttpPost]
        public async Task<ActionResult<string>> TestDataTable01_DeleteByIdAsync([FromForm] string id,string password)
        {
            if (await _repositoryBase.IsEmptyAsync(id.ToString()) == true)
            {
                return BadRequest("ID不存在");
            }
           
            if (await _testDataTableRepository.VerifyPasswordAsync(id.ToString(), password) == false)
            { 
                return BadRequest("密码错误");
            }
            
            await _repositoryBase.DeleteByIdAsync(id.ToString());
            await _repositoryBase.SaveChangesAsync();
            return Ok("成功删除");
        }
        [HttpPost]
        public async Task<ActionResult<TestDataTable01>> TestDataTable01_GetByIdAsync([FromBody] string id)
        {
            if (await _repositoryBase.IsEmptyAsync(id.ToString()) == true)
            {
                return BadRequest("ID不存在");
            }
            return Ok(await _repositoryBase.GetByIdAsync(id.ToString()));
        }
        [HttpPost]
        public async Task<ActionResult<string>> TestDataTable01_ChangePasswordAsync([FromForm] string id,string oldPassword,string newPassword) 
        {
            if (await _repositoryBase.IsEmptyAsync(id.ToString()) == true)
            {
                return BadRequest("ID不存在");
            }
            if (await _testDataTableRepository.VerifyPasswordAsync(id.ToString(), oldPassword) == false) 
            {
                return BadRequest("密码错误");
            }
            if (await _testDataTableRepository.ChangePasswordAsync(id.ToString(), oldPassword, newPassword) == false) 
            {
                return BadRequest("修改失败");
            }
            return Ok("修改成功");
        }
    }
}

屏幕截图 2025-09-25 094452

最后讲一个小知识。post与get,delete,put的区别,post不是幂等,多次调用会创建多个资源,而get,delete,put是幂等的,多次调用不会改变资源的状态。

完整的Program.cs

using APIEFcTYCCJK02x02;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//使用IOptionsMonitor,2025.9.24
//弃用IOptionsSnapshot ,IOptionsSnapshot会为每个请求创建新的实例
builder.Services.AddOptions<SqlServerConfig>();
builder.Services.Configure<SqlServerConfig>(builder.Configuration.GetSection("SqlServerConfig"));

//注册DbContext,2025.9.24
builder.Services.AddDbContext<DataContext>((serviceProvider, options) =>
{   //将IOptionsMonitor从服务容器中获取,2025.9.24
    var sqlServerConfig = serviceProvider.GetRequiredService<IOptionsMonitor<SqlServerConfig>>().CurrentValue;
    options.UseSqlServer(sqlServerConfig.ConnectionString);
});

//注册仓储接口IRepositoryBase和RepositoryBase,2025.9.24
builder.Services.AddScoped(typeof(IRepositoryBase<>), typeof(RepositoryBase<>));

//注册具体的仓储接口和Repository,2025.9.24
builder.Services.AddScoped<ITestDataTableRepository, TestDataTableRepository>();

//builder.Services.AddSingleton<InformationBroadcast>();弃用

var app = builder.Build();

//使用IOptionsMonitor ,确保连接字符串是动态的,修改appsettings.json后,获得最新的连接字符串,2025.9.24
var sqlServerConfig = app.Services.GetRequiredService<IOptionsMonitor<SqlServerConfig>>().CurrentValue;
Console.WriteLine("SQLserver连接字符串: " + sqlServerConfig.ConnectionString);

// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
    app.UseSwagger();
    app.UseSwaggerUI();
//}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

屏幕截图 2025-09-25 094225

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐