Entity Framework Core Integration
This document explains how to integrate EF Core as an ORM provider to ABP based applications and how to configure it.
Installation
Volo.Abp.EntityFrameworkCore
is the main NuGet package for the EF Core integration. Install it to your project (for a layered application, to your data/infrastructure layer):
If you haven't done it yet, you first need to install the ABP CLI. For other installation options, see the package description page.
Note: Instead, you can directly download a startup template with EF Core pre-installed.
Database Management System Selection
Entity Framework Core supports various database management systems (see all). ABP and this document don't depend on any specific DBMS. If you are creating a reusable application module, avoid to depend on a specific DBMS package. However, in a final application you eventually will select a DBMS.
See Switch to Another DBMS for Entity Framework Core document to learn how to switch the DBMS.
Creating DbContext
Your DbContext
class should be derived from AbpDbContext<T>
as shown below:
About the EF Core Fluent Mapping
The application startup template has been configured to use the EF Core fluent configuration API to map your entities to your database tables.
You can still use the data annotation attributes (like [Required]
) on the properties of your entity while the ABP documentation generally follows the fluent mapping API approach. It is up to you.
ABP has some base entity classes and conventions (see the entities document) and it provides some useful extension methods to configure the properties inherited from the base entity classes.
ConfigureByConvention Method
ConfigureByConvention()
is the main extension method that configures all the base properties and conventions for your entities. So, it is a best practice to call this method for all your entities, in your fluent mapping code.
Example: Assume that you've a Book
entity derived from AggregateRoot<Guid>
base class:
You can override the OnModelCreating
method in your DbContext
and configure the mapping as shown below:
- Calling
b.ConfigureByConvention()
is important here to properly configure the base properties. - You can configure the
Name
property here or you can use the data annotation attributes (see the EF Core document).
While there are many extension methods to configure your base properties,
ConfigureByConvention()
internally calls them if necessary. So, it is enough to call it.
Configure the Connection String Selection
If you have multiple databases in your application, you can configure the connection string name for your DbContext
using the [ConnectionStringName]
attribute. Example:
If you don't configure, the Default
connection string is used. If you configure a specific connection string name, but not define this connection string name in the application configuration then it fallbacks to the Default
connection string (see the connection strings document for more information).
AbpDbContextOptions
AbpDbContextOptions
is used to configure the DbContext
options. When you create a new solution with the ABP's application startup template, you will see a simple configuration (in the EntityFrameworkCore
integration project's module class) as shown below:
That configuration configures the default DBMS as SQL Server for all the DbContext
s of the application. That configuration was a shorthand notation and it can be done with the following code block:
options.Configure(...)
method has more options to configure. For example, you can set DbContextOptions
(EF Core's native options) as shown below:
Add actions for the ConfigureConventions
and OnModelCreating
methods of the DbContext
as shown below:
If you have a single DbContext
or you have multiple DbContext
s but want to use the same DBMS and configuration for all, you can leave it as is. However, if you need to configure a different DBMS or customize the configuration for a specific DbContext
, you can specify it as shown below:
See Switch to Another DBMS for Entity Framework Core document to learn how to configure the DBMS.
Registering DbContext To Dependency Injection
Use AddAbpDbContext
method in your module to register your DbContext
class for dependency injection system.
Add Default Repositories
ABP can automatically create default generic repositories for the entities in your DbContext. Just use AddDefaultRepositories()
option on the registration:
This will create a repository for each aggregate root entity (classes derived from AggregateRoot
) by default. If you want to create repositories for other entities too, then set includeAllEntities
to true
:
Then you can inject and use IRepository<TEntity, TPrimaryKey>
in your services. Assume that you have a Book
entity with Guid
primary key:
(BookType
is a simple enum
here and not important) And you want to create a new Book
entity in a domain service:
This sample uses InsertAsync
method to insert a new entity to the database.
Add Custom Repositories
Default generic repositories are powerful enough in most cases (since they implement IQueryable
). However, you may need to create a custom repository to add your own repository methods. Assume that you want to delete all books by type.
It's suggested to define an interface for your custom repository:
You generally want to derive from the IRepository
to inherit standard repository methods (while, you don't have to do). Repository interfaces are defined in the domain layer of a layered application. They are implemented in the data/infrastructure layer (EntityFrameworkCore
project in a startup template).
Example implementation of the IBookRepository
interface:
Now, it's possible to inject the IBookRepository
and use the DeleteBooksByType
method when needed.
Override the Default Generic Repository
Even if you create a custom repository, you can still inject the default generic repository (IRepository<Book, Guid>
for this example). Default repository implementation will not use the class you have created.
If you want to replace default repository implementation with your custom repository, do it inside the AddAbpDbContext
options:
This is especially important when you want to override a base repository method to customize it. For instance, you may want to override DeleteAsync
method to delete a specific entity in a more efficient way:
Loading Related Entities
Assume that you've an Order
with a collection of OrderLine
s and the OrderLine
has a navigation property to the Order
:
And defined the database mapping as shown below:
When you query an Order
, you may want to include all the OrderLine
s in a single query or you may want to load them later on demand.
Actually these are not directly related to the ABP. You can follow the EF Core documentation to learn all the details. This section will cover some topics related to the ABP.
Eager Loading / Load With Details
You have different options when you want to load the related entities while querying an entity.
Repository.WithDetails
IRepository.WithDetailsAsync(...)
can be used to get an IQueryable<T>
by including one relation collection/property.
Example: Get an order with lines
AsyncExecuter
is used to execute async LINQ extensions without depending on the EF Core. If you add EF Core NuGet package reference to your project, then you can directly useawait query.FirstOrDefaultAsync()
. But, this time you depend on the EF Core in your domain layer. See the repository document to learn more.
Example: Get a list of orders with their lines
WithDetailsAsync
method can get more than one expression parameter if you need to include more than one navigation property or collection.
DefaultWithDetailsFunc
If you don't pass any expression to the WithDetailsAsync
method, then it includes all the details using the DefaultWithDetailsFunc
option you provide.
You can configure DefaultWithDetailsFunc
for an entity in the ConfigureServices
method of your module in your EntityFrameworkCore
project.
Example: Include Lines
while querying an Order
You can fully use the EF Core API here since this is located in the EF Core integration project.
Then you can use the WithDetails
without any parameter:
WithDetailsAsync()
executes the expression you've setup as the DefaultWithDetailsFunc
.
Repository Get/Find Methods
Some of the standard Repository methods have optional includeDetails
parameters;
GetAsync
andFindAsync
getsincludeDetails
with default value istrue
.GetListAsync
andGetPagedListAsync
getsincludeDetails
with default value isfalse
.
That means, the methods return a single entity includes details by default while list returning methods don't include details by default. You can explicitly pass includeDetails
to change the behavior.
These methods use the
DefaultWithDetailsFunc
option that is explained above.
Example: Get an order with details
Example: Get an order without details
Example: Get list of entities with details
Alternatives
The repository pattern tries to encapsulate the EF Core, so your options are limited. If you need an advanced scenario, you can follow one of the options;
- Create a custom repository method and use the complete EF Core API.
- Reference to the
Volo.Abp.EntityFrameworkCore
package from your project. In this way, you can directly useInclude
andThenInclude
in your code.
See also eager loading document of the EF Core.
Explicit / Lazy Loading
If you don't include relations while querying an entity and later need to access to a navigation property or collection, you have different options.
EnsurePropertyLoadedAsync / EnsureCollectionLoadedAsync
Repositories provide EnsurePropertyLoadedAsync
and EnsureCollectionLoadedAsync
extension methods to explicitly load a navigation property or sub collection.
Example: Load Lines of an Order when needed
EnsurePropertyLoadedAsync
and EnsureCollectionLoadedAsync
methods do nothing if the property or collection was already loaded. So, calling multiple times has no problem.
See also explicit loading document of the EF Core.
Lazy Loading with Proxies
Explicit loading may not be possible in some cases, especially when you don't have a reference to the Repository
or DbContext
. Lazy Loading is a feature of the EF Core that loads the related properties / collections when you first access to it.
To enable lazy loading;
- Install the Microsoft.EntityFrameworkCore.Proxies package into your project (typically to the EF Core integration project)
- Configure
UseLazyLoadingProxies
for yourDbContext
(in theConfigureServices
method of your module in your EF Core project). Example:
- Make your navigation properties and collections
virtual
. Examples:
Once you enable lazy loading and arrange your entities, you can freely access to the navigation properties and collections:
Whenever you access to a property/collection, EF Core automatically performs an additional query to load the property/collection from the database.
Lazy loading should be carefully used since it may cause performance problems in some specific cases.
See also lazy loading document of the EF Core.
Read-Only Repositories
ABP provides read-only repository interfaces (IReadOnlyRepository<...>
or IReadOnlyBasicRepository<...>
) to explicitly indicate that your purpose is to query data, but not change it. If so, you can inject these interfaces into your services.
Entity Framework Core read-only repository implementation uses EF Core's No-Tracking feature. That means the entities returned from the repository will not be tracked by the EF Core change tracker, because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the AsTracking() extension method on the LINQ expression, or EnableTracking()
extension method on the repository object (See Enabling / Disabling the Change Tracking section in this document).
This behavior works only if the repository object is injected with one of the read-only repository interfaces (
IReadOnlyRepository<...>
orIReadOnlyBasicRepository<...>
). It won't work if you have injected a standard repository (e.g.IRepository<...>
) then casted it to a read-only repository interface.
Enabling / Disabling the Change Tracking
In addition to the read-only repositories, ABP allows to manually control the change tracking behavior for querying objects. Please see the Enabling / Disabling the Change Tracking section of the Repositories documentation to learn how to use it.
Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the DbContext
instance over the repository, you can use GetDbContext()
or GetDbSet()
extension methods. Example:
GetDbContextAsync
returns aDbContext
reference instead ofBookStoreDbContext
. You can cast it if you need. However, you don't need it in most cases.
Important: You must reference to the
Volo.Abp.EntityFrameworkCore
package from the project you want to access to theDbContext
. This breaks encapsulation, but this is what you want in that case.
Extra Properties & Object Extension Manager
Extra Properties system allows you to set/get dynamic properties to entities those implement the IHasExtraProperties
interface. It is especially useful when you want to add custom properties to the entities defined in an application module, when you use the module as package reference.
By default, all the extra properties of an entity are stored as a single JSON
object in the database.
Entity extension system allows you to to store desired extra properties in separate fields in the related database table. For more information about the extra properties & the entity extension system, see the following documents:
This section only explains the EF Core related usage of the ObjectExtensionManager
.
ObjectExtensionManager.Instance
ObjectExtensionManager
implements the singleton pattern, so you need to use the static ObjectExtensionManager.Instance
to perform all the operations.
MapEfCoreProperty
MapEfCoreProperty
is a shortcut extension method to define an extension property for an entity and map to the database.
Example: Add Title
property (database field) to the IdentityRole
entity:
MapEfCoreEntity
MapEfCoreEntity
is a shortcut extension method to configure the Entity
.
Example: Set the max length of Name
to the IdentityRole
entity:
MapEfCoreDbContext
MapEfCoreDbContext
is a shortcut extension method to configure the DbContext
.
Example: Set the max length of Name
to the IdentityRole
entity of IdentityDbContext
:
If the related module has implemented this feature(explained below), then the new property is added to the model or the DbContext/Entity configure changed. Then you need to run the standard Add-Migration
and Update-Database
commands to update your database to add the new field.
The
MapEfCoreProperty
,MapEfCoreEntity
andMapEfCoreDbContext
methods must be called before using the relatedDbContext
. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has aYourProjectNameEfCoreEntityExtensionMappings
class that is safe to use this method inside.
ConfigureEfCoreEntity, ApplyObjectExtensionMappings and TryConfigureObjectExtensions
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the ConfigureEfCoreEntity
, ApplyObjectExtensionMappings
and TryConfigureObjectExtensions
extension methods in your entity mapping.
Example:
If you call
ConfigureByConvention()
extension method (likeb.ConfigureByConvention()
for this example), ABP internally calls theConfigureObjectExtensions
andConfigureEfCoreEntity
methods. It is a best practice to use theConfigureByConvention()
method since it also configures database mapping for base properties by convention.
The
Object Extension
feature need theChange Tracking
, which means you can't use the read-only repositories for the entities that haveextension properties(MapEfCoreProperty)
, Please see the Repositories documentation to learn the change tracking behavior.
See the "ConfigureByConvention Method" section above for more information.
Advanced Topics
Controlling the Multi-Tenancy
If your solution is multi-tenant, tenants may have separate databases, you have multiple DbContext
classes in your solution and some of your DbContext
classes should be usable only from the host side, it is suggested to add [IgnoreMultiTenancy]
attribute on your DbContext
class. In this case, ABP guarantees that the related DbContext
always uses the host connection string, even if you are in a tenant context.
Example:
Do not use the [IgnoreMultiTenancy]
attribute if any one of your entities in your DbContext
can be persisted in a tenant database.
When you use repositories, ABP already uses the host database for the entities don't implement the
IMultiTenant
interface. So, most of time you don't need to[IgnoreMultiTenancy]
attribute if you are using the repositories to work with the database.
Set Default Repository Classes
Default generic repositories are implemented by EfCoreRepository
class by default. You can create your own implementation and use it for all the default repository implementations.
First, define your default repository classes like that:
First one is for entities with composite keys, second one is for entities with single primary key.
It's suggested to inherit from the EfCoreRepository
class and override methods if needed. Otherwise, you will have to implement all the standard repository methods manually.
Now, you can use SetDefaultRepositoryClasses
option:
Set Base DbContext Class or Interface for Default Repositories
If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example:
IBookStoreDbContext
is implemented by the BookStoreDbContext
class. Then you can use generic overload of the AddDefaultRepositories
:
Now, your custom BookRepository
can also use the IBookStoreDbContext
interface:
One advantage of using an interface for a DbContext is then it will be replaceable by another implementation.
Replace Other DbContextes
Once you properly define and use an interface for DbContext, then any other implementation can use the following ways to replace it:
ReplaceDbContext Attribute
ReplaceDbContext Option
In this example, OtherDbContext
implements IBookStoreDbContext
. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.
Replacing with Multi-Tenancy
It is also possible to replace a DbContext based on the multi-tenancy side. ReplaceDbContext
attribute and ReplaceDbContext
method can get a MultiTenancySides
option with a default value of MultiTenancySides.Both
.
Example: Replace DbContext only for tenants, using the ReplaceDbContext
attribute
Example: Replace DbContext only for the host side, using the ReplaceDbContext
method
Split Queries
ABP enables split queries globally by default for better performance. You can change it as needed.
Example
Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing IEfCoreBulkOperationProvider
.
- You may use example template below: