How to write condition based on expression? - linq

I am trying to write condition based on expression.
I have two classes Test1 and Test2.
public class Test1<TEntity, TProperty>
{
public IQueryable<TEntity> Test(
IQueryable<TEntity> queryable,
Expression<Func<TEntity, TProperty>> expression,
TProperty value
)
{
MemberExpression memberExpression = (MemberExpression)(expression.Body);
var propName = memberExpression.Member.Name;
// Cannot apply operator '>' to operands of type 'P' and 'P'
queryable = queryable.Where(e => EF.Property<TProperty>(e, propName) > value));
return queryable;
}
}
public class Test2<TEntity, TProperty>
where TProperty : IComparable
{
public IQueryable<TEntity> Test(
IQueryable<TEntity> queryable,
Expression<Func<TEntity, TProperty>> expression,
TProperty value
)
{
MemberExpression memberExpression = (MemberExpression)(expression.Body);
var propName = memberExpression.Member.Name;
// This one compiles
queryable = queryable.Where(e => EF.Property<TProperty>(e, propName).CompareTo(value) > 0);
return queryable;
}
}
First one (Test1) tries to compare values with > but it does not compile. Second one (Test2) declares generic type as IComparable and uses .CompareTo() method inside where condition. This one compiles but on runtime it throws:
The LINQ expression 'DbSet<SortableEntity>
.Where(s => s.IsDeleted == False)
.Where(s => EF.Property<long>((object)s, "Id").CompareTo((object)__value_0) > 0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()
Is it possible to somehow write custom condition based on expression? I want property/member expression to be passed by user and then decide what condition to apply to IQueryable.

Well, EF.Property is last thing that you have to use in such situation. You have property, even correct body - use this:
public class Test1<TEntity, TProperty>
{
public IQueryable<TEntity> Test(
IQueryable<TEntity> queryable,
Expression<Func<TEntity, TProperty>> expression,
TProperty value
)
{
var predicateLambda = Expression.Lambda<Func<TEntity, bool>>(
Expression.GreaterThan(expression.Body, Expression.Constant(value, typeof(TProperty))),
expression.Parameters[0]);
queryable = queryable.Where(predicateLambda);
return queryable;
}
}

Related

linq expression for ExecuteUpdateAsync

I have found ExecuteDeleteAsync and ExecuteUpdateAsync in EF Core 7 with great enthusiasm. They help to make my code much simpler and faster. There is no need to use self-made procedures for batch delete or update of 1-2 fields.
Anyway I have situations when the exact table and field of database for update should be selected in run time.
I can use the database table:
public static IQueryable<object> Set(this DbContext context, Type entity) =>
context.ClrQuery(context.ClrType(entity));
I have the method to make expression to filter rows:
public static IQueryable Where(this IQueryable source, string equalProperty, object value, [NotNull] Type EntityType)
{
PropertyInfo? property = EntityType.GetProperty(equalProperty);
if (property == null)
throw new NotImplementedException($"Type {EntityType.Name} does not contain property {equalProperty}");
ParameterExpression parameter = Expression.Parameter(EntityType, "r");
MemberExpression member = Expression.MakeMemberAccess(parameter, property);
LambdaExpression whereExpression = Expression.Lambda(Expression.Equal(member, Expression.Constant(value, property.PropertyType)), parameter);
MethodCallExpression resultExpression = WhereCall(source, whereExpression);
return source.Provider.CreateQuery(resultExpression);
}
So I can find the rows to make update using
IQueryable Source = db.Set(EntityType).Where(FieldName, FieldValue, EntityType);
I should make expression to update IQueryable ExecuteUpdateQuery = Source.ExecuteUpdateAsync(EntityType, FieldName, FieldValue);
What is the way to access to expression for SetProperty?
Try the following extensions. I have also corrected method signature:
var affected = anyQuery.ExecuteUpdate(FieldName, FieldValue);
var affected = await anyQuery.ExecuteUpdateAsync(FieldName, FieldValue, cancellationToken);
And implementation:
public static class DynamicRelationalExtensions
{
static MethodInfo UpdateMethodInfo =
typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdate));
static MethodInfo UpdateAsyncMethodInfo =
typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdateAsync));
public static int ExecuteUpdate(this IQueryable query, string fieldName, object? fieldValue)
{
var updateBody = BuildUpdateBody(query.ElementType, fieldName, fieldValue);
return (int)UpdateMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody });
}
public static Task<int> ExecuteUpdateAsync(this IQueryable query, string fieldName, object? fieldValue, CancellationToken cancellationToken = default)
{
var updateBody = BuildUpdateBody(query.ElementType, fieldName, fieldValue);
return (Task<int>)UpdateAsyncMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody, cancellationToken })!;
}
static LambdaExpression BuildUpdateBody(Type entityType, string fieldName, object? fieldValue)
{
var setParam = Expression.Parameter(typeof(SetPropertyCalls<>).MakeGenericType(entityType), "s");
var objParam = Expression.Parameter(entityType, "e");
var propExpression = Expression.PropertyOrField(objParam, fieldName);
var valueExpression = ValueForType(propExpression.Type, fieldValue);
// s.SetProperty(e => e.SomeField, value)
var setBody = Expression.Call(setParam, nameof(SetPropertyCalls<object>.SetProperty),
new[] { propExpression.Type }, Expression.Lambda(propExpression, objParam), valueExpression);
// s => s.SetProperty(e => e.SomeField, value)
var updateBody = Expression.Lambda(setBody, setParam);
return updateBody;
}
static Expression ValueForType(Type desiredType, object? value)
{
if (value == null)
{
return Expression.Default(desiredType);
}
if (value.GetType() != desiredType)
{
value = Convert.ChangeType(value, desiredType);
}
return Expression.Constant(value);
}
}

