Passing an expression from a LINQ Queryable - c#

I have a Service Repository pattern built on top of Entity Framework.
The service has methods such as Find(IQuery query) that return IEnumerable.
The IQuery object is our own query object type where we convert strings to an IQueryable expression that the repository, which exposes IQueryable, can use.
What I'd like to do is be able to write a queryable on the client side and pass that over the service so that we can take advantage of the static typing and linq style queries instead of building our own query object in formation.
In other words I want to be able to do something like:
var query = new List<Type>().Where(x => x.Property == "argument").AsQueryable();
service.find(query);
Then I would pass the queryable or the expression it creates to my repository and work like that.
Is this sort of thing possible, or would I have to build the expression from scratch? It seems like this should be possible, but I really don't know where to begin or see examples of how to share an expression like this.

If you call AsQueryable() on an IEnumerable that doesn't already implement IQueryable, then that whole enumerable is just stuffed into a ConstantExpression, and any previous LINQ operations are not expressed as expression trees.
If you call AsQueryable() right on the source, then the LINQ operations will return an IQueryable with the proper query expression tree:
var query = new List<Type>().AsQueryable().Where(x => x.Property == "argument");

Related

How to invoke lambda expression when calling sql query?

I need to create a generic method (ContainsLambda) that takes collection and a lambda expression (a single property) and would check if the given property contains values in the given collection.
Here is my method
public static TModel[] ContainsLambda<TModel, TKey>(IEnumerable<TKey> keys, Func<TModel, TKey> property)
{
DbSet<TModel> repository = DbContext.Set<TModel>();
return repository.Where(x => keys.Contains(property.Invoke(x)))
.ToArray();
}
Then I would call it using something like this ContainsLambda<Customer, int>(new List<int> {10, 20, 30}, p => p.Age)
The above code throws the following runtime error.
LINQ to Entities does not recognize the method 'Int32
Invoke(Customer)' method, and this method cannot be translated into a
store expression.
How can I call .Invoke() on a lambda that would be used in LINQ which would then be translated into SQL expression?
Try making your repository an enumerable. This bypasses Linq to Entities, and causes your code to run in Linq to Objects.
return repository.AsEnumerable()
.Where(x => keys.Contains(property.Invoke(x)))
.ToArray();
If you don't want all of the records in your repository to travel across the wire (i.e. you need the filtering to run on the server), you will have to write a custom SQL query to retrieve the data.

How does the compiler determine which provider to use when using multiple Linq2 .... contexts?

Based on this question: Why isn't it possible to combine LINQ to XML with LINQ to SQL? I would like to know how the compiler determines which LINQ provider to use.
In question Linq2Xml and Linq2Sql are used and the compiler uses Linq2Sql. I am wondering why this is because both providers are used: xml and sql.
Could someone explain how the compiler knows which one to use?
There are different overloads of Linq methods. Some of them are operating on IEnumerable<T> and some of them are IQueryable<T>.IEnumerable<T> methods are used in Linq to Objects, Linq to Xml etc.
IQueryable<T> on the other hand is created for query providers. From MSDN
The IQueryable<T> interface is intended for implementation by query providers.
This interface inherits the IEnumerable<T> interface so that if it
represents a query, the results of that query can be enumerated.
Enumeration forces the expression tree associated with an
IQueryable<T> object to be executed. Queries that do not return
enumerable results are executed when the Execute<TResult>(Expression)
method is called.The definition of "executing an expression tree" is specific to a query provider. For example, it may involve translating the expression tree to a query language appropriate for an underlying data source.
So that's how they are differentiate. But the compiler actually has nothing to do with this, the execution of the query completely depends on which source you run the query.For example in Linq to SQL, assume you have a dbContext.Users, it will return an IQueryable<T> so the methods you call on this object will use the IQueryable<T> overloads and they are convertible to SQL by the Linq to SQL query provider. And if you turn them into an IEnumerable<T> by calling AsEnumerable or other linq methods (ToList, ToArray), the result will be fetched from DB and the rest of the query will be executed in memory using Linq to Objects.

Dynamic Linq gives error in combination with EntityFramework

