简体   繁体   中英

Generic Query With PredicateBuilder in Linqkit

I've been using LinqKit to create generic queries for quite some time.

One thing that has always bothered me is the fact that you always have to test whether 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 look something like this:

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

    // Plus a giant amount of if's with multiple filters

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

To end this bunch of If's, I decided to create a generic method to apply the filter to the 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 of the type:

public List<MyModel> Get(MyModelFilter filter)
{
    predicate = predicate.And(_myModel => myModel.Prop, filter.PropFilterDefinition);

    // Goodnye If's, Only others filter impl

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

For this, I've created some extension methods to handle this

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;
    }

    // This is LinqKit's And Extension Method
    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 can not translate the above expression to SQL. The EF 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 construction work? Also, any suggestions on how best this?

You seem to forgot that besides the PredicateBuilder , the really useful feature provided by LINQKit AsExpandable , Expand and Invoke custom extension methods is to be able to correctly embed expressions inside the expression tree.

In order to utilize that feature, you should use Expression<Func<...>> instead of Func<...> . In the posted code, replace all occurrences of Func<TPredicate, string> with Expression<Func<TPredicate, string>> and the issue should be solved.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM