📜 ⬆️ ⬇️

Specification Design Pattern in C #

A “specification” in programming is a design pattern by which the representation of business logic rules can be transformed into a chain of objects connected by boolean logic operations.

I became acquainted with this term in the process of reading DDD Evans . On Habré there are articles describing the practical application of the pattern and problems encountered in the implementation process .

In short, the main advantage of using the “specifications” is to have one clear place, in which all the filtering rules of the objects of the subject model are concentrated, instead of thousands of spreads on an even layer by the application of lambda expressions.

The classic implementation of the design pattern looks like this:
')
public interface ISpecification { bool IsSatisfiedBy(object candidate); } 

What is wrong with it for C #?


  1. There are Expression<Func<T, bool>> and Func<T, bool>> , the signature of which coincides with IsSatisfiedBy
  2. There are extension methods. alexanderzaytsev using them does this:

     public class UserQueryExtensions { public static IQueryable<User> WhereGroupNameIs(this IQueryable<User> users, string name) { return users.Where(u => u.GroupName == name); } } 

  3. And you can also implement this addin over LINQ :

     public abstract class Specification<T> { public bool IsSatisfiedBy(T item) { return SatisfyingElementsFrom(new[] { item }.AsQueryable()).Any(); } public abstract IQueryable<T> SatisfyingElementsFrom(IQueryable<T> candidates); } 

In the end, the question arises: is it worth using a C # template from the world of Java a decade ago and how to implement it?


We decided that it was worth it this way:

 public interface IQueryableSpecification<T> where T: class { IQueryable<T> Apply(IQueryable<T> query); } public interface IQueryableOrderBy<T> { IOrderedQueryable<T> Apply(IQueryable<T> queryable); } public static bool Satisfy<T>(this T obj, Func<T, bool> spec) => spec(obj); public static bool SatisfyExpresion<T>(this T obj, Expression<Func<T, bool>> spec) => spec.AsFunc()(obj); public static bool IsSatisfiedBy<T>(this Func<T, bool> spec, T obj) => spec(obj); public static bool IsSatisfiedBy<T>(this Expression<Func<T, bool>> spec, T obj) => spec.AsFunc()(obj); public static IQueryable<T> Where<T>(this IQueryable<T> source, IQueryableSpecification<T> spec) where T : class => spec.Apply(source); 

Why not Func<T, bool> ?


From Func very difficult to go to Expression . Often you need to transfer the filtering to the level of building a query to the database, otherwise you will have to pull out millions of records and filter them in memory, which is not optimal.

Why not Expression<Func<T, bool>> ?


The transition from Expression to Func , on the contrary, is trivial: var func = expression.Compile() . However, the layout of the Expression is not a trivial task . It is no longer pleasant if a conditional assembly of the expression is required (for example, if the specification contains three parameters, two of which are optional). And really bad Expression<Func<T, bool>> does it in cases requiring subqueries like query.Where(x => someOtherQuery.Contains(x.Id)) .

Ultimately, this reasoning suggested that the easiest way is to modify the target IQueryable and transmit further through the fluent interface. Additional Where methods allow code to appear as if it were a normal LINQ transformation chain.

Guided by this logic, you can select an abstraction to sort


 public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, IQueryableOrderBy<T> spec) where T : class => spec.Apply(source); public interface IQueryableOrderBy<T> { IOrderedQueryable<T> Apply(IQueryable<T> queryable); } 

Then, by adding Dynamic Linq and a bit of special street magic Reflection , you can write a basic object to filter anything in a declarative style. The code below analyzes the public properties of an AutoSpec and the type to which you want to apply filtering. If a match is found and the AutoSpec inheritor AutoSpec filled in, a filtering rule by this field will be automatically added to the Queryable .

 public class AutoSpec<TProjection> : IPaging, ILinqSpecification<TProjection>, ILinqOrderBy<TProjection> where TProjection : class, IHasId { public virtual IQueryable<TProjection> Apply(IQueryable<TProjection> query) => GetType() .GetPublicProperties() .Where(x => typeof(TProjection).GetPublicProperties().Any(y => x.Name == y.Name)) .Aggregate(query, (current, next) => { var val = next.GetValue(this); if (val == null) return current; return current.Where(next.PropertyType == typeof(string) ? $"{next.Name}.StartsWith(@0)" : $"{next.Name}=@0", val); }); IOrderedQueryable<TProjection> ILinqOrderBy<TProjection>.Apply(IQueryable<TProjection> queryable) => !string.IsNullOrEmpty(OrderBy) ? queryable.OrderBy(OrderBy) : queryable.OrderBy(x => x.Id); } 

AutoSpec can be implemented without Dynamic Linq , using only Expression , but the implementation does not fit into ten lines and the code will be much more difficult to understand.

