LINQ Building Dynamic Expression in C# - c#

I would like to dynamically build the linq expression used in the following Select statement on a DataTable filled from a DB:
IQueryable iq = myDataTable
.AsEnumerable()
.AsQueryable()
.Select(
d => new {
Field1 = d.Field<int>(37),
Field2 = d.Field<string>(0),
Field3 = d.Field<DateTime>(1)
}
);
The fields are not known before runtime. I will get the list of fields ("Field1", "Field2", "Field3"...), their types (int, string, datetime...) and their indexes (37, 0, 1...) at runtime.
Is there any way to achieve that?
[UPDATE]
I started to write the following code:
Type type = typeof(DataRow);
MethodInfo mi1 = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(int) });
mi1 = mi1.MakeGenericMethod(type);
List<Expression> list1 = new List<Expression>();
ParameterExpression datarow1 = Expression.Parameter(type, "datarow");
ConstantExpression constant1 = Expression.Constant(37, typeof(int));
list1.Add(datarow1);
list1.Add(constant1);
ReadOnlyCollection<Expression> collection1 = new ReadOnlyCollection<Expression>(list1);
MethodCallExpression right1 = Expression.Call(null, mi1, datarow1, constant1);
Expression left1 = Expression.Parameter(type, "Field1");
var expression1 = Expression.Assign(left1, right1);
MethodInfo mi2 = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(int) });
mi2 = mi2.MakeGenericMethod(type);
List<Expression> list2 = new List<Expression>();
ParameterExpression datarow2 = Expression.Parameter(type, "datarow");
ConstantExpression constant2 = Expression.Constant(0, typeof(int));
list2.Add(datarow2);
list2.Add(constant2);
ReadOnlyCollection<Expression> collection2 = new ReadOnlyCollection<Expression>(list2);
MethodCallExpression right2 = Expression.Call(null, mi2, datarow2, constant2);
Expression left2 = Expression.Parameter(type, "Field2");
var expression2 = Expression.Assign(left2, right2);
List<Expression> bindings = new List<Expression>();
bindings.Add(expression1);
bindings.Add(expression2);
var newInit = Expression.New(typeof(object));
var init = Expression.NewArrayInit(type, bindings);
var dr = Expression.Parameter(type, "dr");
var linq = Expression.Lambda<Func<DataRow, DataRow>>(init, dr);
It compiles, however, there are several problems:
expression1 is (Field1 = datarow.Field(0)) instead of Field1 = datarow.Field<int>(0)
same for expression 2
it throws a runtime exception: 'System.Data.DataRow[]' cannot be used for return type 'System.Data.DataRow' on the line var linq = Expression.Lambda...
Could you please help?
#George
I think I do not need a Type dictionary as I have a set of predefined types.

I strongly recommend for this kind of situations to build a query string and run it against your database.
But ... for the sake of art, you can do this. To achieve what you want you need more than just creating an expression tree. First, you will need a "pool" of anonymous types for each projection case found at runtime. Id est, you need to have a Dictionary < somekey, sometype> where somekey can be a string that represents a serialized combination of columns (column indexes, etc) and sometype will be a real System.Type. But wait those types does not exists at compilation time. The question is: How to create these types?. The ONLY solution is to use Reflection.Emit and AssemblyBuilder, TypeBuilder, ModuleBuilder, etc ... in order to create new types at runtime. Belive me or not, anonymous types are just normal types but C# compiler does this magic for us. You would also need a mapping for each column found in your datatable and each property defined in your new type.
1) So ... you eventually request an "anonymous type" for your particular situation. If that type is not currently built, just built it before, store it in your dictionary and then ... use it.
2) The remaining job is to emit the select body wich is actually simple. Use Expression.MemberInit and Expression.Bind API's. Search for them on google, you will find plenty of informations. To find out what to "Bind" using Expression.Bind, you search in the second dictionary (the mapping between column and property).
Hope this helps. I cannot give you code because I am writing from a device where i don't have visual studio.

Related

Getting data from a string defined table name filtered on a string defined field

