Generic expression for where clause - "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." - linq

I am trying to write a really generic way to load EF entities in batches, using the Contains method to generate a SQL IN statement. I've got it working if I pass the entire expression in, but when I try to build the expression dynamically, I am getting a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." So I know this means that EF thinks I'm calling an arbitrary method and it can't translate it into SQL, but I can't figure out how to get it to understand the underlying expression.
So If I do something like this (just showing the relevant snippets):
Function declaration:
public static List<T> Load<T>(IQueryable<T> entityQuery, int[] entityIds, Func<T, int> entityKey, int batchSize = 500, Func<T, bool> postFilter = null) where T : EntityObject
{
var retList = new List<T>();
// Append a where clause to the query passed in, that will use a Contains expression, which generates a SQL IN statement. So our SQL looks something like
// WHERE [ItemTypeId] IN (1921,1920,1922)
// See http://rogeralsing.com/2009/05/21/entity-framework-4-where-entity-id-in-array/ for details
Func<int[], Expression<Func<T, bool>>> containsExpression = (entityArray => (expr => entityArray.Contains(entityKey(expr))));
// Build a new query with the current batch of IDs to retrieve and add it to the list we are returning
newQuery = entityQuery.Where<T>(containsExpression(entityIds));
retList.AddRange(newQuery.ToList());
return retList;
}
Call function:
var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (ek => ek.ItemTypeId)
);
I get "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."
But if I change it to be this:
Function declaration:
public static List<T> Load<T>(IQueryable<T> entityQuery, int[] entityIds, Func<int[], Expression<Func<T, bool>>> containsExpression, int batchSize = 500, Func<T, bool> postFilter = null) where T : EntityObject
{
var retList = new List<T>();
// Build a new query with the current batch of IDs to retrieve and add it to the list we are returning
newQuery = entityQuery.Where<T>(containsExpression(entityIds));
retList.AddRange(newQuery.ToList());
return retList;
}
Call function:
var entities = BatchEntity.Load<ItemType>(from eItemType in dal.Context.InstanceContainer.ItemTypes
select eItemType
, itemTypeData
, (entityArray => (ek => entityArray.Contains(ek.ItemTypeId)))
);
It works fine. Is there any way I can make EF understand the more generic version?

The problem, as you describe, is that the entityKey function in the first example is opaque since it is of type Func rather than Expression. However, you can get the behavior you want by implementing a Compose() method to combine two expressions. I posted the code to implement compose in this question: use Expression<Func<T,X>> in Linq contains extension.
With Compose() implemented, your function can be implemented as below:
public static List<T> Load<T>(this IQueryable<T> entityQuery,
int[] entityIds,
// note that this is an expression now
Expression<Func<T, int>> entityKey,
int batchSize = 500,
Expression<Func<T, bool>> postFilter = null)
where T : EntityObject
{
Expression<Func<int, bool>> containsExpression = id => entityIds.Contains(id);
Expression<Func<T, bool>> whereInEntityIdsExpression = containsExpression.Compose(entityKey);
IQueryable<T> filteredById = entityQuery.Where(whereInEntityIdsExpression);
// if your post filter is compilable to SQL, you might as well do the filtering
// in the database
if (postFilter != null) { filteredById = filteredById.Where(postFilter); }
// finally, pull into memory
return filteredById.ToList();
}

Related

c# linq combine Expression<Func<T, bool>> predicate in where clause with another condition

