Fluxera.Repository.EntityFrameworkCore
8.0.3
Prefix Reserved
See the version list below for details.
dotnet add package Fluxera.Repository.EntityFrameworkCore --version 8.0.3
NuGet\Install-Package Fluxera.Repository.EntityFrameworkCore -Version 8.0.3
<PackageReference Include="Fluxera.Repository.EntityFrameworkCore" Version="8.0.3" />
paket add Fluxera.Repository.EntityFrameworkCore --version 8.0.3
#r "nuget: Fluxera.Repository.EntityFrameworkCore, 8.0.3"
// Install Fluxera.Repository.EntityFrameworkCore as a Cake Addin #addin nuget:?package=Fluxera.Repository.EntityFrameworkCore&version=8.0.3 // Install Fluxera.Repository.EntityFrameworkCore as a Cake Tool #tool nuget:?package=Fluxera.Repository.EntityFrameworkCore&version=8.0.3
Fluxera.Repository
A generic repository implementation.
This repository implementation hides the actual storage implementation from the user. The only part where the abstraction leaks storage specifics is the configuration of a storage specific repository implementation.
The repository can be used with or without a specialized implementation, f.e. one can
just use one of the provided interfaces IRepository
or IReadOnlyRepository
.
The repository implementation is async from top to bottom.
public class PersonController : ControllerBase
{
private readonly IRepository<Person> repository;
public PersonController(IRepository<Person> repository)
{
this.repository = repository
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
Person result = await this.repository.GetAsync(id);
return this.Ok(result);
}
}
If you prefer to create a specialized interface and implementation you can do so, but you have to register the service yourself.
public interface IPersonRepository : IRepository<Person, Guid>
{
}
public class PersonRepository : Repository<Person, Guid>, IPersonRepository
{
public PersonRepository(IRepository<Person, Guid> innerRepository)
: base(innerRepository)
{
}
}
As you can see, you have to provide the IRepository<T, TKey>
as dependency to the constructor
or your specialized repository. The overall architecture of the repository uses the decorator pattern
heavily to split up the many features around the storage implementation, to keep things simple and
have every decorator layer only impolement a single responseibility. Your specialized repository then
acts as the outermost layout of decorators.
Supported Storages
- Entity Framework Core
- LiteDB
- MongoDB
- In-Memory (for testing and prototyping only)
Additional Features
Besides to being able top perform CRUD operation with the underlying data storage, the repository implementation provides several additional features.
Traits
Sometimes you don't want to expose the complete repository interface to you specialized implementations
and sometimes even the IReadOnlyRepository
may be too much. For those cases you can just use
the trait interfaces yo want to support.
ICanAdd
Provides methods to add items.ICanAggregate
Provides methods to aggregate results, likeCount
andSum
.ICanFind
Provides methods to find single and multiple results.ICanGet
Provides methods to get items by ID.ICanRemove
Provides methods to remove items.ICanUpdate
Provides methods to update items.
Using this set of interfaces you cann create a specialized repository interface how you see fit.
public interface IPersonRepository : ICanGet<Person, Guid>, ICanAggregate<Person, Guid>
{
}
Specifications
In the most basic form you can execute find queries using expressions that provide the predicate
to satify by the result items. But this may lead to queries cluttered around your application,
maybe duplicating code in several places. Updating queries may then become cumbersome in the future.
To prevent this from happening you can create specialized specification classes that encapsulate
queries, or parts of queries and can be re-used in your application. Any specification class can
be combines with others using operations like And
, Or
, Not
.
You can f.e. have a specification that finds all persons by name and another one that finds all persons by age.
public sealed class PersonByNameSpecification : Specification<Person>
{
private readonly string name;
public PersonByNameSpecification(string name)
{
this.name = name;
}
protected override Expression<Func<Person, bool>> BuildQuery()
{
return x => x.Name == this.name;
}
}
public sealed class PersonByAgeSpecification : Specification<Person>
{
private readonly int age;
public PersonByAgeSpecification(int age)
{
this.age = age;
}
protected override Expression<Func<Person, bool>> BuildQuery()
{
return x => x.Age == this.age;
}
}
You can combine those to find any person with a specific name and age.
PersonByNameSpecification byNameQuery = new PersonByNameSpecification("Tester");
PersonByAgeSpecification byAgeQuery = new PersonByAgeSpecification(35);
ISpecification<Person> query = byNameQuery.And(byAgeQuery);
Interception
Sometimes you may want to execute actions before or after you execute methods of the repository.
You can do that using the IInterceptor
interface. All you have to do is implement this interface
and register the interceptor when configuring the repository. Your interceptor will then execute the
methods before and after repository calls.
You can use this feature f.e. to set audit timesstamps (CreatedAt, UpdatedAt, ...) or to implement more complex szenarios like multi-tenecy based on a discriminator colum. You can modify the queries that should be sent to the underlying storage. If the interceptor feature is enabled (i.e at least one custom interceptor is registered) it makes sure that any query by ID is redirected to a predicate based method, so you are sure that even a get-by-id will benefit from you modifiing the predicate.
Query Options
To control how you query data is returned, you can use the QueryOptions
to create sorting and
paging options that will be applied to the base-query on execution.
Repository Decorators Hierarchy
The layers of decorators a executed in the following order.
- Diagnostics
This decorator produces diagnostic events using
System.Diagnostic
that can be instrumented by telemetry systems. - Exception Logging This decorator just catches every exception that may occur then logs the exception and throws it again.
- Guards This decorator checks the inputs for sane values and checks if the repository instance was disposed and throws corresponding exception.
- Validation
This decorator validates the inputs to
Add
andUpdate
methods for validity using the configures validators. - Caching If the caching is enabled this decorator manages the handling of the cache. It tries to get the result of a query from the cache and if none was found executes the query and stores the result, in the cache.
- Domain Events This decorator dispatches added domain events after an item was added, updated or removed. The exact time of execution depends on the configuration of the repository. If UoW is enabled the domain events are dispatched to a outbox queue and flushed to the event handlers when the UoW saves the work. If the UoW is disabled the domain events are immediately dispatched to the event handlers after the item was added, updated or removed.
- Data Storage This is the base layer around wich all decorators are configures. This is the storage specific repository implementation.
Unit of Work
The Unit of Work (UoW) pattern is disabled by default an can be enabled using the EnableUnitOfWork
method
of the IRepositoryOptionsBuilder
.
When enabled, a simple call to, f.e. AddAsync(item)
will not persist the given item instantly. The
add operation is added to the UoW instance and is executed when the UoW for the repository saves the changes.
await this.repository.AddAsync(new Company
{
Name = "First Company",
LegalType = LegalType.LimitedLiabilityCompany
});
await this.repository.AddAsync(new Company
{
Name = "Second Company",
LegalType = LegalType.Corporation
});
await this.unitOfWork.SaveChangesAsync();
Due to the fact that this library supports multiple, different repositories at the same time, a UoW
instance can not be obtained directly using dependency injection. You can get a UoW instance from
the IUnitOfWorkFactory
with the name of the repository.
this.unitOfWork = unitOfWorkFactory.CreateUnitOfWork("MongoDB");
OpenTelemetry
The repository produces Activity
events using System.Diagnistic
. Those events are used
my the OpenTelemetry integration to support diagnostic insights. To enable the support for OpenTelemetry
just add the package Fluxera.Repository.OpenTelemetry
to your OpenTelemetry enabled application
and add the instrumentation for the Repository shown below.
// Configure important OpenTelemetry settings, the console exporter, and automatic instrumentation.
builder.Services.AddOpenTelemetryTracing(builder =>
{
builder
.AddConsoleExporter()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("WebApplication1", "1.0.0"))
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation()
.AddMongoDBInstrumentation()
// Add the instrumentation for the Repository.
.AddRepositoryInstrumentation();
});
Usage
You can configure different repositories for different aggregate root types, f.e. you can have persons in a MongoDB and invoices in a SQL database. You can choose to omit the repository name using overloads that do not accept a name parameter. In this case, the default name "Default" is used for the repository.
// Add the repository services to the service collection and configure the repositories.
services.AddRepository(builder =>
{
// Add default services and the repositories.
builder.AddMongoRepository<SampleMongoContext>("MongoDB", options =>
{
// Enabled UoW for this repository.
options.EnableUnitOfWork();
// Configure for what aggregate root types this repository uses.
options.UseFor<Person>();
// Configure the domain events (optional).
options.AddDomainEventHandling(events =>
{
events.AddEventHandlers(typeof(Person).Assembly);
});
// Configure validation providers (optional).
options.AddValidation(validation =>
{
validation.AddValidatorFactory(factory =>
{
factory.AddDataAnnotations(validation.RepositoryName);
});
});
// Configure caching (optional).
options.AddCaching((caching =>
{
caching
.UseStandard()
.UseTimeoutFor<Person>(TimeSpan.FromSeconds(20));
});
// Configure the interceptors (optional).
options.AddInterception(interception =>
{
interception.AddInterceptors(typeof(Person).Assembly);
});
// Set storage specific settings.
options.AddSetting("Mongo.ConnectionString", "mongodb://localhost:27017");
options.AddSetting("Mongo.Database", "test");
});
});
Storage-specific options are configure using a repository-specific context class. The following example shows the configuration of a MongoDB repository.
public class SampleMongoContext : MongoContext
{
protected override void ConfigureOptions(MongoContextOptions options)
{
options.ConnectionString = "mongodb://localhost:27017";
options.Database = "sample";
}
}
The context types are registered as scoped services in the container. The void ConfigureOptions(MongoContextOptions options)
method is called whenever an instance of the context is created. In a web application this will occur for
every request. You can then modify, f.e. the connection strings or database names to use for this context instance.
This comes in handy if you plan on implementing "database-per-tenant" data isolation in SaaS szenarios.
References
The OpenTelemetry project.
The MongoDB C# Driver project.
The Entity Framework Core project.
The LiteDB project.
Product | Versions 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 is compatible. 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 is compatible. 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. |
-
net6.0
- Fluxera.Enumeration.EntityFrameworkCore (>= 8.0.2)
- Fluxera.Guards (>= 8.0.1)
- Fluxera.Repository (>= 8.0.3)
- Fluxera.StronglyTypedId.EntityFrameworkCore (>= 8.0.2)
- Fluxera.ValueObject.EntityFrameworkCore (>= 8.0.2)
- Microsoft.EntityFrameworkCore (= 6.0.25)
-
net7.0
- Fluxera.Enumeration.EntityFrameworkCore (>= 8.0.2)
- Fluxera.Guards (>= 8.0.1)
- Fluxera.Repository (>= 8.0.3)
- Fluxera.StronglyTypedId.EntityFrameworkCore (>= 8.0.2)
- Fluxera.ValueObject.EntityFrameworkCore (>= 8.0.2)
- Microsoft.EntityFrameworkCore (= 7.0.14)
-
net8.0
- Fluxera.Enumeration.EntityFrameworkCore (>= 8.0.2)
- Fluxera.Guards (>= 8.0.1)
- Fluxera.Repository (>= 8.0.3)
- Fluxera.StronglyTypedId.EntityFrameworkCore (>= 8.0.2)
- Fluxera.ValueObject.EntityFrameworkCore (>= 8.0.2)
- Microsoft.EntityFrameworkCore (>= 8.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Fluxera.Repository.EntityFrameworkCore:
Package | Downloads |
---|---|
Fluxera.Extensions.Hosting.Modules.Persistence.EntityFrameworkCore
A module that enables EntityFrameworkCore persistence. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
8.7.3 | 38 | 11/1/2024 |
8.7.2 | 33 | 11/1/2024 |
8.7.1 | 129 | 6/17/2024 |
8.7.0 | 105 | 6/16/2024 |
8.6.5 | 136 | 6/15/2024 |
8.6.4 | 89 | 6/12/2024 |
8.6.3 | 95 | 6/12/2024 |
8.6.2 | 92 | 6/8/2024 |
8.6.1 | 109 | 6/2/2024 |
8.6.0 | 132 | 5/26/2024 |
8.5.0 | 146 | 4/28/2024 |
8.4.2 | 108 | 4/28/2024 |
8.4.1 | 107 | 4/28/2024 |
8.4.0 | 171 | 4/26/2024 |
8.3.0 | 134 | 4/25/2024 |
8.2.0 | 120 | 4/24/2024 |
8.1.0 | 140 | 4/22/2024 |
8.0.10 | 224 | 4/18/2024 |
8.0.9 | 207 | 4/13/2024 |
8.0.8 | 130 | 4/13/2024 |
8.0.7 | 199 | 3/20/2024 |
8.0.6 | 299 | 2/22/2024 |
8.0.5 | 210 | 2/22/2024 |
8.0.4 | 394 | 1/23/2024 |
8.0.3 | 548 | 1/4/2024 |
8.0.2 | 503 | 11/24/2023 |
8.0.1 | 405 | 11/23/2023 |
8.0.0 | 440 | 11/16/2023 |
7.2.6 | 589 | 7/23/2023 |
7.2.5 | 572 | 7/20/2023 |
7.2.4 | 541 | 6/21/2023 |
7.2.3 | 626 | 4/29/2023 |
7.2.2 | 606 | 4/25/2023 |
7.2.1 | 576 | 4/25/2023 |
7.2.0 | 679 | 4/13/2023 |
7.1.4 | 646 | 3/22/2023 |
7.1.3 | 644 | 3/16/2023 |
7.1.2 | 726 | 2/28/2023 |
7.1.1 | 752 | 1/22/2023 |
7.1.0 | 725 | 1/18/2023 |
7.0.11 | 821 | 12/28/2022 |
7.0.10 | 692 | 12/28/2022 |
7.0.9 | 726 | 12/22/2022 |
7.0.8 | 716 | 12/13/2022 |
7.0.7 | 728 | 12/13/2022 |
7.0.6 | 676 | 12/12/2022 |
7.0.5 | 726 | 12/12/2022 |
7.0.4 | 715 | 12/9/2022 |
7.0.3 | 714 | 11/21/2022 |
7.0.2 | 696 | 11/21/2022 |
7.0.1 | 744 | 11/19/2022 |
7.0.0 | 788 | 11/13/2022 |
6.2.1 | 845 | 10/12/2022 |
6.2.0 | 855 | 10/1/2022 |
6.1.19 | 799 | 9/20/2022 |
6.1.18 | 848 | 9/15/2022 |
6.1.17 | 871 | 7/30/2022 |
6.1.16 | 872 | 6/30/2022 |
6.1.15 | 841 | 6/15/2022 |
6.1.14 | 857 | 6/7/2022 |
6.1.13 | 836 | 6/7/2022 |
6.1.12 | 834 | 6/3/2022 |
6.1.8 | 883 | 6/3/2022 |
6.1.7 | 854 | 6/1/2022 |
6.1.6 | 843 | 5/31/2022 |
6.1.5 | 845 | 5/31/2022 |
6.1.4 | 862 | 5/30/2022 |
6.1.3 | 837 | 5/29/2022 |
6.1.2 | 852 | 5/29/2022 |
6.1.1 | 816 | 5/29/2022 |
6.1.0 | 846 | 5/28/2022 |
6.0.21 | 841 | 5/27/2022 |
6.0.19 | 807 | 5/18/2022 |
6.0.18 | 880 | 5/10/2022 |
6.0.16 | 903 | 5/5/2022 |
6.0.14 | 849 | 4/20/2022 |
6.0.13 | 857 | 4/20/2022 |
6.0.12 | 845 | 4/19/2022 |
6.0.11 | 860 | 4/19/2022 |
6.0.10 | 859 | 4/11/2022 |
6.0.9 | 847 | 4/7/2022 |
6.0.8 | 845 | 3/26/2022 |