I am trying to return the contents of a Where filtered table of dynamic type based on a dynamic field name from an array of values.
Heres what I have so far:
public JsonResult GetRelationShips(string linkingTable, string idField, int[] ids)
{
var tableType = typeof(context).GetProperty(linkingTable);
var entityTable = tableType.GetValue(db) as IQueryable;
var method = typeof(List<int>).GetMethod("Contains");
var eParam = Expression.Parameter(tableType.PropertyType.GetGenericArguments()[0]);
var call = Expression.Call(Expression.Constant(ids.ToList()), method, Expression.Property(eParam, idField));
var func = typeof(Func<,>);
var genericFunc = func.MakeGenericType(tableType.PropertyType.GetGenericArguments()[0], typeof(bool));
var lambda = Expression.Lambda(genericFunc, call, eParam);
var results = typeof(System.Linq.Enumerable).GetMethods().Where(x => x.Name == "Where").First().Invoke(db, new object[] { lambda });
return Json(results);
}
That last line is giving me an error:
Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Honestly, I cobbled this together this afternoon from snippets from all over the internet. I have no idea what I'm doing here, this is new to me and I'm keen to learn. Just trying to avoid SQL injection, the rest of the project is entirely Linq so I'm soldiering on. I'm learning Generic types too, got my head around that, don't see how I can use them here though.
There any many flaws in this single line of code:
var results = typeof(System.Linq.Enumerable).GetMethods().Where(x => x.Name == "Where").First().Invoke(db, new object[] { lambda });
Trying to call Enumerable.Where instead of Queryable.Where. This would cause retrieving the whole table data and performing the filtering in memory instead of the database side.
Trying to call potentially wrong method. Where has 2 overloads, and it's undefined which one will be returned as first by the reflection.
Trying to invoke generic method definition, causing the exception you are getting. You have to first construct a generic method by using MakeGenericMethod and invoke that.
Trying to invoke static generic extension method via reflection as if it is instance method. Instead, you should pass null as first argument to Invoke and pass new object[] { entityTable, lambda } as second argument.
You can avoid all these traps by simply using the C# dynamic method dispatch:
IQueryable results = Queryable.Where((dynamic)entityTable, (dynamic)lambda);
The whole code can be simplified by using the following Expression.Call overload:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments);
which is very useful for "calling" static generic extension methods:
var query = (IQueryable)db.GetType().GetProperty(linkingTable).GetValue(db);
// e =>
var entity = Expression.Parameter(query.ElementType, "e");
// ids.Contains(e.idField)
// = Enumerable<int>.Contains(ids, e.idField)
var containsCall = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Contains),
new Type[] { typeof(int) },
Expression.Constant(ids),
Expression.Property(entity, idField)
);
// e => ids.Contains(e.idField)
var predicate = Expression.Lambda(containsCall, entity);
// query = query.Where(predicate);
query = Queryable.Where((dynamic)query, (dynamic)predicate);
You can also avoid the dynamic Where call and use similar Expression.Call based approach to "call" it, combined with IQueryProvider.CreateQuery:
// query.Where(predicate)
// = Queryable.Where<ElementType>(query, predicate)
var whereCall = Expression.Call(
typeof(Queryable),
nameof(Queryable.Where),
new Type[] { query.ElementType },
query.Expression,
predicate
);
// query = query.Where(predicate)
query = query.Provider.CreateQuery(whereCall);
I've provided all that just because you said you are keen to learn. The simplest way of handling such tasks (and not reinvent the wheel) is to use some 3rd party package. For instance, with System.Linq.Dynamic package the whole code would be:
var query = ((IQueryable)db.GetType().GetProperty(linkingTable).GetValue(db))
.Where($"#0.Contains({idField})", ids);

Accessing Type T property in the ParameterExpression type Dictionary<string,T>

I have expression tree code used for data filtering, till date it was used on a Generic list List<T> and following code use to work fine:
var parameterType = Expression.Parameter(typeof(T), "obj");
var memberExpression = Expression.Property(parameterType, "Name");
It was easy to create a binary expression as follows and process the result:
var constantExpression = Expression.Constant("Jack",typeof(string));
var finalExpression = Expression.Equal(memberExpression,constantExpression);
var resultFunc = Expression.Lambda<Func<T, bool>>(finalExpression, parameterType).Compile();
// Final Result
sourceList.Where(obj => resultFunc(obj));
Here Name is a property in Type T, as result post Lambda compilation was Func<T,bool>, I use to apply the same to the Where clause of IEnumerable type. Now the underlying system has changed to use the same code on Dictionary<string,T>, so all the Type T values in the collection now have a string Key associated and type T is now accessible as a value of dictionary object. Also I am applying on a IQueryable, which takes an expression tree the final lambda post compilation at source would be Func<KeyValuePair<string,T>,bool>, therefore cannot apply the Value in the final result processing.
Following is the code modification now:
var parameterType = Expression.Parameter(typeof(KeyValuePair<string,T>), "obj");
Following code fails, since now Name property is in Value of the KeyValuePair and we cannot use it as Type T:
var memberExpression = Expression.Property(parameterType, "Name");
Any pointer to make it work or any suggestion to set me in the right direction?
You could get Expression for calling ["name"] item this way:
var nameProperty= Expression.Call(parameterType,
typeof(IDictionary<string, T>).GetMethod("get_Item"),
Expression.Constant("Name"));
or as:
var nameProperty = Expression.Property(parameterType, "Item",
new Expression[] { Expression.Constant("Name") });
Both is call of Item property
EDIT : To get Value from KeyValuePair you have to get property Key, compare it with "Name" and a property Value and compare it with value:
var parameterType = Expression.Parameter(typeof(KeyValuePair<string,T>), "obj");
var value = Expression.Property(parameterType, "Value" );
var key = Expression.Property(parameterType, "Key");
var eq1 = Expression.Equal(key, Expression.Constant("Name"));
var eq2 = Expression.Equal(value, constantExpression);
var and = Expression.And(eq1, eq2);
var lambda = Expression.Lambda(and, parameterType);

Is there a way to compile a lambda expression passing an object generated run time?