Is it possible to construct a where clause like this, where predicate is of type Expression<Func<T, bool>> predicate:
var resultQuery = query.Where(q => !q.IsDeleted && predicate).ToList();
I would like to avoid double where clauses like this:
var resultQuery = query.Where(q => !q.IsDeleted).Where(predicate).ToList();
Instead, you could use custom extension methods and filter result using them:
public static class QueryExtensions
{
public static IQueryable<Image> NonDeleted(this IQueryable<Image> queryable)
{
return queryable.Where(x => !x.Deleted);
}
public static IQueryable<Image> LatestOnly(this IQueryable<Image> queryable)
{
return queryable.Where(x => x.CreateDate <= DateTime.UtcNow.AddDays(-7));
}
}
And then combine them in query:
var result = context
.Images
.NonDeleted()
.LatestOnly()
.ToList();
I like this approach cause it's clean and easy to read. You can also use interfaces in your entities and extension which use those interfaces to quickly filter items based on interfaces that implemented on entity. For example:
public interface ICreationDate{
DateTime CreateDate {get;}
}
public class Image: ICreationDate{
public DateTime CreateDate {get; private set;} = DateTime.UtcNow;
}
Then extension can be changed like this:
public static IQueryable<T> LatestOnly<T>(this IQueryable<T> queryable)
where T : ICreationDate
{
return queryable.Where(x => x.CreateDate <= DateTime.UtcNow.AddDays(-7));
}
This approach gives you more flexibility and reusability.
I know these all are too far from your original question, but it may bring you some alternative aproaches
IT is impossible. You need to provide a lambda expression as a parameter to the Where clause which would be compiled to an expression tree and after that translated into some SQL query. In your example
var resultQuery = query.Where(q => !q.IsDeleted && predicate).ToList();
you are combining a lambda expression and a boolean check. The only way to avoid the double Where clauses is to create a helper function that returns a lambda expression for filtering which includes filtering for the IsDeleted flag and the predicate logic i.e.
private System.Linq.Expressions.Expression<Func<T, bool>> filterPredicate(int n)
{
return q => !q.IsDeleted && q.Age > n;
}
Here we are assuming that
q.Age > n
is the logic of your predicate function. And then use the filter predicates like this:
var resultQuery = query.Where(filterPredicate(5)).ToList();
More about lambda expressions and expression trees you can read here https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression-1?view=net-5.0

Generic Linq with specific where/order by clauses

I want to create a generic method that allows me to search a Sitecore 7 index using Linq to Sitecore (.Net 4.5).
This will be used for various searches such as:
Get top 5 latest news pages
Get the last 20 event pages
Generic site search
Etc, etc, etc
I can create a very generic search method, which works for all types of page. It provides a generic template predicate for the Where clause that would be applicable for all types of search.
However, for the searches above I also need to add specific predicates for the Where clause, and specific expressions for Order By etc. The intention would be to create subclasses for each type of search, which would implement these specifics.
I've condensed some code which is shown below. In this I try to add specific functionality for a new page search.
All page classes derive from "Base".
public virtual ReadOnlyCollection<T> Search<T>() where T : Base, new()
{
List<T> results = new List<T>();
using (IProviderSearchContext context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
Expression<Func<T, bool>> outerPredicate = PredicateBuilder.True<T>();
// Create a predicate for the template id.
Expression<Func<T, bool>> templatePredicate = PredicateBuilder.False<T>();
templatePredicate = templatePredicate.Or(baseItem => (baseItem.TemplateIdFromIndex.Equals("8b1fc00c76314d32b8e1bce93dd41ccd")));
// Create a predicate for a news page search.
Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.False<NewsPageBase>();
datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
// 1. outerPredicate = outerPredicate.And(datePredicate);
// 2. IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate).OrderByDescending(newsPage => newsPage.ArticleDate).Take(5);
IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate);
results = searchQuery.ToList();
}
return new ReadOnlyCollection<T>(results);
}
This code complies and runs.
However, if I uncomment the line marked as [1], a compiler error me from Anding the generic template predicate with the specific news page predicate.
The error is "The type arguments for method 'Sitecore.ContentSearch.Linq.Utilities.PredicateBuilder.And(System.Linq.Expressions.Expression>, System.Linq.Expressions.Expression>)' cannot be inferred from the usage".
It's a similar error if I uncomment the line marked as [2].
How do I create a generic method which has the specific functionality for each type of search?
Modifying your code as follows achieves what you need..
public virtual ReadOnlyCollection<T> Search<T>() where T : Base, new()
{
List<T> results = new List<T>();
using (IProviderSearchContext context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
Expression<Func<T, bool>> outerPredicate = PredicateBuilder.True<T>();
// Create a predicate for a news page search.
Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.True<NewsPageBase>();
datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
//outerPredicate = outerPredicate.And((Expression<Func<T,bool>>)(object)datePredicate);
outerPredicate = outerPredicate.And((Expression<Func<T, bool>>)(object)datePredicate);
// 2. IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate).OrderByDescending(newsPage => newsPage.ArticleDate).Take(5);
IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate);
results = searchQuery.ToList();
}
return new ReadOnlyCollection<T>(results);
}
The changes I made were:
Removed the unused templatePredicate - I'm not sure if you had intended to use this or had forgotten to remove it when condensing your original code.
Create datePredicate using PredicateBuilder.True instead of PredicateBuilder.False - this is needed for use in .And() otherwise no results will be returned.
I (un)box datePredicate for use within outerPredicate.And(). The compiler complains if you try to cast datePredicate to Expression<Func<T, bool>> but casting it to object first solves this.
I have found a work around.
Instead of:
Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.False<NewsPageBase>();
datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
You can use:
Expression<Func<T, bool>> innerPredicate = PredicateBuilder.False<T>();
innerPredicate = innerPredicate.Or(item => ((DateTime)item[(ObjectIndexerKey)"article_date"] < DateTime.Now));
For the OrderBy clause, you can use:
searchQuery = searchQuery.OrderByDescending(item => item[(ObjectIndexerKey)"article_date"]);
This will work if your base class is subclassed from the Sitecore class "SearchResultItem".

