DataAccessClient
Provides interfaces for Data Access with IRepository, IUnitOfWork and IQueryableSearcher. Also provides haviorial interfaces for entities like IIdentifiable, ICreatable, IModifiable, ISoftDeletable, ITranslatable and IRowVersioned. Last but not least provides some types for Exceptions and searching capabilities like Filtering, Paging, Sorting and Includes. The IRepostory contains some methods to support cloning based on EntityFrameworkCore configuration.
Install / Use
/learn @HenkKin/DataAccessClientREADME
Packages
DataAccessClient.EntityFrameworkCore.Relational
DataAccessClient.EntityFrameworkCore.SqlServer
DataAccessClient
Summary
Provides interfaces for Data Access with IRepository<T>, IUnitOfWork and IQueryableSearcher<T>. Also provides haviorial interfaces for entities like IIdentifiable, ICreatable, IModifiable, ISoftDeletable, ITranslatable, IRowVersionable, ITenantScopable and ILocalizable. Last but not least provides some types for Exceptions and searching capabilities like Filtering, Paging, Sorting and Includes. The IRepostory contains some methods to support cloning based on EntityFrameworkCore configuration.
This library is Cross-platform, supporting net6.0, net7.0, net8.0, net9.0 and net10.0.
Installing DataAccessClient
You should install DataAccessClient with NuGet:
Install-Package DataAccessClient
Or via the .NET Core command line interface:
dotnet add package DataAccessClient
Either commands, from Package Manager Console or .NET Core CLI, will download and install DataAccessClient and all required dependencies.
Dependencies
No external dependencies
(Breaking) Changes
7.0.1: Added option to disable UtcDateTimePropertyEntityBehavior
8.0.1: Removed UtcDateTimePropertyEntityBehavior options and properties with types DateTime and Nullable DateTime no longer default to Utc. THIS IS A BREAKING CHANGE. You have to do that in your own modelbuilder with a convention.
10.1.0: Modified IRowversionable to IRowversionable<TRowVersionableType> to support SqlServer and PostgreSQL and maybe other relational databases.
Version 8.0.1: For example
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<DateTime>()
.HaveConversion<UtcDateTimeValueConverter>();
configurationBuilder
.Properties<DateTime?>()
.HaveConversion<UtcDateTimeValueConverter>();
}
...
public class UtcDateTimeValueConverter : ValueConverter<DateTime?, DateTime?>
{
public UtcDateTimeValueConverter() : this(null)
{
}
public UtcDateTimeValueConverter(ConverterMappingHints mappingHints = null) : base(ConvertToUtcExpression, ConvertToUtcExpression, mappingHints)
{
}
private static readonly Expression<Func<DateTime?, DateTime?>> ConvertToUtcExpression = dateTime => dateTime.HasValue ? ConvertToUtc(dateTime.Value) : dateTime;
public static DateTime ConvertToUtc(DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Unspecified)
{
return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
}
if (dateTime.Kind == DateTimeKind.Local)
{
return dateTime.ToUniversalTime();
}
return dateTime;
}
}
Entity behaviors
The DataAccessClient package provides you a set of EntityBehavior interfaces. These interfaces you can use to decorate your entites.
The implementation packages, like DataAccessClient.EntityFrameworkCore.Relational package, use these interface to apply the behavior automatically.
...
using DataAccessClient;
public class ExampleEntity :
IIdentifiable<int>,
ICreatable<int>,
IModifiable<int>,
ISoftDeletable<int>,
IRowVersionable<byte[]>,
ITranslatable<ExampleEntityTranslation, int, string>,
ITenantScopable<int>
{
// to identify an entity
public int Id { get; set; }
// to track creation
public DateTime CreatedOn { get; set; }
public int CreatedById { get; set; }
// to track modification
public DateTime? ModifiedOn { get; set; }
public int? ModifiedById { get; set; }
// to implement Soft Delete
public bool IsDeleted { get; set; }
public DateTime? DeletedOn { get; set; }
public int? DeletedById { get; set; }
// to implement optimistic concurrency control.
public byte[] RowVersion { get; set; }
// to translate entity specific fields
public ICollection<ExampleEntityTranslation> Translations { get; set; }
// to scope multiple tenants in same database
public int TenantId { get; set; }
// your own fields
public string Name { get; set; }
}
public class ExampleEntityTranslation : IEntityTranslation<ExampleEntity, int, string>
{
public ExampleEntity TranslatedEntity { get; set; }
public int TranslatedEntityId { get; set; }
// language of translations, f.e. en-GB or nl-NL
public string LocaleId { get; set; }
// your custom translatable fields
public string Description { get; set; }
...
}
Alle entity behaviors are optional. No one is required.
All struct types are possible, also the Identifier type of package Identifiers.
For LocaleId the type should by IConvertible, so String is allowed too.
IUnitOfWork and IRepository<T>
To use Repository and UnitOfWork, see example below.
...
using DataAccessClient;
public class HomeController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRepository<ExampleEntity> _exampleEntityRepository;
private readonly IRepository<ExampleSecondEntity> _exampleSecondEntityRepository;
private readonly IQueryableSearcher<ExampleEntity> _exampleEntityQueryableSearcher;
private readonly IQueryableSearcher<ExampleSecondEntity> _exampleSecondEntityQueryableSearcher;
public HomeController(
IUnitOfWork unitOfWork,
IRepository<ExampleEntity> exampleEntityRepository,
IRepository<ExampleSecondEntity> exampleSecondEntityRepository,
IQueryableSearcher<ExampleEntity> exampleEntityQueryableSearcher,
IQueryableSearcher<ExampleSecondEntity> exampleSecondEntityQueryableSearcher)
{
_unitOfWork = unitOfWork;
_exampleEntityRepository = exampleEntityRepository;
_exampleSecondEntityRepository = exampleSecondEntityRepository;
_exampleEntityQueryableSearcher = exampleEntityQueryableSearcher;
_exampleSecondEntityQueryableSearcher = exampleSecondEntityQueryableSearcher;
}
[HttpGet]
public async Task<IActionResult> Test()
{
var exampleEntity1 = new ExampleEntity
{
Name = "DataAccessClient1"
};
var exampleEntity2 = new ExampleEntity
{
Name = "DataAccessClient2"
};
_exampleEntityRepository.Add(exampleEntity1);
_exampleEntityRepository.Add(exampleEntity2);
var exampleSecondEntity1 = new ExampleSecondEntity
{
Name = "SecondDataAccessClient1"
};
var exampleSecondEntity2 = new ExampleSecondEntity
{
Name = "SecondDataAccessClient2"
};
_exampleSecondEntityRepository.Add(exampleSecondEntity1);
_exampleSecondEntityRepository.Add(exampleSecondEntity2);
await _unitOfWork.SaveAsync();
// start change tracking without querying database
var exampleEntityAttach = _exampleEntityRepository.StartChangeTrackingById(10);
// update properties to trigger changetracking
exampleEntityAttach.Name = "Updated DataAccessClient10";
exampleEntity2.Name = "Updated DataAccessClient2";
exampleSecondEntity2.Name = "Updated SecondDataAccessClient2";
await _unitOfWork.SaveAsync();
var exampleEntities = await _exampleEntityRepository.GetChangeTrackingQuery()
.Where(e => !e.IsDeleted)
.ToListAsync();
var exampleSecondEntities = await _exampleSecondEntityRepository.GetChangeTrackingQuery()
.Where(e => !e.IsDeleted)
.ToListAsync();
_exampleEntityRepository.RemoveRange(exampleEntities);
_exampleSecondEntityRepository.RemoveRange(exampleSecondEntities);
await _unitOfWork.SaveAsync();
var criteria = new Criteria();
criteria.OrderBy = "Id";
criteria.OrderByDirection = OrderByDirection.Ascending;
criteria.Page = 1;
criteria.PageSize = 10;
criteria.Search = "Data Access Client";
var exampleEntitiesSearchResults = await _exampleEntityQueryableSearcher.ExecuteAsync(_exampleEntityRepository.GetReadOnlyQuery(), criteria);
var exampleSecondEntitiesSearchResults = await _exampleSecondEntityQueryableSearcher.ExecuteAsync(_exampleSecondEntityRepository.GetReadOnlyQuery(), criteria);
return Json(new{ exampleEntitiesSearchResults, exampleSecondEntitiesSearchResults });
}
}
SoftDelete configuration
To implement SoftDelete into your application your softdeletable entities have to implement the ISoftDeletable<TUserIdentifier> interface. By default this is the only thing to do. If you want to control the SoftDelete behavior then you can inject ISoftDeletableConfiguration service into your logic classes.
The ISoftDeletableConfiguration allows you to
- Enable/Disable SoftDelete Behavior. Disabling SoftDelete behavior also disables the SoftDeleteQueryFilters.
- EnableQueryFilter/DisableQueryFilter.
When using multipe DbContexts, there is only one ISoftDeletableConfiguration per ServiceScope. Disabling QueryFilter will disable QueryFilter for all your DbContexts.
...
using DataAccessClient;
public class HomeController : Controller
{
private readonly IRepository<ExampleEntity> _exampleEntityRepository;
private readonly IQueryableSearcher<ExampleEntity> _exampleEntityQueryableSearcher;
private readonly ISoftDeletableConfigura
