Coming from this question, I have almost the same need.
I want to serialize an array of predicates (Expression<Func<T, bool>>[]) to send it over a gRPC connection.
On the server side, I'm looping through each predicate from the array to apply a WHERE clause on an IQueryable :
foreach (var predicate in predicates)
{
items = items.Where(predicate);
}
So my method's signature looks like that : public Task<List<T>> Get(params Expression<Func<T, bool>>[] predicates)
Using the Serialize.Linq library, I serialize my array of predicates like that :
var serializedPredicates =
predicates
.Select(x => _serializer.SerializeText(x));
var request = new GetRequest()
{
Filters = JsonSerializer.Serialize(serializedPredicates)
};
So I use the library to create a list of string (serialized expressions), then I serialize this list using the usual System.Text.Json library to send it over gRPC.
But I'm not able to deserialize this list on the server side.
Here is the code of the deserialization :
var serializedPredicates =
JsonSerializer
.Deserialize<IEnumerable<string>>(request.Filters);
var predicates =
serializedPredicates
.Select(x => _serializer.DeserializeText(x))
.ToArray();
var result = await service.Get(predicates);
So I take the request.Filters and deserialize it as a list of string, then for each string I deserialize it using the Serialize.Linq library (_serializer.DeserializeText(x)) and apply .ToArray() to get an array of Expression.
Unfortunately, I can only get a Expression[] using this method (which is already kinda ugly), but I'm not having a true Expression<Func<T, bool>>[] like my method is awaiting.
So it obviously results in an error :
Exception was thrown by handler. RuntimeBinderException: The best overloaded method match for 'Tests.Repository<DataModel.TestData>.Get(params System.Linq.Expressions.Expression<System.Func<DataModel.TestData,bool>>[])' has some invalid arguments
Because I'm using gRPC, I can't create a generic service. It would have been easier to cast the deserialization like that :.Select(x => (Expression<Func<T, bool>>) _serializer.DeserializeText(x)) but I don't know the type of T before the running time.
I also tried using
var predicatesType = typeof(Expression<>)
.MakeGenericType(Expression.GetFuncType(requestType, typeof(bool)));
...
.Select(x => Convert.ChangeType(_serializer.DeserializeText(x), predicatesType))
where the requestType is sent over gRPC too, to retrieves the Type in a variable. But unfortunately the Expression does not seem to be convertible :
Exception was thrown by handler. InvalidCastException: Object must implement IConvertible
How can I properly serialize a Expression<Func<T, bool>>[] and deserialize it as a proper Expression<Func<T, bool>>[] ?
Thanks for your help.
Possibly I'm missing something obvious here, but assuming that you have all the required types on the client side (otherwise I doubt this should work at all) - have you tried just casting the expressions to the required type:
var predicates = serializedPredicates
.Select(x => _serializer.DeserializeText(x))
.Cast<Expression<Func<DataModel.TestData, bool>>>()
.ToArray();
If needed - move the casting to helper method (or use Enumerable.Cast itself) and invoke it dynamically via reflection.
Related
(this is for .Net Framework 4.7)
I'm trying to write up some extension methods to aid in creating dynamic where clauses for various entities. I started a few days ago, so there's likely a lot that I don't know and some that I probably misunderstood.
I managed to create one extension method already for filtering by 1 property which works as I expect it to (I did use reflection to get the property, couldn't get it working with an interface - well, without it executing the sql that is).
I can't seem to be able to get this one working for a lambda expression though.
Note, that the solution must not trigger sql execution. Because I was able to write up some variants that "worK', but they will trigger sql execution.
The way I work with this is that once I have the code ready, I start debugging and have the "query" in the watch. And it looks like this (notice the sql code)
Once I step over my FilterString method call, it either turns into a sql result, or I get an exception (with current code), which it shouldn't:
So here's my current code that throws the exception (currently not dealing with the "match" parameter, I am implementing an "equals" call. There will be others like, starts With, like, etc)
The exception is just one of those "type mismatch" having function cannot be passed as param to string Equals or what not.
public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
string criteriaItem, Expression<Func<T, string>> getItemString)
where T : class
{
if (string.IsNullOrEmpty(criteriaItem))
{
return query;
}
var param = Expression.Parameter(typeof(T), "r");
var selector = Expression.Lambda<Func<T, string>>(getItemString, param);
Expression<Func<string, bool>> prototype = item => item == criteriaItem;
var predicate = Expression.Lambda<Func<T, bool>>(
prototype.Body.ReplaceParameter(prototype.Parameters[0], selector.Body),
selector.Parameters[0]);
return query.Where(predicate);
}
and the one that executes the sql instead of just generating it
public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
string criteriaItem, Expression<Func<T, string>> getItemString)
where T : class
{
if (string.IsNullOrEmpty(criteriaItem))
{
return query;
}
var param = Expression.Parameter(typeof(T), "r");
//var value = Expression.Constant(getItemString);
var equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
var item = Expression.Invoke(getItemString, param);
var body = Expression.Call(Expression.Constant(criteriaItem),
equals,
item);
return query.Where(Expression.Lambda<Func<T, bool>>(body, param));
}
calling these is done like so
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.MaybeSomeOtherProp.SomeString);
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.Name);
This same extension method will be called on any number of different entities, with nay number of different properties and prop names. I guess I could make use of the reflection version I got working and passing in all the property names in some array of some sort, but that is just plain ugly.
So long story short, how can I get this working in the way I explained above, taht is: having the sql generated instead of executed?
Thank you,
Note, the "ReplaceParameter" extension method is the one from here: https://stackoverflow.com/a/39206392/630515
So, you're trying to merge your prototype item => item == criteriaItem. With a passed in string property expression, like (r) => r.SomeProperty.Name to create (r) => r.SomeProperty.Name == criteriaItem.
Expression<Func<string, bool>> prototype = item => item == criteriaItem;
var predicate = Expression.Lambda<Func<T, bool>>(
ReplacingExpressionVisitor.Replace(
prototype.Parameters[0],
getItemString.Body,
prototype.Body),
getItemString.Parameters[0]);
And I think you're trying to do it this way so that criteriaItem is bound to an sql parameter, rather than being inlined as a string constant. But your question was a little hard to follow.
I would like my Web API to be able to sort its output by a string parameter such as this one:
http://myapi.com/api/people?skip=0&take=50&orderBy=lastName&descending=true.
Because I also have pagination support (with skipand take) in my API, I would like the orderBy and descending parameter to be applied to the SQL query directly, so that the correct result comes from the database.
When doing this however, the code can become very hard to manage when trying to match the parameters for orderBy with the actual properties of the classes I wish to sort by just using string comparisons.
I have found a solution which is supposed to work with LINQ to Entities and thus also with the new EF7, however when I try to compile this code using the new Core CLR, I get the following message:
Error CS1503 Argument 2: cannot convert from 'System.Linq.Expressions.Expression>' to 'string'
The code from the solution that fails is the OrderBy<T>method:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
It seems like the new Core CLR does not support this attempt. Is there another way to get the solution to work with the new CLR? If no, what other alternatives do I have to enable sorting using EF7 without resulting in countless if or switch statements to compare the input strings to the property names?
The solution from your link uses an "Expression.Convert" which most of the time doesn't work with LINQ to Entities.
Here is a working extension method:
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
{
// LAMBDA: x => x.[PropertyName]
var parameter = Expression.Parameter(typeof(TSource), "x");
Expression property = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(property, parameter);
// REFLECTION: source.OrderBy(x => x.Property)
var orderByMethod = typeof(Queryable).GetMethods().First(x => x.Name == "OrderBy" && x.GetParameters().Length == 2);
var orderByGeneric = orderByMethod.MakeGenericMethod(typeof(TSource), property.Type);
var result = orderByGeneric.Invoke(null, new object[] { source, lambda });
return (IOrderedQueryable<TSource>)result;
}
Disclaimer: I'm the owner of the project EF+ on GitHub.
You can find other methods to order by property name in my repository: GitHub
OrderByDescending
ThenBy
ThenByDescending
AddOrAppendOrderBy
AddOrAppendOrderByDescending
EDIT: Answer sub-question
Is it possibly to sort by navigation properties using something like
this, e.g. a property name "NavigationProperty.PropertyName"
Yes, you can either split the string and loop to create the expression with the property path or use a real expression evaluator.
Disclaimer: I'm the owner of the project Eval-Expressions.NET
This library allows you to execute all LINQ method dynamically.
See: LINQ Dynamic
var result = list.OrderByDynamic(x => "NavigationProperty.PropertyName");
I'm trying to separate out as much as possible an android from the business logic in order to speed things up.
Dotted around the code I have a piece of LINQ that looks like this
var jobItemDoneTest = JobItemsData.GetJobIfDone(wheelpos, theOrder.GetOrderItemStockItemID);
var jobsDoneList = new JobItemsData(theOrder.OrderData.jobItemsID).JobItemDone;
var stillToDo = theOrder
.OrderItemsData
.Where(p => jobsDoneList.All(p2 => p2.orderItemStockItemID != p.orderItemStockItemID))
.Where(t => !t.description.Contains("2hr"))
.Where(t => !t.description.Contains("Staff"))
.ToList();
In other words, there is a comparison between two lists of to filter out some results.
What I'm trying to do is remove the instances of this to create a generic method in a business object class.
So far, I've got this
public List<T> GetWorkStillToDo<T, U>(List<T> orderItems, List<U> jobItems, params object[] searchList1, string searchList2)
{
var stillToDo = orderItems.Where(p=>jobItems.Add(p2=>p2.orderItemStockItemID != p.orderItemStockItemID);
}
The problem is that if I want to search on different properties for p=> and p2=> and then filter on the where conditions, I'm getting lost and can't think of a way to iterate from n = 1 to n in the searchList object array and include them in a LINQ
Is
p2=>searchList[0].ToString() != searchList2
permitted within LINQ and how can I create the Where part of the query?
Where clauses in LINQ are a Func<object, bool> object where object can be a generic if you have well-defined rules around them. The construction syntax is just a normal lambda. So:
Func<object, bool> where = obj => obj.Propery == delta;
return someEnumOrList.Where(where);
Wrapping this into a parameter lets you ad-hoc query against your items. You may need to provide an extension method, something like a WhereAll that takes an array of Func<> objects and checks them all. One idea for that would be:
public static IEnumerable<T> WhereAll(this IEnumerable<T> enumerable, Func<object, bool>[] clauses)
{
var result = enumerable;
foreach(var c in clauses)
{
result = result.Where(c);
}
return result;
}
I'm using System.Linq.Dynamic to allow me to dynamically select a list of fields from a query like this:
finalQuery = query.Select(string.Format("new({0})", string.Join(",", selectors)));
Where selectors is just a List<string> with all the fields I want. This works great, but this version of the extension method Select returns an IQueryable. Not this is not IQueryable<T>. If I have an IQueryable<T> I can simply do a .ToList() to convert it into a list and force the query to actually run on the database, but with the non-generic IQueryable, that method doesn't exists.
This is because ToList is inherited from IEnumerable<T> which IQueryable<T> inherits and IQueryable, obviously, doesn't.
So what's the most efficient way to get an IQueryable to execute the query and give me back a list? I can do this:
List<object> rtn = new List<object>();
foreach (var o in finalQuery)
{
rtn.Add(o);
}
But it seems like their ought to be an easier way.
Edit: In response to suggestions, I tried both:
finalQuery.Cast<object>().ToList();
and:
finalQuery.Cast<dynamic>().ToList();
Which both give NotSupportedExceptions with the message:
Unable to cast the type 'DynamicClass1' to type 'System.Object'. LINQ to Entities
only supports casting EDM primitive or enumeration types.
This appears to be a limitation in the way LINQ to Entities translates IQueryable.Cast with anonymous types. You can work around this by using it as an IEnumerable (your working example does this). This causes the code to do the cast in the .NET runtime after it's retrieved from the DB, instead of trying to handle it in the DB engine. E.g.
IEnumerable finalQuery = query.Select(string.Format("new({0})",
string.Join(",", selectors)));
var result = finalQuery.Cast<dynamic>().ToList();
Or
public static IList<T> CastToList<T>(this IEnumerable source)
{
return new List<T>(source.Cast<T>());
}
var finalQuery = query.Select(string.Format("new({0})",
string.Join(",", selectors)));
var result = finalQuery.CastToList<dynamic>();
I have an XElement with values for mock data.
I have an expression to query the xml:
Expression<Func<XElement, bool>> simpleXmlFunction =
b => int.Parse(b.Element("FooId").Value) == 12;
used in:
var simpleXml = xml.Elements("Foo").Where(simpleXmlFunction).First();
The design time error is:
The type arguments for method 'System.Linq.Enumerable.Where(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly'
The delegate supplied to Where should take in an XElement and return a bool, marking if the item matches the query, I am not sure how to add anything more to the delegate or the where clause to mark the type.
Also, the parallel method for the real function against the Entity Framework does not have this issue. What is not correct with the LINQ-to-XML version?
Don't make simpleXmlFunction an Expression<Func<XElement, bool>>. Make it a Func<XElement, bool>. That's what's expected as a delegate of .Where.
Func<XElement, bool> simpleXmlFunction =
new Func<XElement, bool>(b => int.Parse(b.Element("FooId").Value) == 12);
I think the full answer includes the previous answer, David Morton's comment, and an updated code snippet:
The .Where implementation for IQueryable is different than the .Where implementation for IEnumerable. IEnumerable.Where expects a:
Func<XElement, bool> predicate
You can compile the function from the expression you have by doing:
Expression<Func<XElement, bool>> simpleXmlExpression =
b => int.Parse(b.Element("FooId").Value) == 12;
Func<XElement, bool> simpleXmlFunction = simpleXmlExpression.Compile();
var simpleXml = xml.Elements("Foo").Where(simpleXmlFunction).First();
This will allow you to look at the expression tree generated and to use the compiled form to query the xml collection.