3

Before try to use a variable (this has sql server do the calculation):

IQueryable<MyEntity> query = _dbSet; // IQueryable<TEntity>
var results = query.Select(m => new MyViewModel
{
    MyCalculation = m.Column1 * m.Column2
}).ToList();

What I want to do (dynamically create part of my select statement from a Func variable or some other kind of variable to allow this):

IQueryable<MyEntity> query = _dbSet; // IQueryable<TEntity>
Func<MyEntity, decimal> funcVariableAttempt = m => m.Column1 * m.Column2;
var results = query.Select(m => new MyViewModel
{
    MyCalculation = funcVariableAttempt.Invoke(m) // My foolish attempt does not work.
}).ToList();

The error I get when I try what I want (aka my foolish attempt):

LINQ to Entities does not recognize the method 'System.Decimal Invoke(MyProject.Repository.Models.MyEntity)' method, and this method cannot be translated into a store expression.

How do I define and utilize a variable (maybe a Func variable) to define part of a Select statement?

  • You can't use a Func, it can't be passed to the SQL layer. – Gusman Aug 07 '17 at 23:39
  • 1
    See [this answer](https://stackoverflow.com/q/42067722/5951133) and a library [LinqKit](http://www.albahari.com/nutshell/linqkit.aspx) which can be used to embed `Expression>` in `IQueryable` – Jonathan Tyson Aug 07 '17 at 23:57

3 Answers3

1

It's a completely valid question that I stumbeled across earlier and found a solution that works well for me.

The problem with your Func<MyEntity, decimal> is that it is a delegate and that the O/R mapper has to have access to the internal expression (the multiplication of two properties in your case). But this information is compiled into the delegate and hidden forever.

If you however start off with a Expression<Func<MyEntity, decimal>> customCalculation, things look more promising as you have the internal logic as an expression tree. Problem with this is: you cannot invoke an expression. customCalculation(m) doesn't compile.

The compiler would let you write

m => new MyViewModel { MyCalculation = customCalculation.Compile()(m) }

, but this wouldn't be understood by most O/R mappers.

But you see that it at least somehow contains the customCalculation lambda expression and also how it relates to its surrounding expressions.

Getting from here to the expression tree as in your original working version involves some expression manipulation:

We have to replace customCalculation.Compile()(m) with the body of the lambda that is to be Compile()d, but with the lambda's parameter(s) replaced with the respective expression(s) of the delegate invocation. So if customCalculation were x => x.Column1 * x.Column2, customCalculation.Compile()(m) would have to be replaced with m.Column1 * m.Column2

Doing so is not trivial, since the lambda itself has to be dug out of a field inside an instance of a compiler generated closure class.

I've posted my implementation of this expression manipulator in another similar question . Hope that helps.

With that, you should be able to:

var customCalculation = (Expression<Func<MyEntity, decimal>>)(x => x.Column1 * x.Column2);
var selector = Express.Prepare((Expression<Func<MyEntity, MyViewModel>>)(m => new MyViewModel { MyCalculation = customCalculation.Compile()(m) }));
var result = query.Select(selector).ToList();
tinudu
  • 1,139
  • 1
  • 10
  • 20
  • fyi: I consider this the best answer by far! Thank you so much. You actually confused me a bit here but, in conjunction with your link to a similar question I was able to accomplish my goals! Thanks again! – Steven Lyon Aug 08 '17 at 14:21
  • Glad I could help. I admittedly did a bad job at explaining things. Edited the answer to hopefully make it more understandable. – tinudu Aug 09 '17 at 09:27
0

As you already know, your funcVariableAttempt makes no sense to your database, so you have to call your method in the linq-to-object context. i.e. for instance first fetch the data as an Enumerable, then call your method:

var results = query.Select(m => new {
Column1= col1,
Column2= col2
}).AsEnumerable()
    .Select(m => new MyViewModel
{
    MyCalculation = Foo(m.Column1, m.Column2)
});

*Note: Code is not tested.

Mohsen Kamrani
  • 7,177
  • 5
  • 42
  • 66
0

You should call ToList() first and perform the Select() on the result in memory.

var results = query.ToList() 
 .Select(m => new MyViewModel { 
    MyCalculation = Foo(m.Column1, m.Column2)
 });

You're trying to perform the Select as part of the query. You should just use a regular function for the mapping calculation. Lambda functions are useful with LINQ but in this case they're not needed.

Willie
  • 106
  • 2
  • 3
  • 2
    hint: use `AsEnumerable()` instead of `ToList()` in this situation. You don't want a list, you only want to trick the compiler into overload-resolving the IEnumerable version of `Select`. Also I think the question is about how to get the MyCalculation expression into the `.Select` selector to be translated into SQL by EF. – tinudu Aug 08 '17 at 00:44