basically I want to get the values of the parameters of a called method like this:
var x = 1;
var a = 2;
var b = 3;
Do<HomeController>(o => o.Save(x, "Jimmy", a+b+5, Math.Sqrt(81)));
public static void Do<T>(Expression<Action<T>> expression) where T : Controller
{
// get the values 1,Jimmy,10,9 here
}
Well, you'd need to drill into the expression, find the MethodCallExpression, and then look at the arguments to it. Note that we don't have the value of o, so we've got to assume that the arguments to the method don't rely on that. Also we're still assuming that the lambda expression just relies on it being a MethodCallExpression?
EDIT: Okay, here's an edited version which evaluates the arguments. However, it assumes you're not really using the lambda expression parameter within the arguments (which is what the new object[1] is about - it's providing a null parameter, effectively).
using System;
using System.Linq.Expressions;
class Foo
{
public void Save(int x, string y, int z, double d)
{
}
}
class Program
{
static void Main()
{
var x = 1;
var a = 2;
var b = 3;
ShowValues<Foo>(o => o.Save(x, "Jimmy", a + b + 5, Math.Sqrt(81)));
}
static void ShowValues<T>(Expression<Action<T>> expression)
{
var call = expression.Body as MethodCallExpression;
if (call == null)
{
throw new ArgumentException("Not a method call");
}
foreach (Expression argument in call.Arguments)
{
LambdaExpression lambda = Expression.Lambda(argument,
expression.Parameters);
Delegate d = lambda.Compile();
object value = d.DynamicInvoke(new object[1]);
Console.WriteLine("Got value: {0}", value);
}
}
}
As Jon said you can check to see if the expression is a MethodCallExpression
class Program
{
static void Main(string[] args)
{
Program.Do<Controller>(c => c.Save(1, "Jimmy"));
}
public static void Do<T>(Expression<Action<T>> expression) where T : Controller
{
var body = expression.Body as MethodCallExpression;
if (body != null)
{
foreach (var argument in body.Arguments)
{
var constant = argument as ConstantExpression;
if (constant != null)
{
Console.WriteLine(constant.Value);
}
}
}
}
}
public class Controller
{
public void Save(int id, string name)
{
}
}
My universal answer is below. I hope it will help you and somebody else.
var dict = new Dictionary<string, object>();
var parameterExpressions = methodCallExpr.Arguments;
foreach (var param in method.GetParameters())
{
var parameterExpression = parameterExpressions[counter];
var paramValueAccessor = Expression.Lambda(parameterExpression);
var paramValue = paramValueAccessor.Compile().DynamicInvoke();
dict[param.Name] = paramValue;
}
Here is some code that is designed to work with any expression — in the sense that it doesn’t fundamentally assume that you are passing in a method-call expression. However, it is not complete. You will have to fill in the rest.
public static IEnumerable<object> ExtractConstants<T>(
Expression<Action<T>> expression)
{
return extractConstants(expression);
}
private static IEnumerable<object> extractConstants(Expression expression)
{
if (expression == null)
yield break;
if (expression is ConstantExpression)
yield return ((ConstantExpression) expression).Value;
else if (expression is LambdaExpression)
foreach (var constant in extractConstants(
((LambdaExpression) expression).Body))
yield return constant;
else if (expression is UnaryExpression)
foreach (var constant in extractConstants(
((UnaryExpression) expression).Operand))
yield return constant;
else if (expression is MethodCallExpression)
{
foreach (var arg in ((MethodCallExpression) expression).Arguments)
foreach (var constant in extractConstants(arg))
yield return constant;
foreach (var constant in extractConstants(
((MethodCallExpression) expression).Object))
yield return constant;
}
else
throw new NotImplementedException();
}
For the case that you have mentioned, this already works:
// Prints:
// Jimmy (System.String)
// 1 (System.Int32)
foreach (var constant in Ext.ExtractConstants<string>(
str => Console.WriteLine("Jimmy", 1)))
Console.WriteLine("{0} ({1})", constant.ToString(),
constant.GetType().FullName);
For more complex lambda expressions that employ other types of expression nodes, you will have to incrementally extend the above code. Every time you use it and it throws a NotImplementedException, here is what I do:
Open the Watch window in the debugger
Look at the expression variable and its type
Add the necessary code to handle that expression type
Over time the method will become more and more complete.
public override IQueryable<Image> FindAll(System.Linq.Expressions.Expression<Func<Image, dynamic>> Id)
{
dynamic currentType = Id.Parameters[0];
var id = currentType.Type.GUID;
var result = (_uniwOfWork as UnitOfWork).uspGetImages(id.ToString());
return FindAll();
}
use keyword dynamic.
Related
I am attempting to retrieve an object[] from a Linq expression. I use the method name and object array in order to create a cache key as a string. In the end, it will enable me to execute a line like this:
int id = 123;
DeleteCacheItems(()=> someInstance.GetCachedItem(id, null))
The key would resolve to "NameSpaceOfSomeInstance.SomeInstance_123_null"
My unit test works ok:
[TestMethod]
public void CacheDeleteViaExpressionTest()
{
int id = 3186718;
var something = ProviderFactory.Instance.Get(ProviderType.Something);
string methodName = CacheMethodManager.GetExpressionMethodFullName(() => something.GetRealtimeRooms(spid, null)); //returns ok
var objects = CacheMethodManager.GetExpressionArgumentValues(()=> something.GetRealtimeRooms(spid, null));
// returns object[0] = 3186718
// returns object[1] = null
string key = MethodCacheKey.GetCacheKey(methodName, objects, something);
Assert.IsTrue((int)objects[0] == id);
Assert.IsTrue(objects[1] == null);
}
Now I have an MVC controller action that attempts to use this code. I am finding that the object array is only populated with the value types if the expression contains local variables.
If I use id from the parameter in the action, it does not work. If use localId it works.
I have implemented the mechanism described here (Getting object array containing the values of parameters from a Lambda Expression).
The problem is a bit different in that I have a recursive call to resolve the ConstantExpression.
[HttpPost]
public ActionResult ClearCache(int id)
{
try
{
CacheMethodManager manager = new CacheMethodManager();
var objects = CacheMethodManager.GetExpressionArgumentValues(()=> something.GetRealtimeRooms(**id**, null));
//here object[0] = "DisplayClass<> " // <---------------- This uses a closure class or something that wraps the value
int localId = id;
var objects2 = CacheMethodManager.GetExpressionArgumentValues(()=> something.GetRealtimeRooms(localId, null));
//here object2[0] = 3186718 // <---------------- WORKS
return Json(new { result = "OK!" });
}
catch (Exception e)
{
return Json(new { result = "Error", message = $"{e.Message}" });
}
}
public static object[] GetExpressionArgumentValues(Expression<Action> expr)
{
var call = (MethodCallExpression)expr.Body;
var args = call.Arguments;
object[] methodArguments = args.Select(c =>
c is ConstantExpression
? ((ConstantExpression)c).Value
: Extension.GetValue(Extension.ResolveMemberExpression(c))).ToArray();
return methodArguments;
}
public static MemberExpression ResolveMemberExpression(Expression expression)
{
if (expression is MemberExpression)
{
return (MemberExpression)expression;
}
else if (expression is ConstantExpression)
{
return (MemberExpression)expression;
}
else if (expression is UnaryExpression)
{
// if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname)
return (MemberExpression)((UnaryExpression)expression).Operand;
}
else
{
throw new NotSupportedException(expression.ToString());
}
}
public static object GetValue(MemberExpression exp)
{
// expression is ConstantExpression or FieldExpression
if (exp.Expression is ConstantExpression)
{
var o = (((ConstantExpression)exp.Expression).Value)
.GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
return o;
}
else if (exp.Expression is MemberExpression)
{
return GetValue((MemberExpression)exp.Expression);
}
else
{
throw new NotImplementedException();
}
}
Why does the expression behave differently? How could we handle this internally so that developers dont need to create local variables?
Thanks
S
I am writing a method which accepts a lambda expression as a parameter and parses its properties from left to right. The following criteria should be met:
The expression must only use simple property or fields -- no method calls or LINQ queries or anything more complex. (e.g. p => p.HomeAddress.City) The method can throw an exception if the expression does not meet these criteria.
The method should return a list of info about each property or field: name and type.
How can this be accomplished?
private List<SomeClass> ParseExpression<T1,T2>(Expression<Func<T1, T2>> func)
{
// ??
}
This:
private static IReadOnlyList<Tuple<string, Type>> ParseExpression<T1, T2>(Expression<Func<T1, T2>> func)
{
var par = func.Parameters[0];
var lst = new List<Tuple<string, Type>>();
Expression exp = func.Body;
while (exp != par)
{
if (exp.NodeType != ExpressionType.MemberAccess)
{
throw new Exception(exp.ToString());
}
MemberExpression me = (MemberExpression)exp;
MemberInfo mi = me.Member;
if (mi.MemberType == MemberTypes.Field)
{
FieldInfo fi = (FieldInfo)mi;
lst.Add(Tuple.Create(fi.Name, fi.FieldType));
}
else if (mi.MemberType == MemberTypes.Property)
{
PropertyInfo pi = (PropertyInfo)mi;
lst.Add(Tuple.Create(pi.Name, pi.PropertyType));
}
else
{
throw new Exception(exp.ToString());
}
exp = me.Expression;
}
lst.Reverse();
return lst;
}
Example:
class Cl1
{
public Cl2 Cl2;
}
class Cl2
{
public string Str { get; set; }
}
and then:
var result = ParseExpression<Cl1, string>(x => x.Cl2.Str);
foreach (var el in result)
{
Console.WriteLine($"{el.Item1}: {el.Item2}");
}
Doing Add for two elements like T a, T b is simple, Mark has provided a good solution using Expression tree here, which translates into following and is simple to use:
static T Add<T>(T a, T b)
{
// Declare Parameter Expressions
ParameterExpression paramA = Expression.Parameter(typeof(T), "valueA"),
paramB = Expression.Parameter(typeof(T), "valueB");
// add the parameters together
BinaryExpression body = Expression.Add(paramA, paramB);
// Compile it
Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
// Call it
return add(a, b);
}
Challenge that I have is there's a collection of List<T>, where all elements have to added as shown above. I have tried following, on same lines as above, but it doesn't work:
static T AddAll<T>(List<T> list)
{
var parameterExpressionList = list.Select((x,i) => (Expression)Expression.Parameter(typeof(T), "value"+i));
var body = parameterExpressionList
.Skip(1)
.Aggregate(parameterExpressionList.First(),
(paramA, paramB) => Expression.Add(paramA, paramB));
// Compile it
Func<List<T>, T> addAll = Expression.Lambda<Func<List<T>, T>>(body, parameterExpressionList.Cast<ParameterExpression>()).Compile();
return addAll(list);
}
Run-time error that I get is: Incorrect number of parameters supplied for lambda declaration. Any pointer, how to achieve, please note, I don't need a solution where I cumulatively pick two elements from actual list and call Add<T>(T a, T b) , since that would lead to multiple times compilation of the Expression tree, which is not efficient, as I would have > 100 K data points, any suggestion to make my code work would be great, I am not sure where it is going wrong.
Since you have created a generic function, just use it on the list (I added an optional Adder method to handle non-standard classes):
static T AddAll<T>(IEnumerable<T> src, Func<T, T, T> adder = null) {
// Declare Parameter Expressions
ParameterExpression paramA = Expression.Parameter(typeof(T), "valueA"),
paramB = Expression.Parameter(typeof(T), "valueB");
// add the parameters together
BinaryExpression body;
if (adder == null)
body = Expression.Add(paramA, paramB);
else
body = Expression.Add(paramA, paramB, adder.GetMethodInfo());
// Compile it
Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
// Call it
return src.Aggregate(default(T), (ans, n) => add(ans, n));
}
You can use the Adder parameter to handle things like strings:
var ans = AddAll(new[] { "a", "b", "c" }, String.Concat);
Since we know the type of T at compile time though, we can just call Sum:
static T AddAll2<T>(IEnumerable<T> src) {
var paramA = Expression.Parameter(typeof(IEnumerable<T>), "valueA");
var method = typeof(Enumerable).GetMethod("Sum", new[] { typeof(IEnumerable<T>) });
if (method != null) {
// Create lambda body
var body = Expression.Call(method, paramA);
// Compile it
Func<IEnumerable<T>, T> sum = Expression.Lambda<Func<IEnumerable<T>, T>>(body, paramA).Compile();
// Call it
return sum(src);
}
else
return default(T);
}
Of course, if you are going to call Sum, you don't need a lambda:
static T AddAll3<T>(IEnumerable<T> src) {
var method = typeof(Enumerable).GetMethod("Sum", new[] { typeof(IEnumerable<T>) });
if (method != null) {
// Call it
return (T)method.Invoke(null, new[] { src });
}
else
return default(T);
}
Just try to get every item from your list and then accumulate they into result.
static T AddAll<T>(List<T> list)
{
if (list.Count == 0)
{
// It's additional small case
return default(T);
}
var listParam = Expression.Parameter(typeof(List<T>));
var propInfo = typeof(List<T>).GetProperty("Item");
var indexes = list.Select((x, i) => Expression.MakeIndex(listParam, propInfo, new[] { Expression.Constant(i) }));
Expression sum = indexes.First();
foreach (var item in indexes.Skip(1))
{
sum = Expression.Add(sum, item);
}
var lambda = Expression.Lambda<Func<List<T>, T>>(sum, listParam).Compile();
return lambda(list);
}
You can pass list directly as argument and just create sum via indexes:
static T AddAll<T>(List<T> list)
{
if (list.Count == 0) return default(T);
if (list.Count == 1) return list[0];
var indexerProperty = typeof(List<T>).GetProperty("Item");
var p = Expression.Parameter(typeof(List<T>));
var exp = Expression.Add(
Expression.MakeIndex(p, indexerProperty, new [] { Expression.Constant(0) }),
Expression.MakeIndex(p, indexerProperty, new [] { Expression.Constant(1) }));
for (var i = 2; i < list.Count; i++)
{
exp = Expression.Add(
exp,
Expression.MakeIndex(p, indexerProperty, new [] { Expression.Constant(i) }));
}
var lambda = Expression.Lambda<Func<List<T>, T>>(exp, p).Compile();
return lambda(list);
}
Store all applicable Enumerable.Sum overloads in a dictionary:
// all methods with signature public static T Enumerable.Sum(IEnumerable<T>) by element type
private static readonly Dictionary<Type, MethodInfo> _sumMethodsByElementType = typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.Name == "Sum" && !m.IsGenericMethod)
.Select(m => new { Method = m, Parameters = m.GetParameters() })
.Where(mp => mp.Parameters.Length == 1)
.Select(mp => new { mp.Method, mp.Parameters[0].ParameterType })
.Where(mp => mp.ParameterType.IsGenericType && mp.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(mp => new { mp.Method, ElementType = mp.ParameterType.GetGenericArguments()[0] })
.Where(me => me.Method.ReturnType == me.ElementType)
.ToDictionary(mp => mp.ElementType, mp => mp.Method);
Invoke the corresponding one from inside the generic AddAll (or Sum as I prefer to call it) method:
public static T Sum<T>(IEnumerable<T> summands)
{
MethodInfo sumMethod;
if (!_sumMethodsByElementType.TryGetValue(typeof(T), out sumMethod)) throw new InvalidOperationException($"Cannot sum elements of type {typeof(T)}.");
return (T)sumMethod.Invoke(null, new object[] { summands });
}
Test:
Console.WriteLine(Sum(new[] { 1, 2, 3 }));
Console.WriteLine(Sum(new[] { 1, 2, 3, default(int?) }));
Console.WriteLine(Sum(new[] { 1.1, 2.2, 3.3 }));
Console.WriteLine(Sum(new[] { 1.1, 2.2, 3.3, default(double?) }));
try { Console.WriteLine(Sum(new[] { 'a', 'b', 'c' })); }
catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); }
Output:
6
6
6.6
6.6
Cannot sum elements of type System.Char.
You don't necessarily need to solve every part of the problem with expressions, if you are just interested in the operation itself
Here's an implementation that uses Singleton via the Lazy<> type for the default addition of type T (instead of static methods)
The LinqExpression expression could potentially be reused if you really need expressions (say, in an EF scenario), but there's no equivalent expression for the AddAll operation... though it can potentially be expanded to support a generic expression for AddAll
public abstract class Addition<T>
{
private readonly Lazy<Expression<Func<T, T, T>>> _lazyExpression;
private readonly Lazy<Func<T, T, T>> _lazyFunc;
public Func<T, T, T> Execute
{
get { return _lazyFunc.Value; }
}
public Expression<Func<T, T, T>> LinqExpression
{
get { return _lazyExpression.Value; }
}
protected Addition()
{
_lazyExpression = new Lazy<Expression<Func<T, T, T>>>(InitializeExpression);
_lazyFunc = new Lazy<Func<T, T, T>>(() => LinqExpression.Compile());
}
protected abstract Expression<Func<T, T, T>> InitializeExpression();
}
public sealed class DefaultAddition<T> : Addition<T>
{
private static readonly Lazy<DefaultAddition<T>> _lazyInstance = new Lazy<DefaultAddition<T>>(() => new DefaultAddition<T>());
public static DefaultAddition<T> Instance
{
get {return _lazyInstance.Value; }
}
// Private constructor, you only get an instance via the Instance static property
private DefaultAddition()
{
}
protected override Expression<Func<T, T, T>> InitializeExpression()
{
var paramX = Expression.Parameter(typeof(T), "x");
var paramY = Expression.Parameter(typeof(T), "y");
var body = Expression.Add(paramX, paramY);
return Expression.Lambda<Func<T, T, T>>(body, paramX, paramY);
}
}
public static class Operations
{
public static T Add<T>(T x, T y)
{
return DefaultAddition<T>.Instance.Execute(x, y);
}
public static T AddAll<T>(IEnumerable<T> enumerable)
{
var itemAdd = DefaultAddition<T>.Instance.Execute;
return enumerable.Aggregate(default(T), (result, item) => itemAdd(result, item));
// This might be more efficient than Aggregate, but I didn't benchmark it
/*
var result = default(T);
foreach (var item in enumerable)
{
result = itemAdd(result, item);
}
return result;
*/
}
}
Usage:
// Can mix double with int :)
var doubleAdd = Operations.Add(4.5, 3);
// Can mix decimal with int :)
var listAdd = Operations.AddAll(new[] {3, 6.7m, 0.3m});
// Even empty enumerables
var shortAdd = Operations.AddAll(Enumerable.Empty<short>());
// This will not work for byte. System.Byte should be casted to System.Int32
// Throws "InvalidOperationException: The binary operator Add is not defined for the types 'System.Byte' and 'System.Byte'."
var byteAdd = Operations.AddAll(new byte[] {1, 2, 3});
If your T is of value type like int, long, double etc. then you can simply do this:
//add
//using System.Linq;
var items = new List<int>();
items.Add(1);
items.Add(5);
items.Add(10);
var sum = items.Sum();
I have a method, GetSearchExpression, defined as:
private Expression<Func<T, bool>> GetSearchExpression(
string targetField, ExpressionType comparison, object value, IEnumerable<EnumerableResultQualifier> qualifiers = null);
At a high level, the method takes in a Field or Property (such as Order.Customer.Name), a comparison type (like Expression.Equals), and a value (like "Billy"), then returns a lambda expression suitable for input to a Where statement o => o.Customer.Name == "Billy"}.
Recently, I discovered an issue. Sometimes, the field I need is actually the field of an item in a collection (like Order.StatusLogs.First().CreatedDate).
I feel like that should be easy. The code that creates the left side of the expression (above, o => o.Customer.Name) is as follows:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
//turn "Order.Customer.Name" into List<string> { "Customer", "Name" }
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
//loop through each part and grab the specified field or property
foreach (var part in deQualifiedFieldName)
left = Expression.PropertyOrField(left == null ? param : left, part);
It seems like I should be able to revise this to check if the field/property exists, and if not, try to call a method by that name instead. It would look like this:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
var currentType = typeof(T);
foreach (var part in deQualifiedFieldName)
{
//this gets the Type of the current "level" we're at in the hierarchy passed via TargetField
currentType = SingleLevelFieldType(currentType, part);
if (currentType != null) //if the field/property was found
{
left = Expression.PropertyOrField(left == null ? param : left, part);
}
else
{ //if the field or property WASN'T found, it might be a method
var method = currentType.GetMethod(part, Type.EmptyTypes); //doesn't accept parameters
left = Expression.Call(left, method);
currentType = method.ReturnType;
}
}
The problem is that statement near the end (var method currentType.GetMethod(part, Type.EmptyTypes);). Turns out "First" and "Last" don't exist for IEnumerable objects, so I get a null exception when I try to use my Method object. In fact, the only way I can EVER them to show up in a GetMethod() call is by calling typeof(Enumerable).GetMethod(). That's useless of course, because then I get a static method in return rather than the instance method I need.
As a side-note: I tried using the static method, but Entity Framework throws a fit and won't accept it as part of the lambda.
I need help getting the instance MethodInfo of IEnumerable.First() & Last(). Please help!
My first attempt would be to identify if the instance is Enumerable<T> and treat the member name as method instead of a property/field like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
return Expression.Call(typeof(Enumerable), memberName, enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and try use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.First.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
The possible methods are First, FirstOrDefault, Last, LastOrDefault, Singe and SingleOrDefault.
But then you'll find that from the above methods only FirstOrDefault is supported in EF predicates.
Hence we can hardcode that call for collection types and do not include it in the accessors like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate2<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
target = Expression.Call(typeof(Enumerable), "FirstOrDefault", enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
P.S. While this will work, it might not produce the intended result. IEnumerable<T> navigation property means one-to-many relationship and assuming that the condition should apply only for the first (whatever that means in database, it's rather random) element does not make much sense. I would rather imply Any and try to build expression like this in the above case
t => t.StatusLogs.Any(s => s.CreatedDate >= new DateTime(2016, 1, 1))
or support FirstOrDefault, Any, All, (eventually Count, Sum, Min, Max) and handle them differently inside the builder.
Still IMO for collections Any is the most logical equivalent of the single entity criteria.
But all that will be another story (question).
UPDATE: Initially I was thinking to stop here, but for the sake of completeness, here is a sample implementation of the Any concept:
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(string memberPath, ExpressionType comparison, object value)
{
return (Expression<Func<T, bool>>)MakePredicate(
typeof(T), memberPath.Split('.'), 0, comparison, value);
}
static LambdaExpression MakePredicate(Type targetType, string[] memberNames, int index, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(targetType, targetType.Name.ToCamel());
Expression target = parameter;
for (int i = index; i < memberNames.Length; i++)
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var itemType = target.Type.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
var itemPredicate = MakePredicate(itemType, memberNames, i, comparison, value);
return Expression.Lambda(
Expression.Call(typeof(Enumerable), "Any", new[] { itemType }, target, itemPredicate),
parameter);
}
target = Expression.PropertyOrField(target, memberNames[i]);
}
if (value != null && value.GetType() != target.Type)
value = Convert.ChangeType(value, target.Type);
return Expression.Lambda(
Expression.MakeBinary(comparison, target, Expression.Constant(value)),
parameter);
}
static string ToCamel(this string s)
{
if (string.IsNullOrEmpty(s) || char.IsLower(s[0])) return s;
if (s.Length < 2) return s.ToLower();
var chars = s.ToCharArray();
chars[0] = char.ToLower(chars[0]);
return new string(chars);
}
}
so for this sample model
public class Foo
{
public ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public ICollection<Baz> Bazs { get; set; }
}
public class Baz
{
public ICollection<Detail> Details { get; set; }
}
public class Detail
{
public int Amount { get; set; }
}
the sample expression
var predicate = ExpressionUtils.MakePredicate<Foo>(
"Bars.Bazs.Details.Amount", ExpressionType.GreaterThan, 1234);
produces
foo => foo.Bars.Any(bar => bar.Bazs.Any(baz => baz.Details.Any(detail => detail.Amount > 1234)))
What you are possibly looking for is System.Linq.Enumerable.First<T>(this IEnumerable<T> source) etc, so: start at typeof(System.Linq.Enumerable) and work from there. Note: you mention IEnumerable<T>, but it is possible that you actually mean IQueryable<T>, in which case you want Queryable.First<T>(this IQueryable<T> source) etc. Maybe this difference (between Enumerable and Queryable) is why EF "throws a fit".
Thank you to Marc and Ivan for their input. They deserve credit as without their help I would have spent much longer finding a solution. However, as neither answer solved the issue I was having, I'm posting the solution that worked for me (successfully applying criteria as well as successfully querying against an EF data source):
private Expression<Func<T, bool>> GetSearchExpression(string targetField, ExpressionType comparison, object value, string enumMethod)
{
return (Expression<Func<T, bool>>)MakePredicate(DeQualifyFieldName(targetField, typeof(T)), comparison, value, enumMethod);
}
private LambdaExpression MakePredicate(string[] memberNames, ExpressionType comparison, object value, string enumMethod = "Any")
{
//create parameter for inner lambda expression
var parameter = Expression.Parameter(typeof(T), "t");
Expression left = parameter;
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
var currentType = typeof(T);
for (int x = 0; x < memberNames.Count(); x++)
{
string memberName = memberNames[x];
if (FieldExists(currentType, memberName))
{
//assign the current type member type
currentType = SingleLevelFieldType(currentType, memberName);
left = Expression.PropertyOrField(left == null ? parameter : left, memberName);
//mini-loop for non collection objects
if (!currentType.IsGenericType || (!(currentType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
currentType.GetGenericTypeDefinition() == typeof(ICollection<>))))
continue;
///Begin loop for collection objects -- this section can only run once
//get enum method
if (enumMethod.Length < 2) throw new Exception("Invalid enum method target.");
bool negateEnumMethod = enumMethod[0] == '!';
string methodName = negateEnumMethod ? enumMethod.Substring(1) : enumMethod;
//get the interface sub-type
var itemType = currentType.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
//generate lambda for single item
var itemPredicate = MakeSimplePredicate(itemType, memberNames[++x], comparison, value);
//get method call
var staticMethod = typeof(Enumerable).GetMember(methodName).OfType<MethodInfo>()
.Where(m => m.GetParameters().Length == 2)
.First()
.MakeGenericMethod(itemType);
//generate method call, then break loop for return
left = Expression.Call(null, staticMethod, left, itemPredicate);
right = Expression.Constant(!negateEnumMethod);
comparison = ExpressionType.Equal;
break;
}
}
//build the final expression
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
static LambdaExpression MakeSimplePredicate(Type inputType, string memberName, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(inputType, "t");
Expression left = Expression.PropertyOrField(parameter, memberName);
return Expression.Lambda(Expression.MakeBinary(comparison, left, Expression.Constant(value)), parameter);
}
private static Type SingleLevelFieldType(Type baseType, string fieldName)
{
Type currentType = baseType;
MemberInfo match = (MemberInfo)currentType.GetField(fieldName) ?? currentType.GetProperty(fieldName);
if (match == null) return null;
return GetFieldOrPropertyType(match);
}
public static Type GetFieldOrPropertyType(MemberInfo field)
{
return field.MemberType == MemberTypes.Property ? ((PropertyInfo)field).PropertyType : ((FieldInfo)field).FieldType;
}
/// <summary>
/// Remove qualifying names from a target field. For example, if targetField is "Order.Customer.Name" and
/// targetType is Order, the de-qualified expression will be "Customer.Name" split into constituent parts
/// </summary>
/// <param name="targetField"></param>
/// <param name="targetType"></param>
/// <returns></returns>
public static string[] DeQualifyFieldName(string targetField, Type targetType)
{
return DeQualifyFieldName(targetField.Split('.'), targetType);
}
public static string[] DeQualifyFieldName(string[] targetFields, Type targetType)
{
var r = targetFields.ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r.ToArray();
}
I included related methods in case someone actually needs to sort through this at some point. :)
Thanks again!
Say I have this expression:
int setsize = 20;
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1
|| x.Seed % setsize == 4;
This basically 'partitions' a set of elements into 20 partitions and retrieves from each set each first and fourth element.
This expression is passed to MongoDB which it's driver is perfectly capable of translating into a MongoDB "query". The predicate can, however, also be used on a list of objects (LINQ2Objects) etc. I want this expression to be reusable (DRY). However, I want to be able to pass in an IEnumerable<int> to specify which items to retrieve (so 1 and 4 aren't "hardcoded" into it):
public Expression<Func<Foo, bool>> GetPredicate(IEnumerable<int> items) {
//Build expression here and return it
}
With LINQPad using this code:
int setsize = 20;
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1 || x.Seed % setsize == 4;
predicate.Dump();
}
class Foo
{
public int Seed { get; set; }
I can examine the expression:
Now, I want to be able to build an exact reproduction of this expression but with a variable amount of integers to pass (so instead of 1 and 4 I could pass, for example, [1, 5, 9, 11] or [8] or [1, 2, 3, 4, 5, 6, ..., 16]).
I have tried using BinaryExpressions etc. but haven't been able to construct this message correctly. The main issue is that most of my attempts will fail when passing the predicate to MongoDB. The "hardcoded" version works fine but somehow all my attempts to pass my dynamic expressions fail to be translated into a MongoDB query by the C# driver:
{
"$or" : [{
"Seed" : { "$mod" : [20, 1] }
}, {
"Seed" : { "$mod" : [20, 4] }
}]
}
Basically, I want to dynamically build the expression at runtime in such a way that it exactly replicates what the compiler generates for the 'hardcoded' version.
Any help will be appreciated.
EDIT
As requested in the comments (and posted on pastebin), one of my tries below. I'm posting it in the question for furure reference should pastebin take it down or stop their serivce or...
using MongoRepository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
class Program
{
static void Main(string[] args)
{
MongoRepository<Foo> repo = new MongoRepository<Foo>();
var reporesult = repo.All().Where(IsInSet(new[] { 1, 4 }, 20)).ToArray();
}
private static Expression<Func<Foo, bool>> IsInSet(IEnumerable<int> seeds, int setsize)
{
if (seeds == null)
throw new ArgumentNullException("s");
if (!seeds.Any())
throw new ArgumentException("No sets specified");
return seeds.Select<int, Expression<Func<Foo, bool>>>(seed => x => x.Seed % setsize == seed).JoinByOr();
}
}
public class Foo : Entity
{
public int Seed { get; set; }
}
public static class Extensions
{
public static Expression<Func<T, bool>> JoinByOr<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
{
var firstFilter = filters.First();
var body = firstFilter.Body;
var param = firstFilter.Parameters.ToArray();
foreach (var nextFilter in filters.Skip(1))
{
var nextBody = Expression.Invoke(nextFilter, param);
body = Expression.Or(body, nextBody);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
This results in: Unsupported where clause: <InvocationExpression>.
Try this:
public Expression<Func<Foo, bool>> GetExpression<T>(
int setSize, int[] elements,
Expression<Func<Foo, T>> property)
{
var seedProperty = GetPropertyInfo(property);
var parameter = Expression.Parameter(typeof(Foo));
Expression body = null;
foreach(var element in elements)
{
var condition = GetCondition(parameter, seedProperty, setSize, element);
if(body == null)
body = condition;
else
body = Expression.OrElse(body, condition);
}
if(body == null)
body = Expression.Constant(false);
return Expression.Lambda<Func<Foo, bool>>(body, parameter);
}
public Expression GetCondition(
ParameterExpression parameter, PropertyInfo seedProperty,
int setSize, int element)
{
return Expression.Equal(
Expression.Modulo(Expression.Property(parameter, seedProperty),
Expression.Constant(setSize)),
Expression.Constant(element));
}
public static PropertyInfo GetPropertyInfo(LambdaExpression propertyExpression)
{
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");
var body = propertyExpression.Body as MemberExpression;
if (body == null)
{
throw new ArgumentException(
string.Format(
"'propertyExpression' should be a member expression, "
+ "but it is a {0}", propertyExpression.Body.GetType()));
}
var propertyInfo = body.Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException(
string.Format(
"The member used in the expression should be a property, "
+ "but it is a {0}", body.Member.GetType()));
}
return propertyInfo;
}
You would call it like this:
GetExpression(setSize, elements, x => x.Seed);
If you want it to be generic in Foo also, you need change it like this:
public static Expression<Func<TEntity, bool>> GetExpression<TEntity, TProperty>(
int setSize, int[] elements,
Expression<Func<TEntity, TProperty>> property)
{
var propertyInfo = GetPropertyInfo(property);
var parameter = Expression.Parameter(typeof(TEntity));
Expression body = null;
foreach(var element in elements)
{
var condition = GetCondition(parameter, propertyInfo , setSize, element);
if(body == null)
body = condition;
else
body = Expression.OrElse(body, condition);
}
if(body == null)
body = Expression.Constant(false);
return Expression.Lambda<Func<TEntity, bool>>(body, parameter);
}
Now, the call would look like this:
GetExpression(setSize, elements, (Foo x) => x.Seed);
In this scenario it is important to specify the type of x explicitly, otherwise type-inference won't work and you would have to specify both Foo and the type of the property as generic arguments to GetExpression.