Skip to content

Latest commit

 

History

History

CoreEx.EntityFrameworkCore

CoreEx

The CoreEx.EntityFrameworkCore namespace provides extended Entity Framework Core (EF) capabilities.


Motivation

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.


Requirements

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 of IIdentifier or IPrimaryKey.
  • 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, etc.


Railway-oriented programming

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.


CRUD capabilities

The IEfDb and corresponding EfDb provides the base CRUD capabilities as follows.


Query (read)

A query is actioned using the EfDbQuery which is obstensibly 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.

Get (Read)

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.


Create

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.


Update

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.


Delete

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).


Usage

To use EfDB relationships to the EF Core DbContext must be established as follows.

Alternatively, review the Beef MyEf.Hr data sample implementation.


MySql

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));
}