Entity Framework Core 集成最佳实践
本文档提供了在模块和应用程序中实现Entity Framework Core集成的最佳实践。
请确保您已首先阅读 Entity Framework Core 集成 文档。
通用原则
- 务必 为每个模块定义一个独立的
DbContext接口和类。 - 切勿 在应用开发中依赖延迟加载。
- 切勿 为
DbContext启用延迟加载。
DbContext 接口
- 务必 为继承自
IEfCoreDbContext的DbContext定义一个接口。 - 务必 向
DbContext接口添加ConnectionStringName属性。 - 务必 仅针对聚合根向
DbContext接口添加DbSet<TEntity>属性。示例:
[ConnectionStringName("AbpIdentity")]
public interface IIdentityDbContext : IEfCoreDbContext
{
DbSet<IdentityUser> Users { get; }
DbSet<IdentityRole> Roles { get; }
}
- 切勿 在此接口中为属性定义
set;。
DbContext 类
- 务必 让
DbContext继承自AbpDbContext<TDbContext>类。 - 务必 向
DbContext类添加ConnectionStringName属性。 - 务必 为
DbContext类实现相应的接口。示例:
[ConnectionStringName("AbpIdentity")]
public class IdentityDbContext : AbpDbContext<IdentityDbContext>, IIdentityDbContext
{
public DbSet<IdentityUser> Users { get; set; }
public DbSet<IdentityRole> Roles { get; set; }
public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
: base(options)
{
}
//代码已省略以保持简洁
}
表前缀与架构
- 务必 向
DbContext类添加静态的TablePrefix和Schema属性。从常量设置默认值。示例:
public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
- 务必 为模块使用简短的
TablePrefix值,以在共享数据库中创建唯一的表名。Abp表前缀是为ABP核心模块保留的。 - 务必 将
Schema默认设置为null。
模型映射
- 务必 通过重写
DbContext的OnModelCreating方法显式配置所有实体。示例:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureIdentity();
}
- 切勿 直接在
OnModelCreating方法中配置模型。相反,为ModelBuilder创建一个扩展方法。使用 Configure模块名 作为方法名。示例:
public static class IdentityDbContextModelBuilderExtensions
{
public static void ConfigureIdentity([NotNull] this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<IdentityUser>(b =>
{
b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users", AbpIdentityDbProperties.DbSchema);
b.ConfigureByConvention();
//代码已省略以保持简洁
});
builder.Entity<IdentityUserClaim>(b =>
{
b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "UserClaims", AbpIdentityDbProperties.DbSchema);
b.ConfigureByConvention();
//代码已省略以保持简洁
});
//代码已省略以保持简洁
}
}
- 务必 为每个实体映射调用
b.ConfigureByConvention();(如上所示)。
仓储实现
- 务必 让仓储继承自
EfCoreRepository<TDbContext, TEntity, TKey>类,并实现相应的仓储接口。示例:
public class EfCoreIdentityUserRepository
: EfCoreRepository<IIdentityDbContext, IdentityUser, Guid>, IIdentityUserRepository
{
public EfCoreIdentityUserRepository(
IDbContextProvider<IIdentityDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
- 务必 使用
DbContext接口作为泛型参数,而不是类。 - 务必 使用
GetCancellationToken辅助方法将cancellationToken传递给 EF Core。示例:
public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
string normalizedUserName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync())
.IncludeDetails(includeDetails)
.FirstOrDefaultAsync(
u => u.NormalizedUserName == normalizedUserName,
GetCancellationToken(cancellationToken)
);
}
GetCancellationToken 回退到 ICancellationTokenProvider.Token 来获取取消令牌,如果调用者代码未提供。
- 务必 为具有子集合的每个聚合根,为
IQueryable<TEntity>创建一个IncludeDetails扩展方法。示例:
public static IQueryable<IdentityUser> IncludeDetails(
this IQueryable<IdentityUser> queryable,
bool include = true)
{
if (!include)
{
return queryable;
}
return queryable
.Include(x => x.Roles)
.Include(x => x.Logins)
.Include(x => x.Claims)
.Include(x => x.Tokens);
}
务必 在仓储方法中使用
IncludeDetails扩展方法,就像上面的示例代码中所示(参见FindByNormalizedUserNameAsync)。务必 为具有子集合的聚合根重写仓储的
WithDetails方法。示例:
public override async Task<IQueryable<IdentityUser>> WithDetailsAsync()
{
// 使用上面定义的扩展方法
return (await GetQueryableAsync()).IncludeDetails();
}
模块类
- 务必 为 Entity Framework Core 集成包定义一个模块类。
- 务必 使用
AddAbpDbContext<TDbContext>方法将DbContext添加到IServiceCollection。 - 务必 将实现的仓储添加到
AddAbpDbContext<TDbContext>方法的选项中。示例:
[DependsOn(
typeof(AbpIdentityDomainModule),
typeof(AbpEntityFrameworkCoreModule)
)]
public class AbpIdentityEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<IdentityDbContext>(options =>
{
options.AddRepository<IdentityUser, EfCoreIdentityUserRepository>();
options.AddRepository<IdentityRole, EfCoreIdentityRoleRepository>();
});
}
}
抠丁客


