Question

I have written this filter to get only documents that match certain periods of time from the database :

The Period entity is straightforward and contains two properties : DateFrom and DateTo.

I need to build a filter from lambdas, each one for each Period that is submitted to build the filter.

The filter, when is completely built, has to look like this :

ObjectSet.Where(d => 
    (d.Date >= Period1.DateFrom && d.Date <= Period1.DateTo)
    || (d.Date >= Period2.DateFrom && d.Date <= Period2.DateTo)
    || (d.Date >= Period3.DateFrom && d.Date <= Period3.DateTo));

As you can guess, I have to dynamically build this filter because the number of submitted periods to build the filter can vary.

(The following is the Expression I use to combine the lambdas (each one for each period of time that have been submitted to build the filter)

private Expression<Func<T, bool>> CombineWithOr<T>(
    Expression<Func<T, bool>> firstExpression, 
    Expression<Func<T, bool>> secondExpression)
{
    var parameter = Expression.Parameter(typeof(T), "x");

    var resultBody = Expression.Or(
        Expression.Invoke(firstExpression, parameter), 
        Expression.Invoke(secondExpression, parameter));

    return Expression.Lambda<Func<T, bool>>(resultBody, parameter);
}

Here is where I combine each lambda for each period there is to add to the document filter :

public IList<Document> GetDocuments(IList<Periods> periods)
{
    Expression<Func<Document, bool>> resultExpression = n => false;

    foreach (var submittedPeriod in periods)
    {
        var period = submittedPeriod;
        Expression<Func<Document, bool>> expression =
            d => (d.Date >= period.DateFrom && d.Date <= period.DateTo);
        resultExpression = this.CombineWithOr(resultExpression, expression);
    }

    var query = this.ObjectSet.Where(resultExpression.Compile());
}

The problem is, when I launch deferred execution of the query ...

var documents = query.ToList();

... and I look at the resulting SQL, nothing is added to the SELECT statement.


If I execute the query without compiling the resulting Expression like this :

var query = this.ObjectSet.Where(resultExpression);

I get this exception :

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

That means that the Linq-To-Entities query provider doesn't know how to translate my filter into SQL code.

What is bugging me right now is how such a simple DateTime comparison from entities (Document and Period) that are both part of my Entity schema can mess up the provider ?

Any ideas how I can achieve such a filtering ?

Was it helpful?

Solution

try .AsExpandable() before the query. More information is here

OTHER TIPS

The problem is with your combining function. SQL to Entities doesn't really like Invoke. Here's a combining function that worked for me:

public static class ExpressionCombiner
{
    public static Expression<Func<T, bool>> Or<T>(Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
    {
        var parameter = Expression.Parameter(typeof(T), "x");
        var substituter = new SubstituteParameter(parameter, p => true);

        var resultBody = Expression.Or(
            substituter.Visit(a.Body),
            substituter.Visit(b.Body));

        Expression<Func<T, bool>> combined = Expression.Lambda<Func<T, bool>>(resultBody, parameter);
        return combined;
    }
}

public class SubstituteParameter : ExpressionVisitor
{
    private ParameterExpression toReplace;
    private Func<ParameterExpression, bool> isReplacementRequiredFunc;
    public SubstituteParameter(ParameterExpression toReplace, Func<ParameterExpression, bool> isReplacementRequiredFunc)
    {
        this.toReplace = toReplace;
        this.isReplacementRequiredFunc = isReplacementRequiredFunc;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return isReplacementRequiredFunc(node) ? toReplace : node;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top