The CoreEx.EntityFrameworkCore
namespace provides extended Entity Framework Core (EF) capabilities.
The motivation is to provide supporting EF Core capabilities for CRUD related access that support standardized CoreEx data access patterns. This for the most part will simplify and unify the approach to ensure consistency of implementation where needed.
The requirements for usage are as follows.
- An entity (DTO) that represents the data that must as a minimum implement
IEntityKey
; generally via either the implementation ofIIdentifier
orIPrimaryKey
. - A model being the underlying configured EF Core data source model.
- An
IMapper
that contains the mapping logic to map to and from the entity and model.
The entity and model are different types to encourage separation between the externalized entity representation and the underlying model; which may be shaped differently, and have different property to column naming conventions, internalized columns, etc.
To support railway-oriented programming whenever a method name includes WithResult
this indicates that it will return a Result
or Result<T>
including the resulting success or failure information. In these instances an Exception
will only be thrown when considered truly exceptional.
The IEfDb
and corresponding EfDb
provides the base CRUD capabilities as follows.
A query is actioned using the EfDbQuery
which is ostensibly a lightweight wrapper over an IQueryable<TModel>
that automatically maps from the model to the entity.
Queried entities are not tracked by default; internally uses AsNoTracking
; this behaviour can be overridden using EfDbArgs.QueryNoTracking
.
Note: a consumer should also consider using IgnoreAutoIncludes
to exclude related data, where not required, to improve query performance.
The following methods provide additional capabilities:
Method | Description |
---|---|
WithPaging |
Adds Skip and Take paging to the query. |
SelectSingleAsync , SelectSingleWithResult |
Selects a single item. |
SelectSingleOrDefaultAsync , SelectSingleOrDefaultWithResultAsync |
Selects a single item or default. |
SelectFirstAsync , SelectFirstWithResultAsync |
Selects first item. |
SelectFirstOrDefaultAsync , SelectFirstOrDefaultWithResultAsync |
Selects first item or default. |
SelectQueryAsync , SelectQueryWithResultAsync |
Select items into or creating a resultant collection. |
SelectResultAsync , SelectResultWithResultAsync |
Select items creating a ICollectionResult which also contains corresponding PagingResult . |
Gets (GetAsync
or GetWithResultAsync
) the entity for the specified key mapping from the model. Uses the DbContext.Find
internally for the model and specified key.
Where the data is not found, then a null
will be returned. Where the model implements ILogicallyDeleted
and IsDeleted
then this acts as if not found and returns a null
.
Creates (CreateAsync
or CreateWithResultAsync
) the entity by firstly mapping to the model. Uses the DbContext.Add
to begin tracking the model which will be inserted into the database when DbContext.SaveChanges
is called.
Where the entity implements IChangeLogAuditLog
generally via ChangeLog
or ChangeLogEx
, then the CreatedBy
and CreatedDate
properties will be automatically set from the ExecutionContext
.
Where the entity and/or model implements ITenantId
then the TenantId
property will be automatically set from the ExecutionContext
.
Generally, the DbContext.SaveChanges
is called to perform the insert; unless EfDbArgs.SaveChanges
is set to false
(defaults to true
).
The inserted model is then re-mapped to the entity and returned where EfDbArgs.Refresh
is set to true
(default); this will ensure all properties updated as part of the insert are included in the refreshed entity.
Updates (UpdateAsync
or UpdateWithResultAsync
) the entity by firstly mapping to the model. Uses the DbContext.Update
to begin tracking the model which will be updated within the database when DbContext.SaveChanges
is called.
First will check existence of the model by performing a DbContext.Find
. Where the data is not found, then a NotFoundException
will be thrown. Where the model implements ILogicallyDeleted
and IsDeleted
then this acts as if not found and will also result in a NotFoundException
.
Where the entity implements IETag
this will be checked against the just read version, and where not matched a ConcurrencyException
will be thrown. Also, any DbUpdateConcurrencyException
thrown will be converted to a corresponding ConcurrencyException
for consistency.
Where the entity implements IChangeLogAuditLog
generally via ChangeLog
or ChangeLogEx
, then the UpdatedBy
and UpdatedDate
properties will be automatically set from the ExecutionContext
.
Where the entity and/or model implements ITenantId
then the TenantId
property will be automatically set from the ExecutionContext
.
Generally, the DbContext.SaveChanges
is called to perform the update; unless EfDbArgs.SaveChanges
is set to false
(defaults to true
).
The updated model is then re-mapped to the entity and returned where EfDbArgs.Refresh
is set to true
(default); this will ensure all properties updated as part of the update are included in the refreshed entity.
Deletes (DeleteAsync
or DeleteWithResultAsync
) the entity either physically or logically.
First will check existence of the model by performing a DbContext.Find
. Where the data is not found, then a NotFoundException
will be thrown. Where the model implements ILogicallyDeleted
and IsDeleted
then this acts as if not found and will also result in a NotFoundException
.
Where the model implements ILogicallyDeleted
then an update will occur after setting IsDeleted
to true
. Uses the DbContext.Update
to begin tracking the model which will be updated within the database when DbContext.SaveChanges
is called.
Otherwise, will physically delete. Uses the DbContext.Remove
to begin tracking the model which will be deleted from the database when DbContext.SaveChanges
is called.
Generally, the DbContext.SaveChanges
is called to perform the update; unless EfDbArgs.SaveChanges
is set to false
(defaults to true
).
To use EfDB
relationships to the EF Core DbContext
must be established as follows.
Database
must be defined; see example.DbContext
andDatabase
relationship must be defined; see example.EfDb
andDbContext
relationship must be defined; see example.
Alternatively, review the Beef MyEf.Hr data sample implementation.
Where leveraging MySQL it is recommended to use the Pomelo.EntityFrameworkCore.MySql package; this has greater uptake and community supporting than the Oracle-enabled MySql.Data.EntityFrameworkCore package.
The DbContextOptionsBuilder
code within the DbContext
implementation should be as follows (review version specification) to enable.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
if (!optionsBuilder.IsConfigured)
optionsBuilder.UseMySql(BaseDatabase.GetConnection(), ServerVersion.Create(new Version(8, 0, 33), Pomelo.EntityFrameworkCore.MySql.Infrastructure.ServerType.MySql));
}