1. 程式人生 > 實用技巧 >ABP 如何與EFCore整合

ABP 如何與EFCore整合

  • 每個模組定義單獨的DbContext類
  • 不要在應用程式開發中使用延遲載入
  • 不要為DbContext啟用延遲載入

DbContext Interface

  • 介面繼承自IEfCoreDbContext介面
  • 新增ConnectionStringName特性到DbContext介面
  • 將DbSet 屬性新增到DbContext介面中,注意:僅適用於聚合根

DbContext Class

  • DbContext類繼承自 AbpDbContext
  • 新增ConnectionStringName 特性到DbContext類
  • 實現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)
    {

    }

    //code omitted for brevity
}

表字首與架構

  • 新增靜態屬性 TablePrefix和Schema 到DbContext類,使用常量為其設定一個預設值
public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;

  • 要用簡短的TablePrefix值為模組在共享資料庫中建立不重複的表名
  • Schema預設賦值為null

模型對映

  • 重寫DbContext的OnModelCreating方法顯式配置所有實體
protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.ConfigureIdentity(options =>
    {
        options.TablePrefix = TablePrefix;
        options.Schema = Schema;
    });
}

  • 不要直接在OnModelCreating方法中配置model ,而是為ModelBuilder定義一個擴充套件方法 ,使用ConfigureModuleName作為方法名稱, 例:
public static class IdentityDbContextModelBuilderExtensions
{
    public static void ConfigureIdentity(
        [NotNull] this ModelBuilder builder,
        Action<IdentityModelBuilderConfigurationOptions> optionsAction = null)
    {
        Check.NotNull(builder, nameof(builder));

        var options = new IdentityModelBuilderConfigurationOptions();
        optionsAction?.Invoke(options);

        builder.Entity<IdentityUser>(b =>
        {
            b.ToTable(options.TablePrefix + "Users", options.Schema);
            b.ConfigureByConvention();
            //code omitted for brevity
        });

        builder.Entity<IdentityUserClaim>(b =>
        {
            b.ToTable(options.TablePrefix + "UserClaims", options.Schema);
            b.ConfigureByConvention();
            //code omitted for brevity
        });
        //code omitted for brevity
    }
}

  • 為每個Entity對映呼叫 b.ConfigureByConvention()
  • 通過繼承 AbpModelBuilderConfigurationOptions 來建立 configuration Options 類. 例如:
public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
    public IdentityModelBuilderConfigurationOptions()
        : base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema)
    {
    }
}

倉儲實現

  • 從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 DbSet
        .IncludeDetails(includeDetails)
        .FirstOrDefaultAsync(
            u => u.NormalizedUserName == normalizedUserName,
            GetCancellationToken(cancellationToken)
        );
}

如果呼叫者程式碼中未提供取消令牌,則GetCancellationToken會從ICancellationTokenProvider.Token獲取取消令牌

  • 為具有子集合的聚合根建立 IQueryable 返回型別的 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 IQueryable<IdentityUser> WithDetails()
{
    return GetQueryable().IncludeDetails(); // Uses the extension method defined above
}

模組類

  • 為Entity Framework Core整合包定義一個Module類.
  • 使用 AddAbpDbContext 方法將 DbContext 新增到 IServiceCollection.
  • 將已實現的倉儲新增到 AddAbpDbContext 方法的options中. 例如:
[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>();
        });
    }
}