heitech.efXt 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package heitech.efXt --version 1.0.0                
NuGet\Install-Package heitech.efXt -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="heitech.efXt" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add heitech.efXt --version 1.0.0                
#r "nuget: heitech.efXt, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install heitech.efXt as a Cake Addin
#addin nuget:?package=heitech.efXt&version=1.0.0

// Install heitech.efXt as a Cake Tool
#tool nuget:?package=heitech.efXt&version=1.0.0                

efXt

Aim of this library:

  • Use Ef Core with common functionality so you dont have to implement it over and over again.
    • IRepository<TEntity> interface
    • Specification pattern for repeatable
    • UnitOfWork and Rollback for better control of the ChangeTracking
  • Make it easy to register the dbContext and setup code via the Microsoft.Extensions.DependencyInjection
  • There is also an abstract class for the Repositories (GenericRepository<TEntity, TId>) which implements the most basic functions already
  • All implementations of the IRepository<,> interface are also automatically added to the Dependency Injection (any assembly in the dependent ones of the executing assembly)

IUnitOfWork

/// <summary>
/// Represents all Commands to interact with the database and have a 
/// Transaction scope in the Application before committing the changes to the database
/// </summary>
public interface IUnitOfWork : IAsyncDisposable
{
   void Add<T>(T one)
       where T : class;
   void  AddMany<T>(IEnumerable<T> many)
       where T : class;

   void Update<T>(T one)
       where T : class;
   void UpdateMany<T>(IEnumerable<T> many)
       where T : class;

   void Delete<T>(T one)
       where T : class;
   void DeleteMany<T>(IEnumerable<T> many)
       where T : class;

   ///<summary>
   /// Resets all changes on this entity, so it will not be commited to the database on Save
   ///</summary>
   void RollbackOne<T>(T entity, Func<T, bool> predicate)
       where T : class;

   ///<summary>
   /// Resets all changes on all changed entities, so it will not be commited to the database on Save
   ///</summary>
   void RollBackAll();

   ///<summary>
   /// Commit all the changes made to any entities since the start of the UnitOfWork
   ///</summary>
   Task SaveAsync();
}

IRepository<TEntity, TId>

///<summary>
/// Abstraction for DataAccess with generic and Specification methods
///</summary>
public interface IRepository<TEntity, TId>
       where TEntity : class
{
   /// <summary>
   /// for special cases operate directly on the IQueryable
   /// </summary>
   IQueryable<TEntity> AsQueryable();

   Task<TEntity> GetByIdAsync(TId id);
   Task<TEntity> GetByIdAsync(ISpecification<TEntity> specification);

   Task<IReadOnlyList<TEntity>> GetAllAsync();
   Task<IReadOnlyList<TEntity>> GetAllBySpecificationAsync(ISpecification<TEntity> specification);
}

ISpecification<TEntity>

///<summary>
/// Provides Filtering capabilities for a Query on the Repository calls.
///</summary>
   public interface ISpecification<TEntity>
       where TEntity : class
{
   Expression<Func<TEntity, bool>> Build();
}

GenericRepositry<TEntity, TId>

public abstract class GenericRepository<TEntity, TId> : IRepository<TEntity, TId>
   where TEntity : class
{
   protected DbContext Context { get; }

   public GenericRepository(DbContext dbContext)
       => Context = dbContext;

   public abstract Task<TEntity> GetByIdAsync(TId id);
   
   public IQueryable<TEntity> AsQueryable() => Context.Set<TEntity>().AsQueryable();

   public Task<TEntity> GetByIdAsync(ISpecification<TEntity> specification) 
       => Context.Set<TEntity>().FirstOrDefaultAsync(specification.Build());

   public async Task<IReadOnlyList<TEntity>> GetAllAsync()
       => await Context.Set<TEntity>().ToListAsync();

   public async Task<IReadOnlyList<TEntity>> GetAllBySpecificationAsync(ISpecification<TEntity> specification)
       => await Context.Set<TEntity>().Where(specification.Build()).ToListAsync();
}

Examples

See some examples below. We assume Asp.Net Core for this Scenario. Else you´d need to use the static Factory class and manage the instances yourself. First Register it in the Startup of Asp.Net Core

// example with SqlServer, but any Action on DbContextOptionsBuilder are allowed.
 services.RegisterEfAbstraction<MyDbContext>(options => options.UseSqlServer("connectionstring"));

Assume the following entities as the POCOS for EF Core

public class Entity
{
    public int Id { get; set; }
    public string Note { get; set; }
}

Example for all interfaces in some fictious usecases for our example Entity

public class EntityUseCases
{
   private readonly IUnitOfWork _unitOfWork;
   private readonly IRepository<Entity, int> _repository;

   public EntityUseCases(IRepository<Entity, int> repository, IUnitOfWork unitOfWork)
   {
       _unitOfWork = unitOfWork;
       _repository = repository;
   }

   public Task AddEntity(int id, string note)
   {
       _unitOfWork.Add(new Entity { Id = id, Note = note });
       return _unitOfWork.SaveAsync();
   }

   public async Task UpdateNote(int id, string note)
   {
       Entity entity = await _repository.GetByIdAsync(id);
       // change tracking on db context already marks the Entity as changed
       // no need to call _unitOfOwrk.Update, only if an entity is not attached yet (e.g. when using AsNoTracking)
       entity.Note = note;
       await _unitOfWork.SaveAsync();
   }

   // it is also possible to provide multiple entities for each call to Add, Update or Delete with the Many Suffix
   public async Task DeleteAllEntities()
   {
       IReadOnlyList<Entity> entities = await _repository.GetAllAsync();
       _unitOfWork.DeleteMany(entities);
       await _unitOfWork.SaveAsync();
   }

   // read use case with special filter (Specifications also work for )
   public Task<IReadOnlyList<Entity>> GetEntitiesWithSpecificNote()
   {
       var specification = new NoteShorterThanThreeCharsSpecification();
       return _repository.GetAllBySpecificationAsync(specification);
   }

   private class NoteShorterThanThreeCharsSpecification : ISpecification<Entity>
   {
       public Expression<Func<Entity, bool>> Build()
           => entity => entity.Note.Length <= 3;
   }
}

Example for a specific Query you still want to be able to test without the dbContext

public interface IFirstTenEntitiesQuery
{
   Task<IReadOnlyList<Entity>> ExecuteAsync();
}

public class Query : IFirstTenEntitiesQuery
{
   private readonly DbContext _context;

   public Query(DbContext repository)
       => _context = repository;

   public async Task<IReadOnlyList<Entity>> ExecuteAsync()
       => await _context.Set<Entity>()
                           .Take(10)
                           .ToListAsync();
}

Example for Specified Query (testable)

  • Specification pattern for repeatable queries
  • UnitOfWork and Rollback for better control of the ChangeTracking
  • Make it easy to register the dbContext and let the abstractions be injected so you can test your code without repetitive mocks for Repositories and such

Examples tbd.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.2 432 12/18/2022
1.0.1 536 8/23/2022
1.0.0 315 11/27/2021

initial release