Can I convert a Func<T, bool> to a Func<U, bool> where T and U are POCO classes where I can map properties of one to the other? If so, how?

I have a scenario where a method will take a predicate of type Func< T, bool > because the type T is the one that is exposed to the outer world, but when actually using that predicate I need that method to call another method that will take in Func< U, bool > where properties of T are mapped to properties of U.
A more concrete example would be:
public IEnumerable<ClientEntity> Search(Func<ClientEntity, bool> predicate)
{
IList<ClientEntity> result = new List<ClientEntity>();
// Somehow translate predicate into Func<Client, bool> which I will call realPredicate.
_dataFacade.Clients.Where(realPredicate).ToList().ForEach(c => result.Add(new ClientEntity() { Id = c.Id, Name = c.Name }));
return result.AsEnumerable();
}
Would that be possible?
Please note that ClientEntity is a POCO class that I defined myself while Client is an Entity Framework class created by the model (DB first).
Thanks!
I once attempted this. It resulted in a not-too-bad working expression tree rewriter when the expression tree consist of the simpler operations (equals, larger-then, smaller-then, etc).
It can be found here.
You can use it as:
Expression<Func<Poco1>> where1 = p => p.Name == "fred";
Expression<Func<Poco2>> where2 = ExpressionRewriter.CastParam<Poco1, Poco2>(where1);
EF doesn't use lambdas - it uses Expression Trees
Func<T, bool> lambda = ( o => o.Name == "fred" );
Expression<Func<T, bool>> expressionTree = ( o => o.Name == "fred" );
Expression Trees are in-memory object graphs that represent a given expression.
As they are just objects, you can create or modify them.
Here's another link: MSDN: How to: Modify Expression Trees
What I ended up doing did not require the use of Expression Trees:
public IEnumerable<ClientEntity> Search(Func<ClientEntity, bool> predicate)
{
IList<ClientEntity> result = new List<ClientEntity>();
Func<Client, bool> realPredicate = (c => predicate(ConvertFromClient(c)));
_dataFacade.Clients.Where(realPredicate).ToList().ForEach(c => result.Add(ConvertFromClient(c)));
return result.AsEnumerable();
}
private static ClientEntity ConvertFromClient(Client client)
{
ClientEntity result = new ClientEntity();
if (client != null)
{
// I actually used AutoMapper from http://automapper.org/ here instead of assigning every property.
result.Id = client.Id;
result.Name = client.Name;
}
return result;
}

Passing expression to Linq to SQL

