I am constructing an IQueryable query using several methods. The methods are somehow complex, but the problem I want to solve can be extracted and simplified as follows. I have two methods
private Expression<Func<T, bool>> CreateExpressionA(string ValueA)
{
return a => a.PropertyA.ToLower() == ValueA;
}
private Expression<Func<T, bool>> CreateExpressionB(string ValueB)
{
return a => a.PropertyB.ToLower() == ValueB;
}
and what I would rather have is this:
private Expression<Func<T, bool>> CreateExpression(??? Selector, string Value)
{
return a => a.Selector.ToLower() == Value;
}
or a similar approach that would allow me to avoid having two same methods with the only difference being in what property of an object is being used there.
Is it possible to do this in some elegant way?
You can pass in a selector Func that returns a string property:
private Expression<Func<T, bool>> CreateExpression<T>(Func<T, string> selector, string value)
{
return a => selector(a).ToLower() == value;
}
Usage:
CreateExpression<MyType>(x => x.PropertyA, "thevalue");
You could use reflection, more precisely the class PropertyInfo as an argument, but the implementation would be more involved. The method could be implemented as follows.
private Expression<Func<T, bool>> CreateExpression(PropertyInfo iInfo, string Value)
{
return a => (iInfo.GetPropertyValue(a) as string).ToLower() == ValueB;
}
However, note that this will work only if the type of the property is string, otherwise an additional type parameter could be used.
What you can do is create the Expression from scratch:
private Expression<Func<T, bool>> CreateExpression(string propertyName, string value) {
ParameterExpression param = Expression.Parameter(typeof(T));
MemberExpression property = Expression.Property(param, propertyName);
var valExpr = Expression.Constant(value);
var body = Expression.Equal(property, valExpr);
return Expression.Lambda<Func<T, bool>>(body, param);
}
Call with:
var expression = CreateExpression<TypeOfA>("PropertyA", "ValueForPropertyA");
It's a bit off the top of my head, but I think this should at least get you started. Let me know if you need help.
Related
Before I forget it, my execution context, I'm using .Net 5 with the packages:
Microsoft.EntityFrameworkCore.Design 5.0.6
Microsoft.EntityFrameworkCore.Relational 5.0.6
MySql.EntityFrameworkCore 5.0.3.1
My main goal was to remove the repetitive task of doing expressions when I need to retrieve entities, something like:
public class GetListEntity
{
property int QueryProperty { get; set }
}
public class Entity
{
property int Property { get; set }
}
public async Task<ActionResult> List(GetListEntity getListEntity)
{
var restrictions = new List<Expression<Func<Entity>
if (model.QueryProperty != null)
{
restrictions.Add(e => e.Property == model.QueryProperty);
}
nonTrackedQueryableEntities = this.dbContext.Set<Entity>()
.AsNoTracking();
var expectedEntity = restrictions.Aggregate((sr, nr) => sr.And(nr)); //The And method is below as an extension
var expectedNonTrackedQueryableEntities = nonTrackedQueryableEntities.Where(expectedEntity);
// I will get the total first because the API was meant to paginate the responses.
var total = await expectedNonTrackedQueryableEntities.CountAsync();
}
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression)
{
return selfExpression.Compose(otherExpression, Expression.OrElse);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression)
{
return selfExpression.Compose(otherExpression, Expression.AndAlso);
}
private static InvocationExpression Casting<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression)
{
return Expression.Invoke(otherExpression, selfExpression.Parameters.Cast<Expression>());
}
private static Expression<Func<T, bool>> Compose<T>(this Expression<Func<T, bool>> selfExpression, Expression<Func<T, bool>> otherExpression, Func<Expression, Expression, Expression> merge)
{
var invocationExpression = selfExpression.Casting(otherExpression);
return Expression.Lambda<Func<T, bool>>(merge(selfExpression.Body, invocationExpression), selfExpression.Parameters);
}
}
I've managed to achieve what I wanted but let's say... partially, because if I try to Query the Database at least two times in a row I get this exception:
System.ArgumentException: An item with the same key has already been added. Key: e
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareLambda(LambdaExpression a, LambdaExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareBinary(BinaryExpression a, BinaryExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareLambda(LambdaExpression a, LambdaExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareUnary(UnaryExpression a, UnaryExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.Equals(Expression x, Expression y)
at Microsoft.EntityFrameworkCore.Query.CompiledQueryCacheKeyGenerator.CompiledQueryCacheKey.Equals(CompiledQueryCacheKey other)
at Microsoft.EntityFrameworkCore.Query.RelationalCompiledQueryCacheKeyGenerator.RelationalCompiledQueryCacheKey.Equals(RelationalCompiledQueryCacheKey other)
at MySql.EntityFrameworkCore.Query.Internal.MySQLCompiledQueryCacheKeyGenerator.MySQLCompiledQueryCacheKey.Equals(MySQLCompiledQueryCacheKey other)
at MySql.EntityFrameworkCore.Query.Internal.MySQLCompiledQueryCacheKeyGenerator.MySQLCompiledQueryCacheKey.Equals(Object obj)
at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValue(TKey key, TValue& value)
at Microsoft.Extensions.Caching.Memory.MemoryCache.TryGetValue(Object key, Object& result)
at Microsoft.Extensions.Caching.Memory.CacheExtensions.TryGetValue[TItem](IMemoryCache cache, Object key, TItem& value)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)'
Following the trace I managed to discover that the ORM is caching for some reason my expressions (and putting the parameter name, in this case 'e') and failing to detect a key collision the second time it has a similar expression to query the database. I said for some reason because, it's not the main deal but at least is odd that cache is involved in a non tracked query, maybe I'm missing something in the middle.
To undenrstand how i got here i will put the code below.
First an interface to implement in every model related with querying a list of entities and expose the extension method ListRestrictions (almost at the bottom).
public interface IEntityFilter<TEntity>
{
}
The next step was to define Attributes to summarize the action to do with the property and generate a partial expression to use in the extension method:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class FilterByPropertyAttribute : Attribute
{
protected string FirstPropertyPath { get; }
protected IEnumerable<string> NPropertyPath { get; }
public FilterByPropertyAttribute(string firstPropertyPath, params string[] nPropertyPath)
{
this.FirstPropertyPath = firstPropertyPath;
this.NPropertyPath = nPropertyPath;
}
protected MemberExpression GetPropertyExpression(ParameterExpression parameterExpression)
{
var propertyExpression = Expression.Property(parameterExpression, this.FirstPropertyPath);
foreach (var propertyPath in this.NPropertyPath)
{
propertyExpression = Expression.Property(propertyExpression, propertyPath);
}
return propertyExpression;
}
public abstract Expression GetExpression(ParameterExpression parameterExpression, object propertyValue);
}
And to avoid comparisons with nullable structs
public abstract class NonNullableValuePropertyFilterAttribute : FilterByPropertyAttribute
{
public NonNullableValuePropertyFilterAttribute(string firstPropertyPath, params string[] nPropertyPath)
: base(firstPropertyPath, nPropertyPath)
{
}
public override Expression GetExpression(ParameterExpression parameterExpression, object propertyValue)
{
var propertyExpression = this.GetPropertyExpression(parameterExpression);
return this.GetExpression(propertyExpression, this.GetConvertedConstantExpression(propertyExpression, Expression.Constant(propertyValue)));
}
protected abstract Expression GetExpression(MemberExpression memberExpression, UnaryExpression unaryExpression);
private UnaryExpression GetConvertedConstantExpression(MemberExpression memberExpression, ConstantExpression constantExpression)
{
var convertedConstantExpression = Expression.Convert(constantExpression, memberExpression.Type);
return convertedConstantExpression;
}
}
An Attribute with a defined role would be:
public class EqualPropertyFilterAttribute : NonNullableValuePropertyFilterAttribute
{
public EqualPropertyFilterAttribute(string firstPropertyPath, params string[] nPropertyPath)
: base(firstPropertyPath, nPropertyPath)
{
}
protected override Expression GetExpression(MemberExpression memberExpression, UnaryExpression unaryExpression)
{
return Expression.Equal(memberExpression, unaryExpression);
}
}
And last, the extension itself:
public static class EntityFilterExtensions
{
public static List<Expression<Func<TEntity, bool>>> ListRestrictions<TEntity>(this IEntityFilter<TEntity> entityFilter)
{
var entityFilterType = entityFilter.GetType();
var propertiesInfo = entityFilterType.GetProperties()
.Where(pi => pi.GetValue(entityFilter) != null
&& pi.CustomAttributes.Any(ca => ca.AttributeType
.IsSubclassOf(typeof(FilterByPropertyAttribute))));
var expressions = Enumerable.Empty<Expression<Func<TEntity, bool>>>();
if (propertiesInfo.Any())
{
var entityType = typeof(TEntity);
var parameterExpression = Expression.Parameter(entityType, "e");
expressions = propertiesInfo.Select(pi =>
{
var filterByPropertyAttribute = Attribute.GetCustomAttribute(pi, typeof(FilterByPropertyAttribute)) as FilterByPropertyAttribute;
var propertyValue = pi.GetValue(entityFilter);
var expression = filterByPropertyAttribute.GetExpression(parameterExpression, propertyValue);
return Expression.Lambda<Func<TEntity, bool>>(expression, parameterExpression);
});
}
return expressions.ToList();
}
}
A usage would be:
public class GetListEntity : IEntityFilter<Entity>
{
[EqualPropertyFilter(nameof(Entity.Property))]
property int QueryProperty { get; set }
}
public class Entity
{
property int Property { get; set }
}
public async Task<ActionResult> List(GetListEntity getListEntity)
{
var restrictions = getListEntity.ListRestrictions();
nonTrackedQueryableEntities = this.dbContext.Set<Entity>()
.AsNoTracking();
var expectedEntity = restrictions.Aggregate((sr, nr) => sr.And(nr));
var expectedNonTrackedQueryableEntities = nonTrackedQueryableEntities .Where(expectedEntity);
// I will get the total first because the API was meant to paginate the responses.
var total = await expectedNonTrackedQueryableEntities.CountAsync();
}
And to be discarded, if I Aggregate a non dynamic expression of a list of expressions, the ORM works fine, when I do it with the dynamic ones I get the exception at the beginning.
I found a workaround, changing in the extension method this line:
var parameterExpression = Expression.Parameter(entityType, "e");
For this one:
var parameterExpression = Expression.Parameter(entityType, $"{entityType.Name}{entityFilter.GetHashCode()}");
I wanna know why this happens and maybe if there is another way to fix it.
I posted here before opening a thread in any Github repository because I'm still curious if is a fault of mine for missing something in the way or a bug.
From the explanations it was pretty clear that there is some issue with ParameterExpressions of the dynamically built predicates. And at the end it was in the one of the custom expression extension methods used.
While technically it could be considered ORM bug/issue, they have to solve very complex things during the expression tree transformation, so we must be tolerant and fix our code when possible.
There are some important things you need to be aware of when building dynamically query expression trees.
First, the name of the used ParameterExpressions doesn't matter - they are identified by reference. It's perfectly fine to have all parameters with one and the same name (something that C# compiler won't allow you to create at compile time) as soon as they are separate instances properly referenced by the other expressions.
Second, some things which make sense when creating expression trees for compiling and executing as code (like in LINQ to Objects) are not good for expression trees which are supposed to be transformed to something else (they are valid, but make the transformation harder and lead to bugs/issues). Specifically (what was causing the issue in question) is "calling" lambda expressions. Yes, there is a dedicated Expression.Invoke, but it is causing issues with almost all IQueryable implementations, so it is better to emulate it by "inlining" it, which means replacing parameter instances inside the body with actual expressions.
Here is the modified version of your ExpressionExtensions class applying the aforementioned principle:
public static partial class ExpressionExtensions
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Combine(left, right, ExpressionType.AndAlso);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
=> Combine(left, right, ExpressionType.OrElse);
private static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right, ExpressionType type)
{
if (left is null) return right;
if (right is null) return left;
bool constValue = type == ExpressionType.AndAlso ? false : true;
if ((left.Body as ConstantExpression)?.Value is bool leftValue)
return leftValue == constValue ? left : right;
if ((right.Body as ConstantExpression)?.Value is bool rightValue)
return rightValue == constValue ? right : left;
return Expression.Lambda<Func<T, bool>>(Expression.MakeBinary(type,
left.Body, right.Invoke(left.Parameters[0])),
left.Parameters);
}
public static Expression Invoke<T, TResult>(this Expression<Func<T, TResult>> source, Expression arg)
=> source.Body.ReplaceParameter(source.Parameters[0], arg);
}
which uses the following little helpers for parameter replacing:
public static partial class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression source, ParameterExpression parameter, Expression value)
=> new ParameterReplacer { Parameter = parameter, Value = value }.Visit(source);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Parameter;
public Expression Value;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Parameter ? Value : node;
}
}
As confirmed in the comments, this solves the issue in question.
Now, unrelated, but as a bonus. Another thing which makes sense for expressions supposed to be compiled is the usage of ConstantExpressions - they are evaluated once and then used in potentially many places.
However for expression trees which are supposed to be transformed to SQL or similar, using ConstantExpressions makes each query different, thus non cacheable. For performance reasons, it is better to use expression type which is treated as variable, thus allowing the cache the transformation and parameterizing the generated SQL query, so both client and database query processors can reuse the "compiled" query/execution plan.
Doing so is quite easy. It does not require changing the type of the predicate or the way you generate. All you need is to replace the ConstantExpression with member (property/field) of a ConstantExpression. In your case it's a matter of replacing
var propertyValue = pi.GetValue(entityFilter);
with
var propertyValue = Expression.Property(Expression.Constant(entityFilter), pi);
and of course adjusting the signatures/implementation (in general try to not use specific expression types if they are not essential for the method), e.g.
FilterByPropertyAttribute class:
public abstract Expression GetExpression(ParameterExpression parameter, Expression value);
NonNullableValuePropertyFilterAttribute class:
public override Expression GetExpression(ParameterExpression parameter, Expression value)
{
var property = this.GetPropertyExpression(parameter);
if (value.Type != property.Type)
value = Expression.Convert(value, property.Type);
return this.GetExpression(property, value);
}
protected abstract Expression GetExpression(MemberExpression member, Expression value);
EqualPropertyFilterAttribute class:
protected override Expression GetExpression(MemberExpression member, Expression value)
=> Expression.Equal(member, value);
All other things, including the usage remain the same. But the result would be nicely parameterized query as if it was created at compile time.
I'm building Linq Extension methods.
Shortly, I've built an extension method in order to create a MemberExpression looks like:
public static Expression Field<T>(this object entity, string field)
{
Type entityType = entity.GetType();
PropertyInfo propertyInfo = entityType.GetProperty(field);
if (propertyInfo == null)
throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");
return Expression.Property(parameterExpression, propertyInfo);
}
So, I'm able to do that:
IEnumerable<C> classes = this.backend.cs.Where(
c => c.Field<C>("Matter").EndsWith(string.Empty)<<<<<<<< Compilation error.
);
Since MemberExpression have not EndsWith method, I'm not able to extend this MemberExpression like a String property access like:
IEnumerable<C> classes = this.backend.cs.Where(
c => c.Matter.EndsWith(string.Empty)
);
Is there some way to do that.
As you are able to figure out I'm trying to get something a bit more complex, Nevertheless, this example is for explaining the situation.
I hope it's enought clear.
Scope
My UI is using a backend.
This backend have three implementations. Each one of them provides a Linq implementation (Linq collections, NHibernate, custom-made Linq provider).
So, my UI is able to work on collections, a database or getting data from our server.
I'd like to provide util extension methods like AnyField().
So, after digging a bit I'm thinking on two approaches:
AnyField() generates an expression tree which is able to be translated by every Linq provider (first answer of this post).
Provide a default implementation of Anyfield() for Linq Collections, and then use each Linq provider extension mechanism for handle it. Or, if you are building a Linq Provider, support it on implementation.
Okay, so you're getting tripped up on the syntactic sugar that C# provides for you when building ExpressionTrees
Where expects Expression<Func<TObjectType, TReturnType>> or a compiled lambda; Func<TObjectType, TReturnType>.
Your method Field currently only returns an untyped Expression. That means your query is actually returning Expression<Func<TObjectType, Expression>>. That's not right! It should be returning a Expression<Func<TObjectType, string>>! But how do we do that? That would mean our method would have to return a string, but we want to build an expression tree.
To get it working as you're expecting, it's quite a bit more difficult than you would imagine, but that's only because we're so spoiled with the syntactic sugar.
What we actually need to do is write methods which accept lambda methods, and return lambda methods, each one re-writing the body a little bit.
So... what does that look like?
public static Expression<Func<TElementType, object>> Field<TElementType, TReturnType>(this Expression<Func<TElementType, TReturnType>> expr, string field)
{
Type entityType = expr.Body.Type;
PropertyInfo propertyInfo = entityType.GetProperty(field);
if (propertyInfo == null)
throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");
return Expression.Lambda<Func<TElementType, object>>(
Expression.Property(parameterExpression, propertyInfo),
parameterExpression
);
}
Notice that it's almost the exact same as what you wrote, but we wrap it with Lambda<Func<TElementType, TReturnType>>. And the signature is a bit different too.
Instead of operating on an object, we want to operate on a lambda expression. We also return a lambda expression.
So how do we use it?
var classes = objects.Where(
ExpressionExtensions.Field<Test, Test>(q => q, "Matter")
);
Great! Now we're passing Expression<Func<Test, string>> to Where, rather than Expression<Func<Test, MemberExpression>>. Making progress.
But that won't compile, and rightly so. We're returning a string, but we're using a filtering method, which requires a bool.
So let's now write EndsWith:
public static Expression<Func<T, bool>> EndsWith<T, TReturnType>(this Expression<Func<T, TReturnType>> expr, string str)
{
var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var newBody = Expression.Call(expr.Body, endsWithMethod, Expression.Constant(str));
var result = Expression.Lambda<Func<T, bool>>(newBody, expr.Parameters);
return result;
}
And using it:
var classes = objects.Where(
ExpressionExtensions.Field<Test, Test>(q => q, "Matter")
.EndsWith("A")
);
Which is now compiling! And the expression tree looks like this:
UserQuery+Test[].Where(e => e.Matter.EndsWith("A"))
That's not too pretty, having Field take a redundant lambda, though. Let's add a helper method to make it look prettier:
public static Expression<Func<TElementType, TElementType>> Query<TElementType>(this Expression<Func<TElementType, TElementType>> expr)
{
return expr;
}
Putting it all together:
void Main()
{
var objects = new[] { new Test { Matter = "A" } }.AsQueryable();
var classes = objects.Where(
ExpressionExtensions.Query<Test>(q => q)
.Field("Matter")
.EndsWith("A")
);
classes.Expression.Dump();
}
public class Test
{
public string Matter { get; set;}
}
public static class ExpressionExtensions
{
public static Expression<Func<TElementType, TElementType>> Query<TElementType>(this Expression<Func<TElementType, TElementType>> expr)
{
return expr;
}
public static Expression<Func<TElementType, object>> Field<TElementType, TReturnType>(this Expression<Func<TElementType, TReturnType>> expr, string field)
{
Type entityType = expr.Body.Type;
PropertyInfo propertyInfo = entityType.GetProperty(field);
if (propertyInfo == null)
throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
ParameterExpression parameterExpression = Expression.Parameter(entityType, "e");
return Expression.Lambda<Func<TElementType, object>>(
Expression.Property(parameterExpression, propertyInfo),
parameterExpression
);
}
public static Expression<Func<T, bool>> EndsWith<T, TReturnType>(this Expression<Func<T, TReturnType>> expr, string str)
{
var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var newBody = Expression.Call(expr.Body, endsWithMethod, Expression.Constant(str));
var result = Expression.Lambda<Func<T, bool>>(newBody, expr.Parameters);
return result;
}
}
I don't know if you mess the code to show a piece of code or if it was intended, but you have a generic extension that wants A T but you don't use it
Anyway if what you want is a method that returns you the value of a property, why don't you do a static exception that return T ?
public static class EntityExtension {
public static T Field<T>(this object entity, string field) {
Type entityType = entity.GetType();
PropertyInfo propertyInfo = entityType.GetProperty(field);
if (propertyInfo == null) {
throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
}
return (T)propertyInfo.GetValue(entity);
}
}
this is a fiddle i've done to show you the usage, pretty simple
https://dotnetfiddle.net/PoSfli
posting the code too in case fiddle get lost:
using System;
using System.Reflection;
using System.Linq.Expressions;
public class Program
{
public static void Main()
{
YourClass c = new YourClass() {
PropA = 1,
PropB = 2,
PropC = "ciao"
};
var propBValue = c.Field<int>("PropB");
Console.WriteLine("PropB value: {0}", propBValue);
var propCValue = c.Field<string>("PropC");
Console.WriteLine("PropC value: {0}", propCValue);
}
}
public static class EntityExtension {
public static T Field<T>(this object entity, string field) {
Type entityType = entity.GetType();
PropertyInfo propertyInfo = entityType.GetProperty(field);
if (propertyInfo == null) {
throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
}
return (T)propertyInfo.GetValue(entity);
}
}
public class YourClass {
public int PropA { get; set; }
public int PropB { get; set; }
public string PropC { get; set; }
}
nota that you can improve a lot, using a typed extension and a property expression as argument instead of a string
You can also do something really simple like if you want to use the property name:
IEnumerable<C> classes = this.backend.cs.Where(
c => c.Field<C>("Matter").ToString().EndsWith(string.Empty)
Or if your are filtering by property type:
IEnumerable<C> classes = this.backend.cs.Where(
c => c.Field<C>("Matter").Type.ToString().EndsWith(string.Empty)
How about something like this? Actually, your generic approach is not of use right now.
public static bool Evaluate<TField>(this object entity, string fieldName, Predicate<TField> condition)
{
Type entityType = entity.GetType();
PropertyInfo propertyInfo = entityType.GetProperty(field);
if (propertyInfo == null)
throw new ArgumentException(string.Format("{0} doesn't exist on {1}", field, entityType.Name));
var value = (TField)propertyInfo.GetValue(entity); //read the value and cast it to designated type, will raise invalid cast exception, if wrong
return condition.Invoke(value); //invoke the predicate to check the condition
}
Usage would be then.
.Where(item => item.Evaluate<string>("Matter", prop => prop.EndsWith(string.Empty))
You can add a new extension method which returns your desired type.
public static T Compile<T>(this Expression expression)
{
return Expression.Lambda<Func<T>>(expression).Compile()();
}
In your statement you just have to add .Compile<type>()
IEnumerable<C> classes = this.backend.cs.Where(
c => c.Field<C>("Matter").Compile<string>().EndsWith(string.Empty));
I have this function:
public List<T> Find(Expression<Func<T, bool>> query)
{
}
Find(x => x.Id == 4);
Inside the method Find I want to chain And Condition.
something like:
query.And(x => x.Secured == false);//Secured is a memeber inside T like Id.
Your problem is that you want to access a member of T within the generic method. T could be anything at this point so the compiler will not let you access Secured since T may not have a Secured member.
You could cast T to dynamic, but this just changes a compile time error to a runtime error (plus it's horrible).
The best way would be to ensure T implements some known interface that has a Secured member.
public List<T> Find(Expression<Func<T, bool>> query) where T : ISecured
The expression must be "opened" and rebuilt, like this:
public List<T> Find<T>(Expression<Func<T, bool>> query)
{
ParameterExpression parameter = query.Parameters[0];
Expression body = query.Body;
MemberExpression property = Expression.Property(parameter, "Secured");
body = Expression.AndAlso(body, Expression.Not(property));
Expression<Func<T, bool>> query2 = Expression.Lambda<Func<T, bool>>(body, parameter);
// Now you can use query2
return null;
}
Note that I'm considering this x.Secured == false to be equivalent to !x.Secured. Clearly Secured could be a strange class that overloads the == operator, but I'll ignore that case.
As suggested by #Ralf, you could even simply do two .Where. like:
public List<T> Find<T>(Expression<Func<T, bool>> query)
{
ParameterExpression parameter = query.Parameters[0];
MemberExpression property = Expression.Property(parameter, "Secured");
Expression<Func<T, bool>> query2 = Expression.Lambda<Func<T, bool>>(Expression.Not(property), parameter);
return context.Set<T>
.Where(query)
.Where(query2)
.ToList();
}
(I'm using as an example context.Set<T>, that is very similar to what you would do if you are using Entity Framework, but in general nearly all the IQuerable<>/IEnumerable<> treat two .Where() like a single .Where() with an && condition)
Something like
Find(x => x.Id == 4 && x.Secured == false);
so I want to make filtering work automaticly based on some easy settings. The code I have is this:
public ActionResult Index() // here I want to add filtering for Status I only want to show the active ones
{
IQueryable<Ticket> cases = db.Cases().AsQueryable();
cases = cases.EnablePaging().EnableFilterFor(x => x.Status);
return View(cases);
}
EnableFilterFor looks like this:
public static IQueryable<T> EnableFilterFor<T>(this IQueryable<T> queryable, Expression<Func<T, string>> keySelector)
{
string filterValue= "Active";
//Expression<Func<T, bool>> whereexpresion = keySelector.Compile() == "Active"
queryable = queryable.Where(
//here do the magic !! so that the result will be 'x=>x.Status == filterValue');
);
return queryable;
}
I googled a lot, tried many different things but no success. I somehow have to combine the keySelector and the filterValue to work (I need an Expression for the Where method to work). Any help would be greatly appreciated.
EDIT: After testing both solutions (thank you both!) I found out that Poke had the best one. Poke his code is the only code that doesn't change the way that the SQL is generated. When I took a look at Servy his generated SQL it always did an EXTRA Sql select query and an EXTRA and in the WHERE clause... No idea why :)
IQueryable.Where requires an Expression<Func<T, bool>>, so that will be the thing we need to build. As we want to integrate something from another expression (a Expression<Func<T, string>>), we have to build the expression “by hand”.
So in the end, we want to call LambdaExpression.Lambda<Func<T, bool>>(…) to get our expression for Where, but we need to fill in the expression body:
// first, we reuse the parameter from the `keySelector` expression
ParameterExpression param = keySelector.Parameters[0];
// The body is now just an equality comparison of the `keySelector`
// body, and the constant `filterValue`
Expression body = Expression.Equal(keySelector.Body, Expression.Constant(filterValue));
// now we just need to create a lambda expression for that body with the
// saved parameter and it’s all done:
queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(body, param));
What we'll need here is a Compose method, for expressions. It'll take an expression that uses a value, and another expression that conceptually will use the result of the first expression as its input, generating a new output.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
It will require the ability to replace one expression with another, which we can do using the following:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now we can write:
public static IQueryable<T> EnableFilterFor<T>(
this IQueryable<T> queryable,
Expression<Func<T, string>> keySelector)
{
string filterValue= "Active";
return queryable.Where(keySelector.Compose(status => status == filterValue));
}
I am not sure how to create this expression:
(e => e.Prop1, e.Prop2, e.Prop3)
I want to pass an expression like this into a method so I can iterate through the collection and pull out the property names.
pseudo method:
public void ParseProperties<T>(The_Expression_being_Passed_In)
{
foreach(var member in expression.members)
{
.....
}
}
You need to accept a parameter of type
params Expression<Func<T, object>>[] expressions
So you can say
public void ParseProperties<T>(params Expression<Func<T, object>>[] expressions) {
foreach(var expression in expressions) {
expression.GetPropertyName();
}
}
You'll have to write GetPropertyName; implementations abound on StackOverflow.
You can't quite call it like you desire. You'll have to say
ParseProperties(e => e.Prop1, e => e.Prop2, e => e.Prop3);
you could try something like this, but it may blow up on you if you try to use a value type property as the expression will most likely be a cast expression.
public PropertyInfo ParseProperty<T>(Expression<Func<T,object>> expr)
{
MemberExpression member;
if(expr == null)
throw new ArgumentNullException("expr");
if((member = expr.Body as MemberExpression) == null)
throw new ArgumentException("incorrect format");
return member.Member as PropertyInfo;
}
public PropertyInfo[] ParseProperties<T>(params Expression<Func<T,object>> expressions)
{
return Array.ConvertAll(expressions,ParseProperty);
}