SkillAgentSearch skills...

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/DataAccessClient
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Packages

Build Status

DataAccessClient

DataAccessClient.EntityFrameworkCore.Relational

DataAccessClient.EntityFrameworkCore.SqlServer

DataAccessClient

NuGet NuGet

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
View on GitHub
GitHub Stars7
CategoryCustomer
Updated11d ago
Forks0

Languages

C#

Security Score

70/100

Audited on Mar 12, 2026

No findings