这是 IRepository:
public interface IRepository<T> where T : Entity
{
//methods go here
}
这是 IUnitOfWork:
public interface IUnitOfWork {
IRepository<Product> ProductRepository { get; }
IRepository<User> UserRepository { get; } //User is a simple class not inheritated from IdentityUser (I can't use ApplicationUser in Domain)
public Task SaveAllAsync();
public Task DeleteDataBaseAsync();
public Task CreateDataBaseAsync();
}
我的问题是,如果用户的存储库必须使用不同的 dbcontext,如何处理产品和用户?例如,产品的 AppDbContext 可能如下所示:
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);
}
}
因此,这里我处理两个不同的上下文,而我需要在 IRepository 和 IUnitOfWork 实现中使用一个上下文。我该怎么办?我应该创建两个不同的存储库(如 IUserRepository 和 IProductRepository)并创建两个单独的类来实现它们吗? (就像 UserRepository 将注入 UserDbContext 和 ProductRepository 将注入 AppDbContext)
我尝试在网上查找相关信息,但没有任何与我的情况类似的例子。当身份验证逻辑是 UI 层的一部分并且看起来像服务时,我只见过一种实现,但这不是我想要的。 我还发现了一个关于将 ApplicationUser 添加到域项目的有趣想法。这是不正确的,因为这意味着添加对“Microsoft.AspNetCore.Identity.EntityFrameworkCore”的引用,这违背了干净的架构。
DbContext
已经是一个工作单元,而
DbSet
实际上是一个存储库。创建单独的抽象通常会增加不必要的复杂性,除非您需要模拟或自定义查询逻辑。通过正确的设置,您可以专注于功能而不是管理基础设施的复杂性。 为了解决您的问题,您不需要多个
DbContext
实例。相反,扩展
IdentityDbContext
来处理身份和域实体。这可以隔离身份问题,同时保持数据访问的单一入口点。 具体方法如下:
public class ApplicationDbContext : IdentityDbContext<IdentityUser, IdentityRole, string>
{
public DbSet<Product> Products => Set<Product>();
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
}
完整的工作示例:
这种方法避免了管理多个上下文的开销。对于大多数CRUD操作,您可以直接使用DbContext。但是,如果需要,您仍然可以将复杂的查询封装在存储库中:
public class ProductRepository
{
private readonly ApplicationDbContext _dbContext;
public ProductRepository(ApplicationDbContext dbContext) => _dbContext = dbContext;
public async Task<Product?> GetByIdAsync(int id, CancellationToken cancellationToken) =>
await _dbContext.Products.FindAsync(new object[] { id }, cancellationToken);
}
完整的工作示例:Pragmatic Clean Architecture ProjectRepository.cs
但是,在您的情况下,如果您不想要解耦的域 User,而是希望使用自定义的ApplicationUser
,它继承自
IdentityUser
。要实现此目的,您不需要将其单独声明为
DbSet
,因为当您将其作为泛型参数传递时,它已经在
IdentityDbContext
中定义。以下是此用例的示例:
https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Infrastruct/Data/ApplicationDbContext.cs
通过IdentityServer 提供对这些身份用户的访问。通常,您可以使用 UserManager
和
RoleManager
类与它们进行交互,如以下示例所示:
public class IdentityService(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
: IIdentityService
{
public async Task<User> GetUserAsync(string id)
{
var identityUser = await userManager.FindByIdAsync(id)
?? throw new NotFoundException(nameof(User), id);
var roles = await userManager.GetRolesAsync(identityUser);
return new User(identityUser.Id, identityUser.UserName, new Email(identityUser.Email))
{
Roles = roles.Select(role => new Role(role, role, new List<string>())).ToList()
};
}
public async Task DeleteUserAsync(string userId) =>
await userManager.DeleteAsync(await userManager.FindByIdAsync(userId)
?? throw new NotFoundException(nameof(User), userId));
public async Task CreateRoleAsync(Role role) =>
await roleManager.CreateAsync(new IdentityRole { Name = role.Name });
}
完整的工作示例: