| name | abp-efcore |
| description | ABP Framework v10.x (10.4/10.5) Entity Framework Core: AbpDbContext, ConfigureByConvention, AddAbpDbContext, repository (EfCoreRepository), migration, PostgreSQL/MySQL/SQLite/Oracle. Use when working with EF Core, DbContext, migrations, or repository implementation in ABP. |
ABP Framework — Entity Framework Core
A guide to EF Core integration in ABP Framework v10.x (10.4/10.5). DbContext, repository, migration, eager/lazy loading, and advanced topics.
Trigger
- "ABP EF Core"
- "ABP DbContext"
- "ABP migration"
- "ABP repository EF"
- "ABP ConfigureByConvention"
- "ABP WithDetails"
- "ABP DbSet"
- "ABP entity mapping"
Installation
abp add-package Volo.Abp.EntityFrameworkCore
Creating a DbContext
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace MyCompany.MyProject
{
public class MyDbContext : AbpDbContext<MyDbContext>
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options) { }
}
}
Entity Mapping (Fluent API)
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable("Books");
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.HasIndex(x => x.Name);
});
}
ConfigureByConvention() must always be called — it automatically configures base class properties (Id, CreationTime, etc.).
DbContext Registration
[DependsOn(typeof(AbpEntityFrameworkCoreModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<MyDbContext>(options =>
{
options.AddDefaultRepositories();
});
}
}
DBMS Configuration
Choosing a DBMS with the CLI
abp new Acme.BookStore -dbms PostgreSQL
abp new Acme.BookStore -dbms MySQL
abp new Acme.BookStore -dbms SQLite
abp new Acme.BookStore -dbms Oracle
Manually Changing the DBMS
PostgreSQL:
[DependsOn(typeof(AbpEntityFrameworkCorePostgreSqlModule))]
Configure<AbpDbContextOptions>(options => options.UseNpgsql());
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
MySQL (Pomelo):
Configure<AbpDbContextOptions>(options =>
{
options.Configure(ctx =>
{
if (ctx.ExistingConnection != null)
ctx.DbContextOptions.UseMySql(ctx.ExistingConnection);
else
ctx.DbContextOptions.UseMySql(ctx.ConnectionString);
});
});
builder.ConfigureOpenIddict(options =>
{
options.DatabaseProvider = EfCoreDatabaseProvider.MySql;
});
SQLite:
Configure<AbpDbContextOptions>(options => options.UseSqlite());
Supported DBMSs
| DBMS | Package |
|---|
| SQL Server | Volo.Abp.EntityFrameworkCore.SqlServer |
| PostgreSQL | Volo.Abp.EntityFrameworkCore.PostgreSql |
| MySQL | Volo.Abp.EntityFrameworkCore.MySQL |
| SQLite | Volo.Abp.EntityFrameworkCore.Sqlite |
| Oracle | Volo.Abp.EntityFrameworkCore.Oracle |
Choosing a Connection String
[ConnectionStringName("MySecondConnString")]
public class MyDbContext : AbpDbContext<MyDbContext> { }
If not specified, the Default connection string is used.
Using the Default Repository
public class BookManager : DomainService
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookManager(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task<Book> CreateBookAsync(string name, BookType type)
{
var book = new Book(GuidGenerator.Create(), name, type);
await _bookRepository.InsertAsync(book);
return book;
}
}
Custom Repository
public interface IBookRepository : IRepository<Book, Guid>
{
Task DeleteBooksByType(BookType type);
}
public class BookRepository : EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository
{
public BookRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider) { }
public async Task DeleteBooksByType(BookType type)
{
var dbContext = await GetDbContextAsync();
await dbContext.Database.ExecuteSqlRawAsync(
$"DELETE FROM Books WHERE Type = {(int)type}"
);
}
}
Overriding the Default Repository
context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
{
options.AddDefaultRepositories();
options.AddRepository<Book, BookRepository>();
});
Eager Loading (WithDetails)
var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines);
var orders = await AsyncExecuter.ToListAsync(queryable);
Configure<AbpEntityOptions>(options =>
{
options.Entity<Order>(orderOptions =>
{
orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines);
});
});
var queryable = await _orderRepository.WithDetailsAsync();
includeDetails on Get/Find Methods
var order = await _orderRepository.GetAsync(id);
var order = await _orderRepository.GetAsync(id, includeDetails: false);
var orders = await _orderRepository.GetListAsync(includeDetails: true);
Explicit Loading
var order = await _orderRepository.GetAsync(id, includeDetails: false);
await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines);
Lazy Loading
Configure<AbpDbContextOptions>(options =>
{
options.PreConfigure<MyDbContext>(opts =>
{
opts.DbContextOptions.UseLazyLoadingProxies();
});
options.UseSqlServer();
});
public virtual ICollection<OrderLine> Lines { get; set; }
public virtual Order Order { get; set; }
Read-Only Repositories
public class MyService : ApplicationService
{
private readonly IReadOnlyRepository<Book, Guid> _bookRepository;
var query = (await _bookRepository.GetQueryableAsync()).AsTracking();
}
Object Extension Manager (Extra Properties)
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
);
MapEfCoreProperty must be called before the DbContext is used. In startup templates, the EfCoreEntityExtensionMappings class is the safe spot.
Split Queries
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer(optionsBuilder =>
{
optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery);
});
});
EF Core with Multi-Tenancy
[IgnoreMultiTenancy]
public class TenantManagementDbContext : AbpDbContext<TenantManagementDbContext> { }
Default Repository Base Class
public class MyRepositoryBase<TEntity> : EfCoreRepository<BookStoreDbContext, TEntity>
where TEntity : class, IEntity
{
public MyRepositoryBase(IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider) { }
}
context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
{
options.SetDefaultRepositoryClasses(
typeof(MyRepositoryBase<,>),
typeof(MyRepositoryBase<>)
);
});
Best Practices
- Always call
ConfigureByConvention() — for base property mapping
- Prefer the Fluent API — over data annotations
- Keep the domain layer isolated from EF Core — use
IAsyncQueryableExecuter
- Do eager loading with
WithDetailsAsync — to avoid the N+1 problem
- Use
IReadOnlyRepository for read-only queries — No-Tracking is automatic
- Define custom repositories in the EF Core layer — only the interface in the domain layer
- Manage migrations in the EF Core project —
Add-Migration, Update-Database
Migrations
dotnet ef migrations add InitialCreate --project Acme.BookStore.EntityFrameworkCore --startup-project Acme.BookStore.DbMigrator
dotnet ef database update --project Acme.BookStore.EntityFrameworkCore --startup-project Acme.BookStore.DbMigrator
dotnet run --project Acme.BookStore.DbMigrator
Multiple DbContext Migrations
builder.Entity<Book>(b => { ... });
public class BookStoreDbContextModelSnapshot : ModelSnapshot { }
Seed Data via Migration
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "Books",
columns: new[] { "Id", "Name", "Price" },
values: new object[] { Guid.NewGuid(), "1984", 29.99m }
);
}
ReplaceDbContext Pattern
Combining multiple DbContexts into a single DbContext:
public interface IBookStoreDbContext : IEfCoreDbContext
{
DbSet<Book> Books { get; }
}
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>, IBookStoreDbContext
{
public DbSet<Book> Books { get; set; }
}
context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
{
options.AddDefaultRepositories<IBookStoreDbContext>();
});
[ReplaceDbContext(typeof(IBookStoreDbContext))]
public class UnifiedDbContext : AbpDbContext<UnifiedDbContext>, IBookStoreDbContext
{
public DbSet<Book> Books { get; set; }
}
Bulk Operations Customization
public class MyCustomEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
{
public async Task InsertManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
{
}
public async Task UpdateManyAsync<TDbContext, TEntity>(...) { }
public async Task DeleteManyAsync<TDbContext, TEntity>(...) { }
}
What's New in v10.5
- MySQL
ResourcePermissionGrant index length fix (v10.5+): for MySQL only, ABP shortened the ResourceName and ResourceKey max lengths of the Permission Management module's ResourcePermissionGrant entity to stay within MySQL's utf8mb4 index key limit. Other providers are unchanged. When creating a fresh MySQL solution or generating new migrations after upgrading, regenerate/review the affected migrations; if you have a custom migration touching ResourcePermissionGrant, align its column lengths with the updated model.
Related