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

Docs: How to extend or define evaluators #231

Open
fretje opened this issue Jan 21, 2022 · 8 comments
Open

Docs: How to extend or define evaluators #231

fretje opened this issue Jan 21, 2022 · 8 comments
Assignees
Labels
docs Help updating docs good first issue Good for newcomers hacktoberfest help wanted Extra attention is needed

Comments

@fretje
Copy link
Contributor

fretje commented Jan 21, 2022

I see an important page in the documentation is still missing:

https://ardalis.github.io/Specification/extensions/extend-define-evaluators.html

And I didn't find any issue about it?

Maybe also an example in the sample app of how to override a single evaluator would be handy.

A good example might be a custom SearchEvaluator which uses the progresql specific EF.Functions.ILike in stead of the default EF.Functions.Like? (Related to #230 ==> I was actually looking up the documentation to try and create a specific evaluator for this, but kind of hit this wall...)

@fiseni
Copy link
Collaborator

fiseni commented Jan 21, 2022

Hey @fretje

Here is what you do.
Create your own search evaluator and implement it as you wish. Create your specification evaluator by inheriting from the provided one in the package. We have a constructor overload that accepts a list of evaluators. Refer to this comment here on why there is no constructor accepting a single evaluator.

public class MySearchEvaluator : IEvaluator
{
    private MySearchEvaluator() { }
    public static MySearchEvaluator Instance { get; } = new MySearchEvaluator();

    public bool IsCriteriaEvaluator { get; } = true;

    public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
    {
        // Write your desired implementation

        return query;
    }
}

public class MySpecificationEvaluator : SpecificationEvaluator
{
    public static MySpecificationEvaluator Instance { get; } = new MySpecificationEvaluator();

    private static IEvaluator[] evaluators = new IEvaluator[] {
                WhereEvaluator.Instance,
                MySearchEvaluator.Instance,
                IncludeEvaluator.Default,
                OrderEvaluator.Instance,
                PaginationEvaluator.Instance,
                AsNoTrackingEvaluator.Instance,
                IgnoreQueryFiltersEvaluator.Instance,
                AsSplitQueryEvaluator.Instance,
                AsNoTrackingWithIdentityResolutionEvaluator.Instance
            };

    private MySpecificationEvaluator()
        : base(evaluators)
    { 
    }
}

In your application, usually, you have to create your own repository class so you can pass your DbContext. We have a constructor overload that accepts ISpecificationEvaluator, and you can pass your newly created MySpecificationEvaluator.

public interface IRepository<T> : IRepositoryBase<T> where T : class
{
}

public class Repository<T> : RepositoryBase<T>, IRepository<T> where T : class
{
    public Repository(AppDbContext dbContext)
        : base(dbContext, MySpecificationEvaluator.Instance)
    {
    }
}

public class AppDbContext : DbContext
{
}

@fretje
Copy link
Contributor Author

fretje commented Jan 21, 2022

Thanks again for the clear explanation!

I guess this issue can stay open until this is up on the docs site? (I might have a stab at making a PR for it once I find the time ;-)).

@fiseni
Copy link
Collaborator

fiseni commented Jan 22, 2022

Sure, any help is welcome.

@snax4a
Copy link

snax4a commented May 1, 2022

Hey @fretje

Here is what you do. Create your own search evaluator and implement it as you wish. Create your specification evaluator by inheriting from the provided one in the package. We have a constructor overload that accepts a list of evaluators. Refer to this comment here on why there is no constructor accepting a single evaluator.

public class MySearchEvaluator : IEvaluator
{
    private MySearchEvaluator() { }
    public static MySearchEvaluator Instance { get; } = new MySearchEvaluator();

    public bool IsCriteriaEvaluator { get; } = true;

    public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
    {
        // Write your desired implementation

        return query;
    }
}

public class MySpecificationEvaluator : SpecificationEvaluator
{
    public static MySpecificationEvaluator Instance { get; } = new MySpecificationEvaluator();

    private static IEvaluator[] evaluators = new IEvaluator[] {
                WhereEvaluator.Instance,
                MySearchEvaluator.Instance,
                IncludeEvaluator.Default,
                OrderEvaluator.Instance,
                PaginationEvaluator.Instance,
                AsNoTrackingEvaluator.Instance,
                IgnoreQueryFiltersEvaluator.Instance,
                AsSplitQueryEvaluator.Instance,
                AsNoTrackingWithIdentityResolutionEvaluator.Instance
            };

    private MySpecificationEvaluator()
        : base(evaluators)
    { 
    }
}

In your application, usually, you have to create your own repository class so you can pass your DbContext. We have a constructor overload that accepts ISpecificationEvaluator, and you can pass your newly created MySpecificationEvaluator.

public interface IRepository<T> : IRepositoryBase<T> where T : class
{
}

public class Repository<T> : RepositoryBase<T>, IRepository<T> where T : class
{
    public Repository(AppDbContext dbContext)
        : base(dbContext, MySpecificationEvaluator.Instance)
    {
    }
}

