Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic predicates for Linq-to-Entity queries

The following Linq-to-Entities query works fine:

var query = repository.Where(r => r.YearProp1.HasValue &&
                                  r.YearProp1 >= minYear &&
                                  r.YearProp1 <= maxYear);

My database has a dozen or so columns that all report year-related information (short? data type). I want to reuse the same Linq-to-Entities logic for all these columns. Something like:

Func<RepoEntity, short?> fx = GetYearPropertyFunction();
var query = repository.Where(r => fx(r).HasValue &&
                                  fx(r) >= minYear &&
                                  fx(r) <= maxYear);

This results in the error:

LINQ to Entities does not recognize the method 'System.Nullable`1[System.Int16] fx(RepoEntity)' method, and this method cannot be translated into a store expression.

I understand why I am getting the error, but am wondering if there is a workaround that doesn't involve duplicating code a dozen times just to change the property on which the SQL query is operating.

I would be reusing the function in more than one query, so I guess the general version of my question is: Is there a way to convert a simple property-getter lambda function to an Expression that can be consumed by Linq-to-Entities?

like image 302
Dave Mateer Avatar asked Jun 18 '12 17:06

Dave Mateer


1 Answers

Building off of Raphaël Althaus' answer, but adding the generic selector you were originally looking for:

public static class Examples
{
    public static Expression<Func<MyEntity, short?>> SelectPropertyOne()
    {
        return x => x.PropertyOne;
    }

    public static Expression<Func<MyEntity, short?>> SelectPropertyTwo()
    {
        return x => x.PropertyTwo;
    }

    public static Expression<Func<TEntity, bool>> BetweenNullable<TEntity, TNull>(Expression<Func<TEntity, Nullable<TNull>>> selector, Nullable<TNull> minRange, Nullable<TNull> maxRange) where TNull : struct
    {
        var param = Expression.Parameter(typeof(TEntity), "entity");
        var member = Expression.Invoke(selector, param);

        Expression hasValue = Expression.Property(member, "HasValue");
        Expression greaterThanMinRange = Expression.GreaterThanOrEqual(member,
                                             Expression.Convert(Expression.Constant(minRange), typeof(Nullable<TNull>)));
        Expression lessThanMaxRange = Expression.LessThanOrEqual(member,
                                          Expression.Convert(Expression.Constant(maxRange), typeof(Nullable<TNull>)));

        Expression body = Expression.AndAlso(hasValue,
                      Expression.AndAlso(greaterThanMinRange, lessThanMaxRange));

        return Expression.Lambda<Func<TEntity, bool>>(body, param);
    }
}

Could be used somewhat like the original query you were looking for:

Expression<Func<MyEntity, short?>> whatToSelect = Examples.SelectPropertyOne;

var query = Context
            .MyEntities
            .Where(Examples.BetweenNullable<MyEntity, short>(whatToSelect, 0, 30));
like image 117
Ocelot20 Avatar answered Oct 22 '22 02:10

Ocelot20