Entity Framework Core 集成最佳实践

有关EF Core 集成的基础知识,请参阅Entity Framework Core 集成文档.

  • 推荐 为每个模块定义单独的 DbContext 接口和类.
  • 不推荐 在应用程序开发中使用延迟加载.
  • 不推荐DbContext 启用延迟加载.

DbContext Interface

  • 推荐 继承自IEfCoreDbContextDbContext 定义一个相应的 interface.
  • 推荐 添加 ConnectionStringName attributeDbContext 接口.
  • 推荐DbSet<TEntity> properties 添加到 DbContext 接口中,注意: 仅适用于聚合根. 例如:
  1. [ConnectionStringName("AbpIdentity")]
  2. public interface IIdentityDbContext : IEfCoreDbContext
  3. {
  4. DbSet<IdentityUser> Users { get; set; }
  5. DbSet<IdentityRole> Roles { get; set; }
  6. }

DbContext class

  • 推荐 DbContext 继承自 AbpDbContext<TDbContext> 类.
  • 推荐 添加 ConnectionStringName attributeDbContext 类.
  • 推荐 实现 DbContext 类实现其相应的接口. 例如:
  1. [ConnectionStringName("AbpIdentity")]
  2. public class IdentityDbContext : AbpDbContext<IdentityDbContext>, IIdentityDbContext
  3. {
  4. public DbSet<IdentityUser> Users { get; set; }
  5. public DbSet<IdentityRole> Roles { get; set; }
  6. public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
  7. : base(options)
  8. {
  9. }
  10. //code omitted for brevity
  11. }

表前缀与架构

  • 推荐 添加静态 properties TablePrefixSchemaDbContext 类. 使用常量为其设置一个默认值. 例如:
  1. public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
  2. public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
  • 推荐 总是使用简短的 TablePrefix 值为模块在共享数据库中创建 unique table names. Abp 前缀是为ABP Core模块保留的.
  • 推荐 Schema 默认赋值为 null.

Model Mapping

  • 推荐 重写 DbContextOnModelCreating 方法显式 配置所有实体. 例如:
  1. protected override void OnModelCreating(ModelBuilder builder)
  2. {
  3. base.OnModelCreating(builder);
  4. builder.ConfigureIdentity(options =>
  5. {
  6. options.TablePrefix = TablePrefix;
  7. options.Schema = Schema;
  8. });
  9. }
  • 不推荐 直接在 OnModelCreating 方法中配置model, 而是为 ModelBuilder 定义一个 扩展方法. 使用ConfigureModuleName作为方法名称. 例如:
  1. public static class IdentityDbContextModelBuilderExtensions
  2. {
  3. public static void ConfigureIdentity(
  4. [NotNull] this ModelBuilder builder,
  5. Action<IdentityModelBuilderConfigurationOptions> optionsAction = null)
  6. {
  7. Check.NotNull(builder, nameof(builder));
  8. var options = new IdentityModelBuilderConfigurationOptions();
  9. optionsAction?.Invoke(options);
  10. builder.Entity<IdentityUser>(b =>
  11. {
  12. b.ToTable(options.TablePrefix + "Users", options.Schema);
  13. b.ConfigureByConvention();
  14. //code omitted for brevity
  15. });
  16. builder.Entity<IdentityUserClaim>(b =>
  17. {
  18. b.ToTable(options.TablePrefix + "UserClaims", options.Schema);
  19. b.ConfigureByConvention();
  20. //code omitted for brevity
  21. });
  22. //code omitted for brevity
  23. }
  24. }
  • 推荐 为每个Enttiy映射调用 b.ConfigureByConvention();(如上所示).
  • 推荐 通过继承 AbpModelBuilderConfigurationOptions 来创建 configuration Options 类. 例如:
  1. public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
  2. {
  3. public IdentityModelBuilderConfigurationOptions()
  4. : base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema)
  5. {
  6. }
  7. }

仓储实现

  • 推荐EfCoreRepository<TDbContext,TEntity,TKey>继承 仓储并实现相应的仓储接口. 例如:
  1. public class EfCoreIdentityUserRepository
  2. : EfCoreRepository<IIdentityDbContext, IdentityUser, Guid>, IIdentityUserRepository
  3. {
  4. public EfCoreIdentityUserRepository(
  5. IDbContextProvider<IIdentityDbContext> dbContextProvider)
  6. : base(dbContextProvider)
  7. {
  8. }
  9. }
  • 推荐 使用 DbContext 接口而不是类来作为泛型参数.
  • 推荐 使用 GetCancellationToken 帮助方法将 cancellationToken 传递给EF Core. 例如:
  1. public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
  2. string normalizedUserName,
  3. bool includeDetails = true,
  4. CancellationToken cancellationToken = default)
  5. {
  6. return await DbSet
  7. .IncludeDetails(includeDetails)
  8. .FirstOrDefaultAsync(
  9. u => u.NormalizedUserName == normalizedUserName,
  10. GetCancellationToken(cancellationToken)
  11. );
  12. }

如果调用者代码中未提供取消令牌,则 GetCancellationToken 会从ICancellationTokenProvider.Token 获取取消令牌.

  • 推荐 为具有 子集合 的聚合根创建 IQueryable<TEntity> 返回类型的 IncludeDetails 扩展方法. 例如:
  1. public static IQueryable<IdentityUser> IncludeDetails(
  2. this IQueryable<IdentityUser> queryable,
  3. bool include = true)
  4. {
  5. if (!include)
  6. {
  7. return queryable;
  8. }
  9. return queryable
  10. .Include(x => x.Roles)
  11. .Include(x => x.Logins)
  12. .Include(x => x.Claims)
  13. .Include(x => x.Tokens);
  14. }
  • 推荐 推荐在仓储其他方法中使用 IncludeDetails 扩展方法, 就像上面的示例代码一样(参阅 FindByNormalizedUserNameAsync).

  • 推荐 覆盖具有 子集合 的聚合根仓储中的 WithDetails 方法. 例如:

  1. public override IQueryable<IdentityUser> WithDetails()
  2. {
  3. return GetQueryable().IncludeDetails(); // Uses the extension method defined above
  4. }

Module Class

  • 推荐 为Entity Framework Core集成包定义一个Module类.
  • 推荐 使用 AddAbpDbContext<TDbContext> 方法将 DbContext 添加到 IServiceCollection.
  • 推荐 将已实现的仓储添加到 AddAbpDbContext<TDbContext> 方法的options中. 例如:
  1. [DependsOn(
  2. typeof(AbpIdentityDomainModule),
  3. typeof(AbpEntityFrameworkCoreModule)
  4. )]
  5. public class AbpIdentityEntityFrameworkCoreModule : AbpModule
  6. {
  7. public override void ConfigureServices(ServiceConfigurationContext context)
  8. {
  9. context.Services.AddAbpDbContext<IdentityDbContext>(options =>
  10. {
  11. options.AddRepository<IdentityUser, EfCoreIdentityUserRepository>();
  12. options.AddRepository<IdentityRole, EfCoreIdentityRoleRepository>();
  13. });
  14. }
  15. }