UPD


om2804 and xyzuvw rightly pointed out that IQueryableSpec does not meet the layout requirements. The fact is that I rarely have to deal with the need to do ||, and && is achieved by a simple query.Where(spec1).Where(spec2) . I decided to do a little refactoring to make the code cleaner:

  //  IQueryableSpecification  IQueryableFilter public interface IQueryableFilter<T> where T: class { IQueryable<T> Apply(IQueryable<T> query); } 

There is such a library : LinqSpecs . I do not like the fact that you need to create separate types of specifications for each of them. Expression<Func<T, bool>> enough for me

We use the Predicate Builder from Pete Montgomery .

  /// <summary> /// Creates a predicate that evaluates to true. /// </summary> public static Expression<Func<T, bool>> True<T>() { return param => true; } /// <summary> /// Creates a predicate that evaluates to false. /// </summary> public static Expression<Func<T, bool>> False<T>() { return param => false; } /// <summary> /// Creates a predicate expression from the specified lambda expression. /// </summary> public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; } /// <summary> /// Combines the first predicate with the second using the logical "and". /// </summary> public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.AndAlso); } /// <summary> /// Combines the first predicate with the second using the logical "or". /// </summary> public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.OrElse); } 

Implementation details of the Compose method are explained in the link above. Now add syntactic sugar so that you can use && and || and IHasId restriction on generic, because I am not interested in creating specifications for the Value Object . This restriction is not necessary, it just seems better to me.

  public static class SpecificationExtenions { public static Specification<T> AsSpec<T>(this Expression<Func<T, bool>> expr) where T : class, IHasId => new Specification<T>(expr); } public sealed class Specification<T> : IQueryableFilter<T> where T: class, IHasId { public Expression<Func<T, bool>> Expression { get; } public Specification(Expression<Func<T, bool>> expression) { Expression = expression; if (expression == null) throw new ArgumentNullException(nameof(expression)); } public static implicit operator Expression<Func<T, bool>>(Specification<T> spec) => spec.Expression; public static bool operator false(Specification<T> spec) { return false; } public static bool operator true(Specification<T> spec) { return false; } public static Specification<T> operator &(Specification<T> spec1, Specification<T> spec2) => new Specification<T>(spec1.Expression.And(spec2.Expression)); public static Specification<T> operator |(Specification<T> spec1, Specification<T> spec2) => new Specification<T>(spec1.Expression.Or(spec2.Expression)); public static Specification<T> operator !(Specification<T> spec) => new Specification<T>(spec.Expression.Not()); public IQueryable<T> Apply(IQueryable<T> query) => query.Where(Expression); public bool IsSatisfiedBy(T obj) => Expression.AsFunc()(obj); } 

I am used to writing down "specification-expressions" with static fields in the entity class to which they belong:

  public class Category : HasIdBase<int> { public static readonly Expression<Func<Category, bool>> NiceRating = x => x.Rating > 50; public static readonly Expression<Func<Category, bool>> BadRating = x => x.Rating < 10; public static readonly Expression<Func<Category, bool>> Active= x => x.IsDeleted == false; //... } var niceCategories = db.Query<Category>.Where(Category.NiceRating); 

Given the code above, you can rewrite it like this:

  public class Category : HasIdBase<int> { public static readonly Specification<Category> NiceRating = new Specification(x => x.Rating > 50); //... } var niceCategories = db.Query<Category> .Where((Category.NiceRating || Category.BadRating) && Category.IsActive); 

Now get rid of DynamicLinq . We'll have to work a bit with expression trees.
 public enum Compose { And, Or } public static Spec<T> AsSpec<T>(this object obj, Compose compose = Compose.And) where T : class, IHasId { var filterProps = obj.GetType() .GetPublicProperties() .ToArray(); var filterPropNames = filterProps .Select(x => x.Name) .ToArray(); var props = typeof(T) .GetPublicProperties() .Where(x => filterPropNames.Contains(x.Name)) .Select(x => new { Property = x, Value = filterProps.Single(y => y.Name == x.Name).GetValue(obj) }) .Where(x => x.Value != null) .Select(x => { //     e => e.Prop == Val var parameter = Expression.Parameter(typeof (T)); var property = Expression.Property(parameter, x.Property); var body = Expression.Equal(property, Expression.Constant(x.Value)); var delegateType = typeof(Func<T, bool>); return (Expression<Func<T, bool>>) Expression.Lambda(delegateType, body, parameter); }) .ToArray(); if (!props.Any()) return new Spec<T>(x => true); //    ||  && var expr = compose == Compose.And ? props.Aggregate((c, n) => c.And(n)) : props.Aggregate((c, n) => c.Or(n)); return expr.AsSpec(); } 

Source: https://habr.com/ru/post/325280/


All Articles