Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FAQ entry on combining specs #380

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ardalis
Copy link
Owner

@ardalis ardalis commented Dec 11, 2023

No description provided.

@ardalis ardalis requested a review from fiseni December 11, 2023 19:24
@fiseni
Copy link
Collaborator

fiseni commented Dec 11, 2023

The approach with predicates won't work. EF will throw an exception during the evaluation. Instead, we can write extensions to the builder.

public static class CustomerSpecExtensions
{
    public static ISpecificationBuilder<Customer> IsAdult(this ISpecificationBuilder<Customer> builder)
        => builder.Where(x => x.Age >= 18);

    public static ISpecificationBuilder<Customer> IsAtLeastYearsOld(this ISpecificationBuilder<Customer> builder, int years)
        => builder.Where(x => x.Age >= years);
}

public class AdultCustomersByNameSpec : Specification<Customer>
{
    public AdultCustomersByNameSpec(string nameSubstring)
    {
        Query.IsAdult()
            .Where(x => x.Name.Contains(nameSubstring));
    }
}

We have more examples in the sample apps. https://github.com/ardalis/Specification/blob/main/sample/Ardalis.Sample.Domain/Specs/CustomerSpecExtensions.cs


We don't recommend or support combining *specifications*, because doing so moves query logic out of the domain model and into the consumer (typically UI) layer. Choosing which predicates to AND or OR or NOT together is basically the logic that specifications are meant to encapsulate. Instead, if you have predicates you want to share, these can be shared between Specifications by simply creating (static) properties of type `Func<T,bool>` and then using these within your well-named specifications. It's worth remembering that our implementation of Specification has more scope than merely an `IsSatisfiedby()` method, and that operations like `Skip`, `Take`, `OrderBy`, etc. are within that scope but are not combinable using generic logical operations like predicates are.

However, that's not to say there's no way to extract common predicate logic and reuse it. The recommended approach is to extract the predicate logic (if needed) and reuse that between specifications but still within your domain model. If you find value in using something like a [PredicateBuilder](https://www.albahari.com/nutshell/predicatebuilder.aspx) to support easy AND and OR combinations of your predicates from within your individual specifications, by all means do so.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove this paragraph. Or, update it and suggest using specification builder extensions.

// CustomerPredicates.cs
// Predicates should take in an entity (and optionally, other arguments) and return a boolean
// Optionally these can be extension methods, but that may reduce the ability to combine them consistently
internal static class CustomerPredicates
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update this example with the one provided in the PR comments. I updated our sample too, so perhaps you can pick up the code from this commit 924a68c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants