Specification pattern with Entity Framework generic repository pattern
The specification pattern covers more than just a filter expression, it also covers eager loading and pagination. In your example, if you were dealing with an entity that referenced 3 other entities and had a child collection, how would you tell your repository method which of those should be eager loaded? If your List method could return hundreds of thousands of rows, how would you tell it to load just a page of 50 at a time?
In my personal opinion, using the Specification is a SRP band-aid on a generic repository. Generic repositories are an anti-pattern in EF systems where you are positioning your repository code to have a great number of "forces" lobbying for change using the Specification to mitigate that. I would never advocate for trying to build an MVC web application with a single generic Controller<T>
implementation, and for the same reason I don't advocate using Repository<T>
.
Specifications do not protect callers from being polluted with domain / EF knowledge because every expression passed in must conform to the rules set out by the domain implementation. While a specification might handle filtering, eager loading, and pagination... What about sorting, projection, or other aggregate functions like exist checks and counts? At the end of the day it's a complexity rabbit hole trying to satisfy S.O.L.I.D. principles making your code considerably more complex and harder to follow, and slamming head-first into barriers repeatedly as new requirements don't conform to past assumptions.
How to implement ThenInclude into EF Core custom specification?
Includes
member of the aforementioned ISpecification<T>
is declared as
List<Expression<Func<T, object>>> Includes { get; }
The problem is that EF Core Include
/ ThenInclude
chain cannot be represented with Expression<Func<T, object>>
. This pattern was used in EF6 which supported a special syntax (Select
) inside the include expression to resolve collection element. But EF Core does not support that out of the box.
The easiest and most natural way to plug EF Core pattern is to change the definition as follows:
List<Func<IQueryable<T>, IIncludableQueryable<T, object>>> Includes { get; }
Adding the sample for entity having User
property having UserRoles
collection having Role
property would be like this:
Includes.Add(q => q.Include(e => e.User).ThenInclude(e => e.UserRoles).ThenInclude(e => e.Role));
And the corresponding part of the Specify
method implementation would be:
var queryableResultWithIncludes = spec.Includes
.Aggregate(query,
(current, include) => include(current));
Specification pattern with entity framework and using orderby and skip/take
How about
public class Specification<T> : ISpecification<T>
{
public Expression<Func<T, bool>> Predicate { get; protected set; }
public Func<IQueryable<T>, IOrderedQueryable<T>> Sort {get; protected set; }
public Func<IQueryable<T>, IQueryable<T>> PostProcess {get; protected set;
public Specification<T> OrderBy<TProperty>(Expression<Func<T, TProperty>> property)
{
var newSpecification = new Specification<T>(Predicate) { PostProcess = PostProcess } ;
if(Sort != null) {
newSpecification.Sort = items => Sort(items).ThenBy(property);
} else {
newSpecification.Sort = items => items.OrderBy(property);
}
return newSpecification;
}
public Specification<T> Take(int amount)
{
var newSpecification = new Specification<T>(Predicate) { Sort = Sort } ;
if(PostProcess!= null) {
newSpecification.PostProcess= items => PostProcess(items).Take(amount);
} else {
newSpecification.PostProcess= items => items.Take(amount);
}
return newSpecification;
}
public Specification<T> Skip(int amount)
{
var newSpecification = new Specification<T>(Predicate) { Sort = Sort } ;
if(PostProcess!= null) {
newSpecification.PostProcess= items => PostProcess(items).Skip(amount);
} else {
newSpecification.PostProcess= items => items.Skip(amount);
}
return newSpecification;
}
}
TODO:
- similar construction for OrderByDescending
- Update your other methods so the "Sort" value and "PostProcess" value is not lost when you call "And", for example
then your Satisfying methods become:
private IQueryable<T> Prepare(IQueryable<T> query)
{
var filtered = query.Where(Predicate);
var sorted = Sort(filtered);
var postProcessed = PostProcess(sorted);
return postProcessed;
}
public T SatisfyingItemFrom(IQueryable<T> query)
{
return Prepare(query).SingleOrDefault();
}
public IQueryable<T> SatisfyingItemsFrom(IQueryable<T> query)
{
return Prepare(query);
}
TODO: check if Sort & PostProcess are not null in the "Prepare" method
Usage:
var spec = new Specification<Wave>(w => w.Id == "1")
.And(w => w.WaveStartSentOn > DateTime.Now)
.OrderBy(w => w.WaveStartSentOn)
.Skip(20)
.Take(5);
.ThenInclude in Specification Pattern , EF Core
Found a solution to above problem. Below is the solution. Add in BaseSpecification class
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
After that in Specification filter use like below for then include.
AddInclude("Contacts.PrefixTitle");
This will include Contacts in Primary Class i.e. Account in my Case, then it includes Contacts then PrefixTitle
Related Topics
C#/.Net Analysis Tool to Find Race Conditions/Deadlocks
Check a String to See If All Characters Are Hexadecimal Values
Using a Wwwroot Folder (ASP.NET Core Style) in ASP.NET 4.5 Project
Getting Networkcredential for Current User (C#)
How to Delete Cookies on an ASP.NET Website
Escaping the Escape Character Does Not Work - SQL Like Operator
How to Conditionally Control The Visibility of a Control in Asp.Net
Using C# 6 Features with Codedomprovider (Roslyn)
Executing a Cygwin Process from .Net
Getters, Setters, and Properties Best Practices. Java VS. C#
Hmc Sha1 Hash - C# Producing Different Hash Output Than Ruby
How to #Define Constant on a Solution Basis
Sort Datagridview Columns in C#? (Windows Form)
How to Check That a Uri String Is Valid
Getting @@Identity from Tableadapter
How Set Value a Property Selector Expression<Func<T,Tresult>>