项目

Entity Framework Core 集成最佳实践

本文档提供了在模块和应用程序中实现Entity Framework Core集成的最佳实践。

请确保您已首先阅读 Entity Framework Core 集成 文档。

通用原则

  • 务必 为每个模块定义一个独立的 DbContext 接口和类。
  • 切勿 在应用开发中依赖延迟加载。
  • 切勿DbContext 启用延迟加载。

DbContext 接口

  • 务必 为继承自 IEfCoreDbContextDbContext 定义一个接口
  • 务必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 类添加静态的 TablePrefixSchema 属性。从常量设置默认值。示例:
public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
  • 务必 为模块使用简短的 TablePrefix 值,以在共享数据库中创建唯一的表名Abp 表前缀是为ABP核心模块保留的。
  • 务必Schema 默认设置为 null

模型映射

  • 务必 通过重写 DbContextOnModelCreating 方法显式配置所有实体。示例:
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>();
        });
    }
}

另请参阅

在本文档中