Generic Query with Predicatebuilder and Linqkit

Asked

Viewed 675 times

3

I’ve been using Linqkit to create generic queries for a long time.

One thing that always bothered me is that I always have to test if the value sent in the filter is valid.

For example: Suppose I have a string filter. Conditions can be Equal, Startswith, Endswith and Contains.

My method would be more or less the following:

public List<MyModel> Get(MyModelFilter filter)
{
    if (string.IsNullOrEmpty(filter.prop))
    {
        predicate = predicate.And(_myModel => myModel.Prop.Contains(filter.prop));
    }

    // mais uma gigante quantidade de if's com vários filtros

    return DbSet.AsExpandable()
            .Where(predicate)
            .ToList();
}

To end this lot of If’s, I decided to create a generic method to apply the filter in properties. My idea is to pass the property where the filter will be applied, and the filter definition, and encapsulate the Expression creation logic

It would be something like

public List<MyModel> Get(MyModelFilter filter)
{
    predicate = predicate.And(_myModel => myModel.Prop, filter.PropFilterDefinition);
    // adeus If's, apenas as outras implementações de filtros

    return DbSet.AsExpandable()
            .Where(predicate)
            .ToList();
}

For this, I have created some extension methods to take care of it

public static Expression<Func<TPredicate, bool>> And<TPredicate>(
    this ExpressionStarter<TPredicate> predicate,
    Func<TPredicate, string> property, StringFilterDefinition filter,
    bool ignoreNull = true)
{
    if (InvalidStringFilter(filter, ignoreNull))
    {
        return predicate;
    }

    // este é o And do LinqKit
    return predicate.And(BuildPredicate(property, filter));
}

private static Expression<Func<TPredicate, bool>> BuildPredicate<TPredicate>(
    Func<TPredicate, string> property,
    StringFilterDefinition filter)
{
    if (filter.Filter == StringFilterComparators.Equal)
    {
        return x => property.Invoke(x) == filter.Value;
    }

    if (filter.Filter == StringFilterComparators.BeginsWith)
    {
        return x => property.Invoke(x).StartsWith(filter.Value);
    }

    if (filter.Filter == StringFilterComparators.EndsWith)
    {
        return x => property.Invoke(x).EndsWith(filter.Value);
    }

    return x => property.Invoke(x).Contains(filter.Value);
}

private static bool InvalidStringFilter(
    StringFilterDefinition filter, 
    bool ignoreNullValue = true)
{
    if (filter?.Filter == null)
    {
        return true;
    }

    return ignoreNullValue && string.IsNullOrEmpty(filter.Value);
}

The problem is that the filter is not applied, and the answer is in Invoke right up there. EF cannot translate the above expression to SQL. The RU error is

Microsoft.EntityFrameworkCore.Query.Internal.Sqlserverquerycompilationcontextfactory[8] The LINQ Expression '(__property_0.Invoke([x]) == __filter_Value_1)' could not be Translated and will be evaluated locally. To configure this Warning use the Dbcontextoptionsbuilder.Configurewarnings API (Event id 'Relationaleventid.Queryclientevaluationwarning'). Configurewarnings can be used when Overriding the Dbcontext.Onconfiguring method or using Adddbcontext on the application service Provider.

The question is:

How can I make this building work? Also, any suggestions on how to best this?

1 answer

1

Solved.

The big secret is that Linqkit has other really useful tools like Asexpandable, Expand and Invoke.

Because of these methods, it is possible to combine expressions without having problems.

However, these items only work with Expression<T, R>

So just replace Func<T, R> for

private static Expression<Func<TPredicate, bool>> BuildPredicate<TPredicate>(
    Expression<Func<TPredicate, string>> property,
    StringFilterDefinition filter)
{
    if (filter.Filter == StringFilterComparators.Equal)
    {
        return x => property.Invoke(x) == filter.Value;
    }

    if (filter.Filter == StringFilterComparators.BeginsWith)
    {
        return x => property.Invoke(x).StartsWith(filter.Value);
    }

    if (filter.Filter == StringFilterComparators.EndsWith)
    {
        return x => property.Invoke(x).EndsWith(filter.Value);
    }

    return x => property.Invoke(x).Contains(filter.Value);
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.