I am writing an asp.net web api and I am using a cqrs pattern with mediator, repository pattern + unit of work, and now I want to add a specification pattern, I already implemented the pattern like this (showing a part of each class):
public abstract class BaseSpecification<T> : ISpecification<T>
{
protected BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
protected BaseSpecification()
{
}
public Expression<Func<T, bool>> Criteria { get; }
public List<Expression<Func<T, object>>> Includes { get; } = new();
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
}
Specification to include related entities:
public MediaIncludePeopleAndGenres(int mediaId)
: base(x => x.MediaId == mediaId)
{
AddInclude(x => x.MediaGenres);
AddInclude(x => x.MediaPeople);
}
Specification extension class for entity framework context
public static IQueryable<T> Specify<T>(this IQueryable<T> query, ISpecification<T> spec) where T : class
{
var resultWithIncludes = spec
.Includes
.Aggregate(query, (current, include) => current.Include(include));
return resultWithIncludes.Where(spec.Criteria);
}
Method in repository class:
public Task<Media?> GetMediaByIdAsync(int id)
{
return _context.Media
.Specify(new MediaIncludePeopleAndGenres(id))
.FirstOrDefaultAsync();
}
It works just as I expected, but I didn't find a similar approach for commands (like post or put), what functionality should I put in the specification? Can I please get an example according to my approach? Now I am calling post media method like this: repository class
public void AddSingleMedia(Media media)
{
_context.Media.Add(media);
}
P.S: I didn't show commands or queries handlers, because I only do mapping there, and calling the repository methods. UPDATED: For some post and put methods I am using a https://entityframework-extensions.net/bulk-extensions set of methods for bulk operations and it looks like this
public Task BulkInsertMediaAsync(List<Media> mediaList)
{
return _context.Media.BulkInsertAsync(mediaList, options => options.IncludeGraph = true);
}
As per definition, for example from Wiki:
In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. The pattern is frequently used in the context of domain-driven design.
In context of data access the term business logic is not appropriate one, but we can substitute it with "filtering", so for POST and PUT endpoints filtering can be mainly applied for batch updates (and EF, which I assume you are using, is not very suitable for, at least before 7th version and/or without batch extensions), so unless you will create a command which performs some batch update (i.e. something like update ... set ... where some_filter
, where the some_filter
should be build "dynamically") the specification would not be that relevant.
P.S.
Check out Specification by ardalis. It can give you some inspiration for implementation (or maybe you will want to just go straightly use it).