HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Specification pattern in EF part 1

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
specificationpartpattern

Problem

I've been experimenting with different ways of reusing (and naming) the expressions I use in Entity Framework. I've tried static fields with Expressions and extension methods but neither are perfect. In my latest attempt to solve this problem, I'm attempting to implement the specification pattern!

Firstly, I created a hierarchy for Specifications:

Specification.cs

public abstract class Specification
{
    public abstract Expression> ToExpression();

    public abstract bool IsSatisfiedBy(T target);

    public static implicit operator Expression>(Specification specification)
    {
        if (specification == null)
        {
            throw new ArgumentNullException(nameof(specification));
        }
        return specification.ToExpression();
    }

    public Specification And(Specification other)
    {
        ValidateForCompose(other);
        return new AndSpecification(this, other);
    }

    public Specification Or(Specification other)
    {
        ValidateForCompose(other);
        return new OrSpecification(this, other);
    }

    public Specification Negate()
    {
        return new NotSpecification(this);
    }

    public static Specification operator ! (Specification specification)
    {
        if (specification == null)
        {
            throw new ArgumentNullException(nameof(specification));
        }
        return specification.Negate();
    }

    private void ValidateForCompose(Specification other)
    {
        if (ToExpression() == null)
        {
            throw new InvalidOperationException(
                "Cannot compose an empty specification with another specification.");
        }
        if (other == null)
        {
            throw new ArgumentNullException(nameof(other));
        }
    }
}


NotSpecification

```
public class NotSpecification : Specification
{
private readonly Specification specification;

public NotSpecification(Specification specification)
{
if (specification == null)

Solution

I might be missing something, but from what I can see IsSatisfiedBy is only ever used by IsSatisfiedBy, and can be removed.

Consider making ToExpression protected, since with the implicit operator I don't see how exposing it is helpful.

Consider exposing operators & and | to make composition of more complicated specifications easier to read and write.

Here's an illustration of how I'm imaginging it:

public abstract class Specification
{
    protected abstract Expression> Expression { get; }

    public static implicit operator Expression>(Specification specification)
    {
        if (specification == null)
        {
            throw new ArgumentNullException(nameof(specification));
        }

        return specification.Expression;
    }

    public Specification And(Specification second)
    {
        if (second == null)
        {
            throw new ArgumentNullException(nameof(second));
        }

        return new AndSpecification(this, second);
    }

    public static Specification operator &(Specification first, Specification second)
    {
        if (first == null)
        {
            throw new ArgumentNullException(nameof(first));
        }

        if (second == null)
        {
            throw new ArgumentNullException(nameof(second));
        }

        return new AndSpecification(first, second);
    }

    private sealed class AndSpecification : Specification
    {
        public AndSpecification(Specification first, Specification second)
        {
            Expression = first.Expression.And(second);
        }

        protected override Expression> Expression { get; }
    }

    // Similarly for or, not.
}


And example client code

public class IsSomeExample : Specification
{
    protected override Expression> Expression =>
        new IsPrivate() | (new IsFromASubscription() & new LinkedPostingIsPublic());
}

Code Snippets

public abstract class Specification<T>
{
    protected abstract Expression<Func<T, bool>> Expression { get; }

    public static implicit operator Expression<Func<T, bool>>(Specification<T> specification)
    {
        if (specification == null)
        {
            throw new ArgumentNullException(nameof(specification));
        }

        return specification.Expression;
    }

    public Specification<T> And(Specification<T> second)
    {
        if (second == null)
        {
            throw new ArgumentNullException(nameof(second));
        }

        return new AndSpecification(this, second);
    }

    public static Specification<T> operator &(Specification<T> first, Specification<T> second)
    {
        if (first == null)
        {
            throw new ArgumentNullException(nameof(first));
        }

        if (second == null)
        {
            throw new ArgumentNullException(nameof(second));
        }

        return new AndSpecification(first, second);
    }

    private sealed class AndSpecification : Specification<T>
    {
        public AndSpecification(Specification<T> first, Specification<T> second)
        {
            Expression = first.Expression.And(second);
        }

        protected override Expression<Func<T, bool>> Expression { get; }
    }


    // Similarly for or, not.
}
public class IsSomeExample : Specification<Posting>
{
    protected override Expression<Func<Posting, bool>> Expression =>
        new IsPrivate() | (new IsFromASubscription() & new LinkedPostingIsPublic());
}

Context

StackExchange Code Review Q#138282, answer score: 3

Revisions (0)

No revisions yet.