How do I dynamically create an ExpressionFuncMyClass, bool predicate from ExpressionFuncMyClass, string?
It's hard to mix compiler-generated expression trees and hand-made ones, precisely because of this sort of thing - extracting out the ParameterExpressions is tricky. So let's start from scratch:
ParameterExpression argParam = Expression.Parameter(typeof(Service), "s");
Expression nameProperty = Expression.Property(argParam, "Name");
Expression namespaceProperty = Expression.Property(argParam, "Namespace");
var val1 = Expression.Constant("Modules");
var val2 = Expression.Constant("Namespace");
Expression e1 = Expression.Equal(nameProperty, val1);
Expression e2 = Expression.Equal(namespaceProperty, val2);
var andExp = Expression.AndAlso(e1, e2);
var lambda = Expression.Lambda<Func<Service, bool>>(andExp, argParam);
One important aspect I've changed is the type passed to Expression.Parameter
- it certainly looks like it should be a Service
rather than a string
.
I've given that a try, and it seemed to work when I called lambda.Compile
and executed it on a couple of sample Service
objects...
How do I dynamically create an ExpressionFuncMyClass, bool predicate?
Original
Like so:
var param = Expression.Parameter(typeof(string), "p");
var len = Expression.PropertyOrField(param, "Length");
var body = Expression.Equal(
len, Expression.Constant(5));
var lambda = Expression.Lambda<Func<string, bool>>(
body, param);
Updated
re (p.Length== 5) && (p.SomeOtherProperty == "hello")
:
var param = Expression.Parameter(typeof(SomeType), "p");
var body = Expression.AndAlso(
Expression.Equal(
Expression.PropertyOrField(param, "Length"),
Expression.Constant(5)
),
Expression.Equal(
Expression.PropertyOrField(param, "SomeOtherProperty"),
Expression.Constant("hello")
));
var lambda = Expression.Lambda<Func<SomeType, bool>>(body, param);
Dynamically constructing an ExpressionFuncT,bool doesn't work for GreaterThen
The problem is the nullability. You could probably get away with just adding a conversion expression from the value to the type of the property:
public Expression<Func<Book, bool>> GreaterThan(string columnName, object value)
{
var param = Expression.Parameter(typeof(Book), "w");
var property = Expression.PropertyOrField(param, columnName);
var propertyType = typeof(Book).GetProperty(columnName).PropertyType;
var body = Expression.GreaterThan(property,
Expression.Convert(Expression.Constant(value), propertyType));
return Expression.Lambda<Func<Book, bool>>(body, param);
}
This is now effectively doing:
w => w.PublishedYear > (int?) 2005
... which is what the C# code is doing implicitly.
Create expression tree (ExpressionFuncTEntity, bool) with property of entity (x.ID == 123)
Based on your description / comments, the following should work for you:
public Expression<Func<TEntity, bool>> SearchExpression()
{
ConstantExpression[] expectedValues = Your_Magic_Method_Of_Obtaining_Expected_Values();
var entity = Expression.Parameter(typeof (TEntity));
var comparisonExpression = typeof(TEntity).GetProperties()
.Select((info, i) => Expression.Equal(
Expression.Property(entity, info),
expectedValues[i]))
.Aggregate(Expression.And);
return Expression.Lambda<Func<TEntity, bool>>(comparisonExpression, entity);
}
Composing invocations with ExpressionFuncT,bool the same way as FuncT,bool
Unfortunately, C# does not currently provide a way to compose expressions from Expression<Func<...>>
objects. You have to use expression trees, which is quite a bit longer:
static Expression<Func<T,bool>> CheckExpr<T>(Expression<Func<T,Customer>> conv, string first, string last) {
var arg = Expression.Parameter(typeof(T));
var get = Expression.Invoke(conv, arg);
return Expression.Lambda<Func<T,bool>>(
Expression.MakeBinary(
ExpressionType.AndAlso
, Expression.MakeBinary(
ExpressionType.Equal
, Expression.Property(get, nameof(Customer.FirstName))
, Expression.Constant(first)
)
, Expression.MakeBinary(
ExpressionType.Equal
, Expression.Property(get, nameof(Customer.LastName))
, Expression.Constant(last)
)
)
, arg
);
}
Extending an expressionfunct, bool
You have to build new expression, because expressions are immutable. In this case you can reuse body and parameters of existing expression for the new one:
public class TenantFilterExpressionAdaptor<T, TIdType> {
public TIdType TenantId { get; set; }
public Expression<Func<T, bool>> Adapt(Expression<Func<T, TIdType>> idExpression) {
return Expression.Lambda<Func<T, bool>>(
Expression.Equal(idExpression.Body, Expression.Constant(TenantId)),
idExpression.Parameters);
}
}
You mentioned in comments that constant is problematic for you. In this case you can reference property itself and not its value:
return Expression.Lambda<Func<T, bool>>(
Expression.Equal(idExpression.Body, Expression.Property(Expression.Constant(this), nameof(TenantId))),
idExpression.Parameters);
This will be expression like:
x => x.TenantId == this.TenantId
where this
is instance of your adapter.
Unable to create a compound ExpressionFuncstring, bool from a set of expressions
If you wanted to do it with Expressions, something like this would work. This doesn't short circuit, though you can build that in. You were quite close. You need to thread one parameter expression through the whole parameter tree.
string badValue = "hello!";
const int minSize = 8;
const int maxSize = 30;
Expression<Func<string, bool>> stringLengthMax = value => value.Length < maxSize;
Expression<Func<string, bool>> stringLengthMin = value => value.Length > minSize;
Expression<Func<string, bool>> isEmpty = value => !string.IsNullOrEmpty(value);
ParameterExpression pe = Expression.Parameter(typeof(string));
var x = Expression.Lambda<Func<string, bool>>(
Expression.And(Expression.Invoke(stringLengthMax, pe),
Expression.And(Expression.Invoke(stringLengthMin, pe), Expression.Invoke(isEmpty, pe))), pe);
Func<string, bool> shouldValidate = x.Compile();
bool resultFalse1 = shouldValidate("hello!");
bool resultFalse2 = shouldValidate("1234567890123456789012345678901");
//bool resultFalse3 = shouldValidate(null); Throws an exception because you can't do (null).Length
bool shouldBeTrue = shouldValidate("123456789");
//LinqPad code to view results:
resultFalse1.Dump();
resultFalse2.Dump();
//resultFalse3.Dump();
shouldBeTrue.Dump();
Related Topics
Set Background Color of Wpf Textbox in C# Code
How to Know User Has Clicked "X" or the "Close" Button
Suppress Warning Cs1998: This Async Method Lacks 'Await'
Use of SQLparameter in SQL Like Clause Not Working
How to Compare Only Date Components from Datetime in Ef
Control Cannot Fall Through from One Case Label
How to Show the Sum of in a Datagridview Column
How to Create a Custom Messagebox
Send a File to the Recycle Bin
Cryptographicexception Was Unhandled: System Cannot Find the Specified File
.Contains() on a List of Custom Class Objects
How to Seed an Admin User in Ef Core 2.1.0
ASP.NET 2012 Unobtrusive Validation with Jquery
C# - Making a Process.Start Wait Until the Process Has Start-Up