Entity Framework Specification Pattern Implementation

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



Leave a reply



Submit