Clean Architecture结合ASP.NET Identity的仓储/工作单元多DbContext处理疑问
问题背景
我正在基于Clean Architecture开发后端项目,要实现用户JWT认证的注册/登录,同时处理产品实体的操作,架构分为四层:
- Domain层:包含实体、
IRepository、IUnitOfWork - Persistence层:包含DbContext、仓储实现
- Application层:通过UnitOfWork操作仓储的命令与查询
- UI层:Web API应用
Domain层已定义的核心接口
通用仓储接口IRepository<T>:
public interface IRepository<T> where T : Entity { //methods go here }
工作单元接口IUnitOfWork:
public interface IUnitOfWork { IRepository<Product> ProductRepository { get; } IRepository<User> UserRepository { get; } //User为未继承IdentityUser的简单类(Domain层无法引用ApplicationUser) public Task SaveAllAsync(); public Task DeleteDataBaseAsync(); public Task CreateDataBaseAsync(); }
冲突场景与疑问
产品使用常规DbContext:
public class AppDbContext:DbContext { public AppDbContext(DbContextOptions<AppDbContext> options):base(options) { Database.EnsureCreated(); } public DbSet<Product> Products{ get; set; } }
用户认证需要继承IdentityDbContext的上下文:
public class AppDbContext:IdentityDbContext<IdentityUser> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<ApplicationUser> Users{ get; set; } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } }
现在面临两个DbContext的冲突,但需要在IRepository和IUnitOfWork的实现中统一使用。是否应该创建IUserRepository、IProductRepository并分别实现(注入对应DbContext的仓储类)?
之前查资料只见过把认证逻辑放在UI层的方案(不符合需求),或者把ApplicationUser加入Domain层(会引入Identity依赖,违背Clean Architecture原则)。
解决方案
1. 拆分专属仓储接口,解耦DbContext依赖
完全可以创建IUserRepository和IProductRepository,它们继承自通用的IRepository<T>,可按需定义专属操作,核心是在Persistence层为它们分别实现对应不同DbContext的仓储类:
Domain层新增专属接口
// Domain层 public interface IUserRepository : IRepository<User> { // 添加用户专属操作,比如根据用户名查找 Task<User> GetByUsernameAsync(string username); } public interface IProductRepository : IRepository<Product> { // 添加产品专属操作,比如按分类查询 Task<IEnumerable<Product>> GetByCategoryAsync(string category); }
同时调整IUnitOfWork,引用专属仓储而非通用:
public interface IUnitOfWork { IProductRepository ProductRepository { get; } IUserRepository UserRepository { get; } Task SaveAllAsync(); Task DeleteDatabaseAsync(); Task CreateDatabaseAsync(); }
2. Persistence层实现多DbContext的仓储
在Persistence层分别实现两个仓储,各自注入对应的DbContext,同时负责Domain实体与数据库实体的映射:
产品仓储实现(依赖常规AppDbContext)
// Persistence层 public class ProductRepository : IProductRepository { private readonly AppDbContext _dbContext; public ProductRepository(AppDbContext dbContext) { _dbContext = dbContext; } // 实现IRepository<T>和IProductRepository的方法 public async Task<IEnumerable<Product>> GetByCategoryAsync(string category) { return await _dbContext.Products.Where(p => p.Category == category).ToListAsync(); } // 其他通用方法实现... }
用户仓储实现(依赖IdentityDbContext)
// Persistence层 public class UserRepository : IUserRepository { private readonly IdentityAppDbContext _identityDbContext; public UserRepository(IdentityAppDbContext identityDbContext) { _identityDbContext = identityDbContext; } public async Task<User> GetByUsernameAsync(string username) { // 把IdentityDbContext中的ApplicationUser映射到Domain层的User实体 var appUser = await _identityDbContext.Users.FirstOrDefaultAsync(u => u.UserName == username); return appUser != null ? new User { Id = appUser.Id, Username = appUser.UserName, Email = appUser.Email } : null; } // 其他通用方法实现... }
3. 实现支持多DbContext的UnitOfWork
UnitOfWork的实现需要同时注入两个DbContext,按需实例化对应的仓储:
// Persistence层 public class UnitOfWork : IUnitOfWork { private readonly AppDbContext _appDbContext; private readonly IdentityAppDbContext _identityDbContext; private IProductRepository _productRepository; private IUserRepository _userRepository; public UnitOfWork(AppDbContext appDbContext, IdentityAppDbContext identityDbContext) { _appDbContext = appDbContext; _identityDbContext = identityDbContext; } public IProductRepository ProductRepository { get => _productRepository ??= new ProductRepository(_appDbContext); } public IUserRepository UserRepository { get => _userRepository ??= new UserRepository(_identityDbContext); } public async Task SaveAllAsync() { // 同时保存两个上下文的变更 await _appDbContext.SaveChangesAsync(); await _identityDbContext.SaveChangesAsync(); } public async Task DeleteDatabaseAsync() { await _appDbContext.Database.EnsureDeletedAsync(); await _identityDbContext.Database.EnsureDeletedAsync(); } public async Task CreateDatabaseAsync() { await _appDbContext.Database.EnsureCreatedAsync(); await _identityDbContext.Database.EnsureCreatedAsync(); } }
4. 依赖注入配置
在UI层的Program.cs中注册两个DbContext和对应的仓储、UnitOfWork:
// 注册产品DbContext builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("ProductDb"))); // 注册Identity DbContext builder.Services.AddDbContext<IdentityAppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityDb"))); // 注册仓储和UnitOfWork builder.Services.AddScoped<IProductRepository, ProductRepository>(); builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); // 配置Identity(仅在Persistence层和UI层依赖) builder.Services.AddIdentityCore<ApplicationUser>() .AddEntityFrameworkStores<IdentityAppDbContext>() .AddDefaultTokenProviders();
关键原则遵守
- Domain层保持纯净:不依赖任何外部库(包括Identity),只定义实体和抽象接口。
- Persistence层负责数据访问细节:处理不同DbContext的映射、仓储实现,隔离Domain层与数据框架的依赖。
- Application层通过UnitOfWork协调业务操作:不需要关心底层用了几个DbContext,只调用抽象接口完成业务逻辑。
内容的提问来源于stack exchange,提问作者CMlllll