Concatenate strings with Procedures inside Expression in Linq-to-Entities

Let me start by asking, please don't answer "use AsEnumerable or ToList before", this would get the data into memory and then order. Since I intend to use the same code to apply filter dynamically, that would not be helpfull.
Having this class:
public class Employee {
public string Name;
public IEnumerable<string> Childs;
}
I need to be able to sort an IQueryable by Childs property.
Since I can't use string.Join directly, I was trying to make it dynamically using Expressions and combine it with a Stored Procedure that would return the names separeted by ",".
The problem is that I wasn't able to merge the procedure call inside the order expression.
The order expression that I'm using was taken from this:
Dynamic LINQ OrderBy on IEnumerable<T>
public static IOrderedQueryable<T> ApplyOrder<T>(this IQueryable<T> source, string propertyName, string methodName)
{
string[] properties = propertyName.Split('.');
Type type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type);
Expression expression = parameter;
PropertyInfo propertyInfo = null;
foreach (string property in properties)
{
propertyInfo = type.GetProperty(property, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (propertyInfo.PropertyType.GetInterfaces().Contains(typeof(IEnumerable)))
{
/*
The ideia was to call the procedure here and use it's value to order the source query
*/
}
else
{
expression = Expression.Property(expression, propertyInfo);
type = propertyInfo.PropertyType;
}
}
Type orderDelegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression orderLambda = Expression.Lambda(orderDelegateType, expression, parameter);
return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(method => method.Name == methodName && method.IsGenericMethodDefinition && method.GetGenericArguments().Length == 2 && method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, orderLambda });
}
Honestly I'm studying Expression for a week now and still have no idea where to begin with.

Change a LINQ expression predicate from one type to another