I need to compile a lambda expression passing an object generated run time. Here is code I have so far.
An example:
var anonType = new { Name = "Florida" }.GetType();
var myObj = Activator.CreateInstance(anonType, "Florida");
var expression = Expression.Parameter(myObj.GetType(), "Name");
var property = Expression.Property(expression, "Name");
var rule = new Rule("Name", "NotEqual", "Florida");
ExpressionType tBinary;
if (!Enum.TryParse(rule.Operator, out tBinary)) return;
var propertyType = myObj.GetType().GetProperty(rule.MemberName).PropertyType;
var right = Expression.Constant(Convert.ChangeType(rule.TargetValue, propertyType));
var result = Expression.MakeBinary(tBinary, property, right);
var expr = Expression.Lambda<Func<Type, bool>>(result, expression).Compile();
var isValid = expr(anonType);
I'm getting an error at the line when its trying to compile Lambda expression.
Additional information: ParameterExpression of type '<>f__AnonymousType0`1[System.String]' cannot be used for delegate parameter of type 'System.Type'
Not sure what you want to achieve with that, but will answer your direct question. You can compile lambda like this in your case:
// create Func<AnonymousType, bool>
var func = typeof(Func<,>).MakeGenericType(anonType,typeof(bool));
// compile
var expr = Expression.Lambda(func, result, expression).Compile();
// invoke
var isValid = expr.DynamicInvoke(new { Name = "NotFlorida" });

Expression Lambda with types known at runtime

I am trying to make some Expressions where I will be using a lambda to create two methods: a selector and condition. Simplified the usage is condition(selector(data)), but the intemediate type is only known at runtime. I have the following code which works as long as the intemediate type is object, but at runtime I know the real type and would like to use that.
public static ICondition<TData> GetRelayConditionByReflection(string name, string message, string assemblyName,
string fullyQualifiedName, string conditionMethodName, string selectorMethodName) {
var asm = Assembly.LoadFrom(assemblyName);
var type = asm.GetType(fullyQualifiedName);
var selectorMi = type.GetMethod(selectorMethodName, BindingFlags.Static | BindingFlags.Public);
var conditionMi = type.GetMethod(conditionMethodName, BindingFlags.Static | BindingFlags.Public);
var tCondType = selectorMi.ReturnType;
var returnType = typeof(RelayCondition<,>);
var typeArgs = new[] { typeof(TData), tCondType };
var paramTData = Expression.Parameter(typeof(TData), "data");
var selector = Expression.Lambda<Func<TData, object>>(
Expression.Call(selectorMi, paramTData), paramTData).Compile();
var paramTCondition = Expression.Parameter(tCondType, "condition");
var condition = Expression.Lambda<Func<object, bool>>(
Expression.Call(conditionMi, paramTCondition), paramTCondition).Compile();
return (ICondition<TData>)Activator.CreateInstance(returnType.MakeGenericType(typeArgs), name, condition, message, selector);
}
Specifically, there is Expression.Lambda<Func<TData, object>> and Expression.Lambda<Func<object, bool>> where object currently only allows the intemediate type to be object.
Is it possible to create this with a type known only at runtime? I am open for other approaches for the entire problem, as long as performance isn't attrocius.
If you don't need implicit downcasting (for example declaring a Func<object> when in truth you method returns a Func<Foo>), you can use the non-generic Expression.Lambda() method. It returns a LambdaExpression, but in truth it returns a Expression<> of Func<> or of Action downcasted to LambdaExpression (Expression<> is a subclass of LambdaExpression), so:
var condition = Expression.Lambda(
Expression.Call(conditionMi, paramTCondition), paramTCondition).Compile();
now condition is a Delegate, but in truth it is a Func<> (or an Action if it had a return type void).

How to generate a lambda at runtime passing a property name as string?

I have a list of PolicyTran objects:
List<PolicyTran> AllTransactions;
I need to run a query filtering by a property, e.g:
var insureds = AllTransactions.Select(x => x.Insured).ToList();
That works fine, but I need to pass the x.Insured property at runtime since that property could take different values.
I tried doing:
ParameterExpression x = Expression.Parameter(typeof (PolicyTran),"x");
MemberExpression body = Expression.Property(x, propertyName);
var lambda = Expression.Lambda(body,x).Compile();
var result = AllTransactions.Select(lambda).ToList();
In this case propertyName contains "Insured" or any other PolicyTran property.
But I get a compilation error saying that "The type arguments cannot be inferred by the ussage..."
I also tried, but no luck:
ParameterExpression x = Expression.Parameter(typeof (PolicyTran),"x");
var result = AllTransactions.Select(Expression.Lambda<Func<PolicyTran, bool>>(x).Compile()).ToList();
Any ideas??
Your first attempt is closer to the solution. You just need to call the generic version of Lambda:
var lambda = Expression.Lambda<Func<PolicyTran, object>>(body, x).Compile();
in order to get a Func<PolicyTran, object> delegate.
Otherwise the labda will return a simple System.Delegate from which the LINQ .Select will be unable to infer the types.

Categories

Resources