Jonathan Wood
Jonathan Wood

Reputation: 67195

Using Expression<Func<>> in a LINQ Query

I want to define a Func<ProductItemVendor, bool> filter expression named CompareProductItemVendorIds, which can be used throughout my application, mostly within Entity Framework/LINQ queries.

I've learned that in order to be able to use this filter in a LINQ query, I must declare it as Expression<Func<>> instead of just Func<>. I understand the reason for this, and it's easy for me to do this.

But I'm having the following problems using that expression in my queries.

First, code such as:

ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds)

Note: ProductItem is a database entity, and its ProductItemVendors property is a navigation collection.

Produces the error:

`Instance argument: cannot convert from 'System.Collections.Generic.ICollection' to 'System.Linq.IQueryable'

And second, code such as:

var results = from v in Repository.Query<ProductItemVendor>()
              where CompareProductItemVendorIds(v)
              select v;

Produces the error:

'CompareProductItemVendorIds' is a 'variable' but is used like a 'method'

So I have my nice shiny new Expression<Func<>>. How can I use it in my LINQ queries?

Upvotes: 8

Views: 10151

Answers (3)

user5233494
user5233494

Reputation: 235

I don't think composing expressions like that is supported by default.
You could try LINQKit

Here's an example from the LINQKit page; one Expression<Func<>> inside another:

Call Invoke to call the inner expression Call Expand on the final result. For example:

Expression<Func<Purchase,bool>> criteria1 = p => p.Price > 1000;
Expression<Func<Purchase,bool>> criteria2 = p => criteria1.Invoke (p)
                                                 || p.Description.Contains ("a");

Console.WriteLine (criteria2.Expand().ToString()); 

(Invoke and Expand are extension methods in LINQKit.) Here's the output:

p => ((p.Price > 1000) || p.Description.Contains("a"))

Notice that we have a nice clean expression: the call to Invoke has been stripped away.

Upvotes: 1

flindeberg
flindeberg

Reputation: 5007

The first case;

ProductItemVendor productItemVendor = ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds);

Produces the error:

`Instance argument: cannot convert from 'System.Collections.Generic.ICollection' to 'System.Linq.IQueryable'

Happens because it is an ICollection, the entity has already been loaded. Given dbContext or objectContext lazy loading vs explicit loading is implemented a bit differently, I assume you are using explicit loading though. If you change the loading to lazy the type of ProductItemVendors will be IQueryable and what you are trying will succeed.

Given the second case, the expression must be compilable to SQL, else you get a lot of weird errors, probably it's possible that that is the case here.

It's hard to give you more explicit help given the information in the question, I cannot recreate it easily. If you can create a MWE-solution and upload it somewhere I can have a look, but I'm afraid I can't help more here.

Upvotes: 1

Aducci
Aducci

Reputation: 26634

ProductItem is already an Entity, so you can't use your Expression, you need to use Compile() to get the Func<> from your Expression<Func<>> since ProductItemVendors is no longer an IQueryable

ProductItem.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds.Compile())

You would have to use your Expression on the ProductItemVendorsContext like this:

var item = Context.ProductItemVendors.FirstOrDefault(CompareProductItemVendorIds);

You cant use an Expression inside query syntax, you need to use method sytanx

var results = from v in Repository.Query<ProductItemVendor>()
                                  .Where(CompareProductItemVendorIds)
              select v;

Upvotes: 5

Related Questions