I am trying to get the following method to work:
private static IQueryable<TObject> ApplyOrderBy<TObject, TKey>(IQueryable<TObject> query, OrderByDirection orderByDirection,
Expression<Func<TObject, TKey>> sortExpression, ref bool first)
{
if (orderByDirection == OrderByDirection.None || sortExpression == null) return;
if (orderByDirection != OrderByDirection.Ascending && orderByDirection != OrderByDirection.Descending)
throw new Exception(string.Format("Should never get here! Unknown OrderByDirection enum - '{0}'.", orderByDirection));
if (first)
{
first = false;
query = orderByDirection == OrderByDirection.Ascending
? query.OrderBy(sortExpression)
: query.OrderByDescending(sortExpression);
}
else
{
query = orderByDirection == OrderByDirection.Ascending
? ((IOrderedQueryable<TObject>)query).ThenBy(sortExpression)
: ((IOrderedQueryable<TObject>)query).ThenByDescending(sortExpression);
}
return query;
}
This method works great if you call it like this:
ApplyOrderByToGet(ref query, OrderByDirection.Ascending, x => x.StartDateTime, ref first);
The sort expression then has a strongly typed DateTime as the type and LINQ to SQL is happy. However, if you want to pass an array of these expressions with varying types, you ultimately need a list with "object" as the type. Problem is that LINQ to SQL does not figure out that the type is not object, but instead is DateTime. This works with a regular list using LINQ to objects.
Seeing as it’s possible to navigate the expression tree and find out what the type is, would it be possible to cast/convert the expression before calling ApplyOrderBy?
Cast or convert from:
Expression<Func<T, object>>
to:
Expression<Func<T, DateTime>>

How to use a string in the linq where clause?

I am trying to send a Linq query as a string to a method to be used in a where clause. Since IEnumerable wouldn't work for this, I have converted my IEnumerable to IQueryable and still it throws error. The following is the code:
public static void FilterData(string Query)
{
if((List<MemberMaintenanceData>)HttpContext.Current.Session["Allmembers"] != null)
{
//Get the IEnumerable object colection from session
var data = (List<MemberMaintenanceData>) HttpContext.Current.Session["Allmembers"];
//Convert it to IQueryable
IQueryable<MemberMaintenanceData> queryData = data.AsQueryable();
//This line doesn't compile!!
queryData = queryData.Where(Query);
HttpContext.Current.Session["Allmembers"] = queryData.AsEnumerable().ToList();
}
}
I intended passing "a => a.AccountId == 1000" as Query
There is a free (and open source) library, provided by Microsoft for parsing strings into Lambda expressions that can then be used in Linq queries. It also contains versions of the standard query operators such as Where() that take a string parameter. You can find it described in Scott Guthries blog post on Dynamic Linq.
For example, you can do queries like this (adapted from a snippet from the Scott guthrie link)
// imagine these have come from a drop down box or some other user input...
string thingToSelectBy = "City";
string citySelectedByUser = "London";
int minNumberOfOrders = 10;
string whereClause = String.Format("{0} = #0 and Orders.Count >= #1", thingToSelectBy);
var query = db.Customers
.Where(whereClause, citySelectedByUser, minNumberOfOrders)
.OrderBy("CompanyName")
.Select("new(CompanyName as Name, Phone");
The Where clause in thisw code snippet shows how you create a where clause using a parameterised string and then dynamically inject values for the parameters at run time, for example, based on user input. This works for parameters of any type.
In your example, the where clause would be
whereClause = "AccountId = 1000";
So in effect you would be doing something like
var newFilteredQueryData = queryData.Where("AccountId = 1000");
That link also contains the location where you can download the source code and a comprehensive document describing the dynamic query API and expression language.
Given a class such as:
public class foo
{
public int AccountID {get;set;}
}
You should be able to do something like this:
Expression<Func<foo, bool>> filter = f => f.AccountID == 1000;
And then pass that as your query. If it is really needed as a string you can do this:
filter.ToString();
//By Using this library
using System.Linq.Dynamic.Core;
InventoryList = Repository.GetAll(); // IQueryable
string filterString = "UnitPrice > 10 And Qty>100 OR Description.Contains("Dairy")";
var filteredGenericList = InventoryList.Where(filterString);

Resources