I have two unrelated classes. One is exposed as API, and the other is used internally by 3rd party API.
Entity is exposed from our API, while EntityProvider is from the 3rd party assembly.
class Entity
{
public A { get; set; }
}
class EntityProvider
{
public A { get; set; }
}
Consumers of our API will provide predicates of the form Expression <Func<Entity, bool>> and I need to modify it to Expression <Func<EntityProvider, bool>> so that I can pass the same to internal 3rd party assembly.
Please help with this conversion.
Since Expressions in .NET are immutable, the only way to do this is to rebuild the whole expression. To do this usually involves inheriting from the ExpressionVisitor class. Depending on the complexity of the expressions you have to convert this could be quite complicated.
This is a simple example of a visitor that will work with simple expressions( like x=>x.Someproperty == somevalue ). It's just an example to get you started and it's in no way finished or tested(it won't handle method calls in the expression for example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//Type from which to convert
public class A
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
//Type to which we want the Expression converted
public class B
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
//the expression we want to convert expresion
Expression<Func<A, bool>> expA = x => x.Property1 == 6 && x.Property2 == 3;
var visitor = new ParameterTypeVisitor<A,B>(expA);
var expB = visitor.Convert();
var b = new B() { Property1 = 6, Property2 = 3 };
//try the converted expression
var result = expB.Compile().Invoke(b);
}
}
public class ParameterTypeVisitor<TFrom,TTo> : ExpressionVisitor
{
private Dictionary<string, ParameterExpression> convertedParameters;
private Expression<Func<TFrom, bool>> expression;
public ParameterTypeVisitor(Expression<Func<TFrom,bool>> expresionToConvert )
{
//for each parameter in the original expression creates a new parameter with the same name but with changed type
convertedParameters = expresionToConvert.Parameters
.ToDictionary(
x => x.Name,
x => Expression.Parameter(typeof (TTo), x.Name)
);
expression = expresionToConvert;
}
public Expression<Func<TTo,bool>> Convert()
{
return (Expression<Func<TTo, bool>>)Visit(expression);
}
//handles Properties and Fields accessors
protected override Expression VisitMember(MemberExpression node)
{
//we want to replace only the nodes of type TFrom
//so we can handle expressions of the form x=> x.Property.SubProperty
//in the expression x=> x.Property1 == 6 && x.Property2 == 3
//this replaces ^^^^^^^^^^^ ^^^^^^^^^^^
if (node.Member.DeclaringType == typeof(TFrom))
{
//gets the memberinfo from type TTo that matches the member of type TFrom
var memeberInfo = typeof (TTo).GetMember(node.Member.Name).First();
//this will actually call the VisitParameter method in this class
var newExp = Visit(node.Expression);
return Expression.MakeMemberAccess(newExp, memeberInfo);
}
else
{
return base.VisitMember(node);
}
}
// this will be called where ever we have a reference to a parameter in the expression
// for ex. in the expression x=> x.Property1 == 6 && x.Property2 == 3
// this will be called twice ^ ^
protected override Expression VisitParameter(ParameterExpression node)
{
var newParameter = convertedParameters[node.Name];
return newParameter;
}
//this will be the first Visit method to be called
//since we're converting LamdaExpressions
protected override Expression VisitLambda<T>(Expression<T> node)
{
//visit the body of the lambda, this will Traverse the ExpressionTree
//and recursively replace parts of the expression we for which we have matching Visit methods
var newExp = Visit(node.Body);
//this will create the new expression
return Expression.Lambda(newExp,convertedParameters.Select(x=>x.Value));
}
}

Transform Expression m => m.Name to m => m[index].Name