public class AppDbContext : DbContext
{
}

Hi @fiseni,
I am trying to get my custom evaluator working, I'm doing exactly what you showed above but there is something wrong with the MySpecificationEvaluator Instance
I am getting runtime exception in MySpecificationEvaluator.cs at this line:

 public static MySpecificationEvaluator Instance { get; } = new MySpecificationEvaluator();
Exception has occurred: CLR/System.TypeInitializationException
A type exception has occurred „System.TypeInitializationException” w FSH.WebApi.Infrastructure.dll, but was not handled in user code: 'The type initializer for 'FSH.WebApi.Infrastructure.Persistence.Specification.Evaluators.MySpecificationEvaluator' threw an exception.'
 Internal exceptions found. See $exception in the variable window for more details.
 	The innermost exception System.ArgumentNullException : Value cannot be null.
   w System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   w System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
   w Ardalis.Specification.EntityFrameworkCore.SpecificationEvaluator..ctor(IEnumerable`1 evaluators)
   w FSH.WebApi.Infrastructure.Persistence.Specification.Evaluators.MySpecificationEvaluator..ctor() w /Users/szymon/dotnet-projects/my-api/src/Infrastructure/Persistence/Specification/Evaluators/MySpecificationEvaluator.cs:row 23
   w FSH.WebApi.Infrastructure.Persistence.Specification.Evaluators.MySpecificationEvaluator..cctor() w /Users/szymon/dotnet-projects/my-api/src/Infrastructure/Persistence/Specification/Evaluators/MySpecificationEvaluator.cs:row 8

InnerException:
Message: Value cannot be null. (Parameter 'collection')
Source: System.Private.CoreLib
StackTrace:

at System.Collections.Generic.List 1.InsertRange(Int32 index, IEnumerable1 collection)
at Ardalis.Specification.EntityFrameworkCore.SpecificationEvaluator..ctor(IEnumerable 1 evaluators)
at FSH.WebApi.Infrastructure.Persistence.Specification.Evaluators.MySpecificationEvaluator..ctor() in /Users/szymon/dotnet-projects/my-api/src/Infrastructure/Persistence/Specification/Evaluators/MySpecificationEvaluator.cs:line 23
at FSH.WebApi.Infrastructure.Persistence.Specification.Evaluators.MySpecificationEvaluator..cctor() in /Users/szymon/dotnet-projects/my-api/src/Infrastructure/Persistence/Specification/Evaluators/MySpecificationEvaluator.cs:line 8

Unfortunatelly I have no idea what is the problem here.

@snax4a
Copy link

snax4a commented May 1, 2022

@fiseni
It works when I pass evaluators array directly to the constructor.
For some reason I can't get this working with the static field.

This is a working specification evaluator:

public sealed class MySpecificationEvaluator : SpecificationEvaluator
{
    public static MySpecificationEvaluator Instance { get; } = new MySpecificationEvaluator();

    private MySpecificationEvaluator()
        : base(new IEvaluator[]
    {
        WhereEvaluator.Instance,
        MySearchEvaluator.Instance,
        IncludeEvaluator.Default,
        OrderEvaluator.Instance,
        PaginationEvaluator.Instance,
        AsNoTrackingEvaluator.Instance,
        IgnoreQueryFiltersEvaluator.Instance,
        AsSplitQueryEvaluator.Instance,
        AsNoTrackingWithIdentityResolutionEvaluator.Instance
    })
    {
    }
}

@ardalis ardalis self-assigned this Mar 9, 2023
@ardalis ardalis added the docs Help updating docs label Mar 9, 2023
@jeffreymonroe
Copy link

Hi @snax4a,

Were you able to get this working with the example @fiseni gave at the beginning of this issue? I am interested in an complete example.

@fiseni
Copy link
Collaborator

fiseni commented Jul 22, 2023

Hi @fretje,

If you're on version 7, the requested feature was merged. So, it's easier to pass your evaluators.
Just today I provided an example in this issue on how to write extensions and defined your evaluators.

Also added sample projects, so you can analyze the full implementation.

  • Here is a sample of specification extension.
  • Here is a complete app that among other things also passes a custom evaluator.

@timdeschryver
Copy link

Hello, I stumbled across issue #291 and this issue because I was looking for a way to ignore auto includes within our application.

The evaluators are a great way to do this, thanks for this feature!

From only looking at the docs I didn't make the connection that an evaluator can be used to ignore auto includes. That's why I think it would be better to update the current docs (because these don't show us a "real" scenario), with with the example shared in #291. If you agree, I'm happy to create a PR for this. It would be similar to "Example: Configure caching behavior through specification builder extension method" in http://specification.ardalis.com/extensions/extend-specification-builder.html

@ardalis ardalis added help wanted Extra attention is needed good first issue Good for newcomers hacktoberfest labels Oct 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Help updating docs good first issue Good for newcomers hacktoberfest help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

6 participants