We use Entity Framework, and we need some runtime build queries on our objects. Building expression trees from scratch seems like a lot of work, so we want to use "System.Linq.Dynamic"
Working through the samples I got this to work:
dbModel.As.Where("AStuff.Contains(#0) OR AStuff.Contains(#1)","ac","bc")
But if I try to build the expressions seperately like this:
Expression<Func<A, bool>> predicateA =
DynamicExpression.ParseLambda<A, bool>(
"AStuff.Contains(#0)",
"ac"
);
Expression<Func<A,bool>> predicateB =
DynamicExpression.ParseLambda<A, bool>(
"AStuff.Contains(#0)",
"bc"
);
dbModel.As.Where("#0(it) OR #1(it)", predicateA, predicateB);
it explodes with an exception:
NotSupportedException>>The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
It may be possible to build the entire query in the first form, but the later would be more useful in our scenario. Is there a way to make that work?
Just use Predicate Builder to join (Or/And) multiple predicates.

The method 'Where' cannot follow the method 'Select' or is not supported

Why am I getting:
The method 'Where' cannot follow the method 'Select' or is not
supported. Try writing the query in terms of supported methods or call
the 'AsEnumerable' or 'ToList' method before calling unsupported
methods.
...when using the WHERE clause, like when calling:
XrmServiceContext.CreateQuery<Contact>().Project().To<Person>().Where(p => p.FirstName == "John").First();
?
This works:
XrmServiceContext.CreateQuery<Contact>().Project().To<Person>().First();
Also this works:
XrmServiceContext.CreateQuery<Contact>().Where(p => p.FirstName == "John").First();
I'm using AutoMapper QueryableExtension.
Additional info:
I don't want to call ToList() before the Where clause. I know it will works that way.
CreateQuery<TEntity>() returns IQueryable<TEntity>.
It's because whatever query provider you are using isn't able to handle this. It's not invalid in the general case; in fact most query providers do support filtering after projecting. Certain query providers simply aren't as robust as others, or they are representing a query model that is less flexible/powerful than the LINQ interface (or both). As a result, LINQ operations that are correct from the C# compiler's point of view might still not be translatable by the query provider, so the best it can do is throw an exception at runtime.
Why don't you just move the where so it is before the projection? It will result in a single query being executed which filters and projects:
XrmServiceContext.CreateQuery<Contact>().Where(p => p.FirstName == "John").Project().To<Person>().First();
Looking at AutoMapper's instructions for the QueryableExtensions it has an example showing the Where clause before the projection. You need to refactor your code to support this model, as opposed to placing the Where clause after the projection.
public List GetLinesForOrder(int orderId)
{
Mapper.CreateMap()
.ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name);
using (var context = new orderEntities())
{
return context.OrderLines.Where(ol => ol.OrderId == orderId)
.Project().To().ToList();
}
}
Given the limitations of Dynamic CRM's LINQ provider you should not expect AutoMapper to necessarily get the LINQ query correct.
There is actually a logic behind this design. As the developer you create a working Where clause. You then let AutoMapper's Project().To() define the select statement. Since CRM's LINQ provider has support for anonymous types in it should work correctly. The purpose of projection in AutoMapper is to limit the data retrieved from each class to only that needed for the projected to class. It is not intended to write a Where clause based on the projected to class.

Generic Repository Linq2Sql impedence mismatch problem

I am working on a repository pattern where the API look as follows:
var visitor = repository.Find(x => x.EmailAddress == credentials.EmailAddress &&
x.Password == credentials.Password);
where visitor is a domain object and x represents this domain object. The method signature of the Find method on the repository is:
T Find(Func<T, bool> query);
This is all wonderful until I attempt to use this with Linq2Sql because linq2sql creates its own objects and as a result when I want to call:
context.visitors.FirstOrDefault(query);
there is a type mismatch because linq2sql expects a function of the type it created and not the function I am passing in.
Well to start with you'll need to change your Find signature to:
T Find(Expression<Func<T, bool>> query);
LINQ to SQL needs to have the logic as an expression tree instead of a plain delegate, otherwise it can't work out how to translate it into SQL.
Beyond that, I'm afraid it's not terribly clear - it sounds like you're not using the same domain classes for your repository and LINQ to SQL. Is that right? That sounds like a potential problem; at the very least it's going to make life pretty tricky.

Categories