Mindfire.EFRepository
2.3.5
dotnet add package Mindfire.EFRepository --version 2.3.5
NuGet\Install-Package Mindfire.EFRepository -Version 2.3.5
<PackageReference Include="Mindfire.EFRepository" Version="2.3.5" />
paket add Mindfire.EFRepository --version 2.3.5
#r "nuget: Mindfire.EFRepository, 2.3.5"
// Install Mindfire.EFRepository as a Cake Addin #addin nuget:?package=Mindfire.EFRepository&version=2.3.5 // Install Mindfire.EFRepository as a Cake Tool #tool nuget:?package=Mindfire.EFRepository&version=2.3.5
EFRepository
Use a LINQ-Enabled version of the Repository Pattern for Entity Framework.
Features Include
- Compatible with .NET Framework and .NET Core Frameworks
- Source Generators automatically create extension methods that allow for a very readable fluent syntax. The generated methods start with
By
E.g.
var query = Repo.Query<Users>()
.ByUsernameStartsWith("Bill")
.ByIsDeleted(false)
.ByRegistrationDateIsBefore(startDate)
.AsNoTracking();
- Decoupling Data Context from Code for Better Testability via the
IRepository
interface - C# 10
nullable
compatible code is generated - Automatic Update / Insert detection with
.AddOrUpdate
- Force Adding of New Object with
.AddNew(object)
- FindOne to find objects by key. Will even work with composite keys
- Events for objects being added or modified or deleted
async
and synchronous methods provided where possible. Chaining to LINQ's async methods is strongly advised (like.CountAsync()
).
Note: If a value is passed into a By
but is null, it will be ignored. If you want to get the the value where the field is null, then use the By...IsNull()
or By...IsNotNull()
. E.g.:
var query = Repo.Query<Users>()
.ByUsernameStartsWith(null); // No users filtered here.
This is helpful if you want to create a "search" where you have some of the values but always have the option to have some of the values.
It is often necessary to separate the server-side LINQ statements from the client side. In those cases use the .AsEnumerable()
function. This will convert the IQueryable to in IEnumerable and everything after that statement will happen after the data is retrieved and there will be no attempt to convert those expressions into the query. A good use-case for this is the mapping of an collection of objects returned from the query to a different type.
Setting Up
Setting up is easy! Add the Mindfire.EFRepository
nuget package to your project, then follow the steps below.
Step 1 - Dependency Registration
In the example below the
// This isn't really necessary, but it's a really good idea. If you have a type that you may not use or if
// you have a type that needs a factory (to use with a using statement, for example), then this pattern
// might be right up your alley
services.AddTransient(typeof(Lazy<>));
services.AddTransient(typeof(Func<>));
// Now register your DataContext
services.AddScoped<DbContext, ExampleDbContext>();
// Now register IRepository
services.AddScoped<IRepository, Repository>();
// Register your other services that depend on IRepository
services.AddScoped<IExampleService, ExampleService>();
Step 2 - Use In Your Project
This example shows a service that depends on an instance of IRepository
. This service could then be injected into something like a Controller for API. It is a good idea to map from your EF database objects to domain objects. This controller uses AutoMapper's IMapper
interface to map between domain objects and data objects.
public class OrderService : IOrderService
{
protected IMapper Mapper { get; }
protected IRepository Repo { get; }
public OrderService(IMapper mapper, IRepository repository)
{
Mapper = mapper;
Repo = repository;
}
public async Task<Order> GetOrder(int orderId)
{
var order = await Repo.Query<Order>()
.ByOrderId(orderId)
.FirstOrDefaultAsync();
return Mapper.Map<Order>(order);
}
public async Task<Order> AddOrder(Order order)
{
var mapped = Mapper.Map<DB.Order>(order);
mapped.Created = DateTimeOffset.Now;
Repository.AddOrUpdate(mapped);
await Repository.SaveAsync();
return await GetOrder(mapped.OrderId);
}
...
}
Step 3 - Unit Testing
Here is a quick example of how to unit test a service that has a dependency on some data from the IRepository
interface. The example below uses XUnit
with Shouldly
and FakeItEasy
.
You'll need to add a reference to the project with your DbContext
in it.
...
using EFRepository;
using FakeItEasy;
using Shouldly;
...
public sealed class OrderServiceTests
{
[Theory, InlineData(1)]
public async Task OrderQueryTest(int orderId)
{
// Arrange
var repo = A.Fake<IRepository>();
A.CallTo(() => repo.Query<Data.Order>())
.Returns(GetFakeOrderData().AsQueryable());
var orderService = new OrderService(repo);
// Act
var target = await orderService.GetOrderById(orderId);
// Assert
A.CallTo(() => repo.Query<Data.Order>()).MustHaveHappened();
target
.ShouldNotBeNull()
.OrderId.ShouldBe(orderId);
}
private IEnumerable<Data.Order> GetFakeOrderData() => new[]
{
new Data.Order
{
OrderId = 1,
Created = DateTime.Now,
Email = "homer@compuserv.net"
}
};
}
Interface
Here is the full interface for IRepository
. Beyond what is listed in the interface, the source generators create the following prototypes for each of the following types:
Numeric Types (byte, short, int, long, single, double, decimal) and bool
- By{Name}({type}? value)
- By{Name}GreaterThan({type}? value)
- By{Name}GreaterThanOrEqual({type}? value)
- By{Name}LessThan({type}? value)
- By{Name}LessThanOrEqual({type}? value)
DateTime or DateTimeOffset
- By{Name}(DateTime? value)
- By{Name}IsBefore(DateTime? value)
- By{Name}IsAfter(DateTime? value)
- By{Name}IsBetween(DateTime? start, DateTime? end)
- By{Name}OnDate(DateTime? value) - Same day, ignore the time
String
- By{Name}(string? value)
- By{Name}IsNullOrWhiteSpace()
- By{Name}IsNotNullOrWhiteSpace()
- By{Name}Contains(string? value)
- By{Name}StartsWith(string? value)
- By{Name}EndsWith(string? value)
Any type that is nullable (including string
)
- By{Name}IsNull()
- By{Name}IsNotNull()
/// <summary>
/// Interface for interacting with data storage through a queryable repository pattern
/// </summary>
public interface IRepository : IDisposable
{
/// <summary>Event that fires when an item is added</summary>
event Action<object> ItemAdding;
/// <summary>Event that fires when an itemis modified</summary>
event Action<object> ItemModifing;
/// <summary>Event that fires when an item is deleted</summary>
event Action<object> ItemDeleting;
/// <summary>Queriable Entity</summary>
IQueryable<TEntity> Query<TEntity>() where TEntity : class, new();
/// <summary>
/// Find an entity based on key(s)
/// </summary>
/// <param name="keys">The key(s) for the table</param>
/// <returns>Entity if found, otherwise null</returns>
TEntity FindOne<TEntity>(params object[] keys) where TEntity : class, new();
/// <summary>
/// Find an entity based on key(s)
/// </summary>
/// <param name="keys">The key(s) for the table</param>
/// <returns>Entity if found, otherwise null</returns>
Task<TEntity> FindOneAsync<TEntity>(params object[] keys) where TEntity : class, new();
/// <summary>
/// Adds entities explicily, even if a key is present
/// </summary>
/// <param name="values">Entities to add</param>
void AddNew<TEntity>(params TEntity[] values) where TEntity : class, new();
/// <summary>
/// Add or update entities
/// </summary>
/// <remarks>
/// If the key field of the entity is populated with a non-default value, the framework
/// will assume that the entity is being updated.
/// </remarks>
/// <param name="values">Entities to add</param>
void AddOrUpdate<TEntity>(params TEntity[] values) where TEntity : class, new();
/// <summary>
/// Add or update entities
/// </summary>
/// <param name="collection">Entities to add</param>
void AddOrUpdate<TEntity>(IEnumerable<TEntity> collection) where TEntity : class, new();
/// <summary>
/// Delete a single entity by key(s)
/// </summary>
/// <param name="keys">The key(s) for the table</param>
void DeleteOne<TEntity>(params object[] keys) where TEntity : class, new();
/// <summary>
/// Delete one or more entities
/// </summary>
/// <param name="values">Entities to delete</param>
void Delete<TEntity>(params TEntity[] values) where TEntity : class, new();
/// <summary>
/// Delete one or more entities
/// </summary>
/// <param name="collection">Entities to delete</param>
void Delete<TEntity>(IEnumerable<TEntity> collection) where TEntity : class, new();
/// <summary>
/// Save pending changes for the collection
/// </summary>
/// <returns>Number of affected entities</returns>
int Save();
/// <summary>
/// Save pending changes for the collection async with cancellation
/// </summary>
/// <param name="cancellationToken">Cancellation Token</param>
/// <returns>Number of affected entities</returns>
Task<int> SaveAsync(CancellationToken cancellationToken = default);
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 is compatible. net5.0-windows was computed. 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. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 is compatible. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net452 is compatible. net46 was computed. net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETCoreApp 3.0
- Microsoft.EntityFrameworkCore (>= 3.1.22)
-
.NETFramework 4.5.2
- EntityFramework (>= 6.1.3)
-
.NETStandard 2.0
- Microsoft.EntityFrameworkCore (>= 2.2.6)
-
.NETStandard 2.1
- Microsoft.EntityFrameworkCore (>= 3.1.22)
-
net5.0
- Microsoft.EntityFrameworkCore (>= 5.0.13)
-
net6.0
- Microsoft.EntityFrameworkCore (>= 6.0.1)
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 |
---|---|---|
2.3.5 | 2,031 | 5/20/2022 |
2.2.0 | 862 | 1/13/2022 |
2.1.1 | 874 | 1/12/2022 |
2.0.0 | 702 | 1/11/2022 |
2.0.0-alpha | 930 | 10/7/2020 |
1.1.3 | 1,099 | 11/27/2019 |
1.1.1 | 1,121 | 11/13/2019 |
1.0.2 | 1,765 | 7/21/2017 |
1.0.1 | 1,404 | 6/19/2017 |
1.0.0-alpha | 1,424 | 5/23/2017 |
Updated the interface to allow for using a single repository objects for multiple different types.
Updated to include a source generator that creates helper methods for filtering by object properties.