Entity Framework Core Integration Best Practices
This document offers best practices for implementing Entity Framework Core integration in your modules and applications.
Ensure you've read the Entity Framework Core Integration document first.
General
- Do define a separated 
DbContextinterface and class for each module. - Do not rely on lazy loading on the application development.
 - Do not enable lazy loading for the 
DbContext. 
DbContext Interface
- Do define an interface for the 
DbContextthat inherits fromIEfCoreDbContext. - Do add a 
ConnectionStringNameattribute to theDbContextinterface. - Do add 
DbSet<TEntity>properties to theDbContextinterface for only aggregate roots. Example: 
[ConnectionStringName("AbpIdentity")]
public interface IIdentityDbContext : IEfCoreDbContext
{
    DbSet<IdentityUser> Users { get; }
    DbSet<IdentityRole> Roles { get; }
}
- Do not define 
set;for the properties in this interface. 
DbContext class
- Do inherit the 
DbContextfrom theAbpDbContext<TDbContext>class. - Do add a 
ConnectionStringNameattribute to theDbContextclass. - Do implement the corresponding 
interfacefor theDbContextclass. Example: 
[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
}
Table Prefix and Schema
- Do add static 
TablePrefixandSchemaproperties to theDbContextclass. Set default value from a constant. Example: 
public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
- Do always use a short 
TablePrefixvalue for a module to create unique table names in a shared database.Abptable prefix is reserved for ABP core modules. - Do set 
Schematonullas default. 
Model Mapping
- Do explicitly configure all entities by overriding the 
OnModelCreatingmethod of theDbContext. Example: 
protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.ConfigureIdentity();
}
- Do not configure model directly in the  
OnModelCreatingmethod. Instead, create an extension method forModelBuilder. Use ConfigureModuleName as the method name. Example: 
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();
            //code omitted for brevity
        });
        builder.Entity<IdentityUserClaim>(b =>
        {
            b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "UserClaims", AbpIdentityDbProperties.DbSchema);
            b.ConfigureByConvention();
            //code omitted for brevity
        });
        
        //code omitted for brevity
    }
}
- Do call 
b.ConfigureByConvention();for each entity mapping (as shown above). 
Repository Implementation
- Do inherit the repository from the 
EfCoreRepository<TDbContext, TEntity, TKey>class and implement the corresponding repository interface. Example: 
public class EfCoreIdentityUserRepository
    : EfCoreRepository<IIdentityDbContext, IdentityUser, Guid>, IIdentityUserRepository
{
    public EfCoreIdentityUserRepository(
        IDbContextProvider<IIdentityDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }
}
- Do use the 
DbContextinterface as the generic parameter, not the class. - Do pass the 
cancellationTokento EF Core using theGetCancellationTokenhelper method. Example: 
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 fallbacks to the ICancellationTokenProvider.Token to obtain the cancellation token if it is not provided by the caller code.
- Do create a 
IncludeDetailsextension method for theIQueryable<TEntity>for each aggregate root which has sub collections. Example: 
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);
}
- Do use the 
IncludeDetailsextension method in the repository methods just like used in the example code above (seeFindByNormalizedUserNameAsync). 
- Do override 
WithDetailsmethod of the repository for aggregates root which have sub collections. Example: 
public override async Task<IQueryable<IdentityUser>> WithDetailsAsync()
{
    // Uses the extension method defined above
    return (await GetQueryableAsync()).IncludeDetails();
}
Module Class
- Do define a module class for the Entity Framework Core integration package.
 - Do add 
DbContextto theIServiceCollectionusing theAddAbpDbContext<TDbContext>method. - Do add implemented repositories to the options for the 
AddAbpDbContext<TDbContext>method. Example: 
[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>();
        });
    }
}