Call Expression within a LINQ to Entities Select w

2019-04-11 23:09发布

问题:

Here's what I want to do :

class MyDbContext : DbContext 
{
    private static Expression<Func<MyClass, int>> myExpression1 = x => /* something complicated ... */;
    private static Expression<Func<Item, int>> myExpression2 = x => /* something else complicated ... */;

    public object GetAllData()
    {
        return (
            from o in MyClassDbSet.AsExpandable() 
            select new 
            {
                data1 = myExpression1.Invoke(o),                      // problem 1
                data2 = o.Items.Select(myExpression2.Compile())       // problem 2
            }
        );
    }
}

UPDATE :

myExpression has to stay separated from my query, because I want to reuse it in multiple LINQ queries.

UPDATE 2 :

Separated myExpression into myExpression1 and myExpression2 to make clear the fact that I want to reuse them separately.

UPDATE 3 :

Added LINQkit to example.

Problem 1 throws : Unable to cast an object of type 'System.Linq.Expressions.FieldExpression' to type 'System.Linq.Expressions.LambdaExpression'.

Problem 2 throws : Internal .NET Framework Data Provider error 1025.

回答1:

On the first problem when using LinqKit you need to assign your expression to a local variable before .Invoke()ing it. More complete explanation can be found on this question.

The second problem is that the select method accept a object of type:

Expression<Func<TSource, TResult>>

Which means that you must supply a lambda expression accepting a TSource object as a parameter and returning a TResult object.

Your TSource object is Item, that is the table you are making your query from. Your TResult is a int in your example, that is what you defined on the expression.

So you must call .Invoke() on the second expression passing a Item object as a parameter, in the same way that you passed the MyClassDbSet object "o". Actually, there is only syntactical diference on both select statements, they essentialy do the same thing.

And you should not call .Compile() on the expressions, this produces a:

Func<TSource, TResult>

Which is a delegate to the compiled version of the expression tree and cannot be translated into a SQL expression. More information can be found here.

It should work with the following changes:

class MyDbContext : DbContext 
{
    private static Expression<Func<MyClass, int>> myExpression1 = x => /* something complicated ... */;
    private static Expression<Func<Item, int>> myExpression2 = x => /* something else complicated ... */;

    public object GetAllData()
    {
        Expression<Func<MyClass, int>> myLocalExpression1 = myExpression1;
        Expression<Func<MyClass, int>> myLocalExpression2 = myExpression2;

        return (
            from o in MyClassDbSet.AsExpandable() 
            select new 
            {
                data1 = myLocalExpression1.Invoke(o),
                data2 = o.Items.Select(item => myLocalExpression1.Invoke(item)) 
            }
        );
    }
}


回答2:

You can try using only a delegate not an expression:

  private static Func<MyClass, int> myExpression = x => /* something complicated ... */;

The problem with this approach, is that the whole MyClass will be retrieved from the database, instead of only the required fields to compute the expression.