Given an Expression<Func<T, TValue>> (like m => m.Name) and an index, I'd like to be able to transform my expression to m => m[index].Name). And I must admit I'm stuck...
I give you the actual scenario if you want the "Why the hell" (and maybe find a better way).
Scenario : imagine a Server Side Editable Grid (without javascript).
I build my grid with an helper which look like that :
#(Html.Grid(Model)
.Columns(columns => {
columns.Edit(m => m.Name);
columns.Edit(m => m.Code);
})
.AsEditable());
Model is an IQueryable<T>
m => m.Name is an Expression<Func<T, TValue>> (TValue is string)
m => m.Code is an Expression<Func<T, TValue>> (TValue is int)
When rendering my view, I'd like to display an html form.
The IQueryable<T> is enumerated (order, pagination). => ok
So I'll have a List<T> of 5, 10 or 20 T items.
And Name and Code should be represented as TextBox, using a classic HtmlHelper.TextBoxFor(Expression<Func<T, TValue>>) (no problem to create the HtmlHelper<T>)
But as I've got a list, if I want correct Model binding, I can't use directly m => m.Name, but should use m => m[indexOfItem in List<T>].Name
Edit : more details :
So let's say we have an entity class
public class Test {
public int Id {get;set;}
public string Name {get;set;}
public string Code {get;set;}
}
Then, a method retrieving an IQueryable<Test>
Then a view
#model IQueryable<Test>
#(Html.Grid(Model)
.Columns(columns => {
columns.Edit(m => m.Name);
columns.Edit(m => m.Code);
})
.AsEditable());
as you see, Model given as parameter is IQueryable<Test>.
m => m.Name
and
m => m.Code
are just properties of the Model (which I wanna display as TextBox in my grid).
The model is an IQueryable<T> (not an IEnumerable<T>), because the Grid manages ordering and Pagination, so that my controller and service layer don't need to know about pagination and ordering.
Is it clearer ?
This could be easily achieved by writing a custom ExpressionVisitor:
public static class ExpressionExtensions
{
private class Visitor : ExpressionVisitor
{
private readonly int _index;
public Visitor(int index)
{
_index = index;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return Expression.ArrayIndex(GetArrayParameter(node), Expression.Constant(_index));
}
public ParameterExpression GetArrayParameter(ParameterExpression parameter)
{
var arrayType = parameter.Type.MakeArrayType();
return Expression.Parameter(arrayType, parameter.Name);
}
}
public static Expression<Func<T[], TValue>> BuildArrayFromExpression<T, TValue>(
this Expression<Func<T, TValue>> expression,
int index
)
{
var visitor = new Visitor(index);
var nexExpression = visitor.Visit(expression.Body);
var parameter = visitor.GetArrayParameter(expression.Parameters.Single());
return Expression.Lambda<Func<T[], TValue>>(nexExpression, parameter);
}
}
and then you could use this extension method like this:
Expression<Func<Test, string>> ex = m => m.Code;
Expression<Func<Test[], string>> newEx = ex.BuildArrayFromExpression(1);
Well, got something (ugly, just for testing purpose) working, but it makes things unclear, so I'll wait for a better solution or build my input another way :
public static Expression<Func<T[], TValue>> BuildArrayFromExpression<T, TValue>(this Expression<Func<T, TValue>> expression, int index)
{
var parameter = Expression.Parameter(typeof(T[]), "m");
Expression body = Expression.ArrayIndex(parameter, Expression.Constant(index));
var type = typeof(T);
var properties = expression.Body.ToString().Split('.');//ugly shortcut for test only
foreach (var property in properties.Skip(1))
{
var pi = type.GetProperty(property);
body = Expression.Property(body, type.GetProperty(property));
type = pi.PropertyType;
}
return Expression.Lambda<Func<T[], TValue>>(body, parameter);
}
used in a RenderMethod, called for each line of my List<T> (the paginated /ordered list returned from my IQueryable<T>)
public override ... RenderContent(T dataItem) {
var helper = new HtmlHelper<T[]>(GridModel.Context, new GridViewDataContainer<T[]>(GridModel.PaginatedItems.ToArray(), GridModel.Context.ViewData));
var modifiedExpression = Expression.BuildArrayFromExpression(GridModel.PaginatedItems.IndexOf(dataItem));//GridModel.PaginatedItems is the List<T> returned when "executing" the IQueryable<T>
var textBox = helper.TextBoxFor(modifiedExpression);
...
}

Dynamic Order (SQL ORDERBY) in LINQ CompiledQuery

how can I create a dynamic ORDERBY in my LINQ CompiledQuery (e.g. supply Order Field and Direction as parameters for the compiled query)?
I would do it this way, first all you really need is a way to access the property value by string on an object. You could use reflection, but its slow. So use this helper class approach which is based on the tests of http://stefan.rusek.org/Posts/LINQ-Expressions-as-Fast-Reflection-Invoke/3/
public static class LINQHelper
{
public static IComparable OrderByProperty<TClass>(TClass item,
string propertyName)
{
var t = Expression.Parameter(typeof(TClass), "t");
var prop = Expression.Property(t, propertyName);
var exp = Expression.Lambda(prop, t).Compile();
return (IComparable)exp.DynamicInvoke(item);
}
}
The in your code where you want your order by string of property name, in this example col1, you just do the following.
var myQuery = from i in Items
select i;
myQuery.OrderBy(i=>LINQHelper.OrderByProperty(i,"col1"));
Hope this helps.
I think I found it:
Check out this link. It will point you to the VS2008 code samples which contains a Dynamic Linq Query Library which contains the extension method below. This will allow you to go:
Object.OrderBy("ColumnName");
Here is the extension methods, but you may want the whole library.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}
public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
if (source == null) throw new ArgumentNullException("source");
if (ordering == null) throw new ArgumentNullException("ordering");
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "") };
ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings) {
queryExpr = Expression.Call(
typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, o.Selector.Type },
queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}

Resources