I have been fiddling with dynamic LINQ for some time now, but I have yet to learn its secrets.
I have an expression that I want to parse that looks like this:
"document.LineItems.Select(i => i.Credit).Sum();"
During parsing of this I reach a point where I need to call a Select function on LineItems Collection. I am using factory method of Expression.Call:
Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(LineItem), typeof(decimal?) },
expr,
Expression.Lambda(expression, new ParameterExpression[] { Expression.Parameter(typeof(LineItem) }));
At this moment
expr: document.LineItems [ICollection<LineItem>]
expression: LineItem.Credit [decimal?]
none of which is materialized yet. I am just building Expression Tree at the moment.
Now, the problem:
This Expresssion.Call throws and Exception: "No generic method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments";
I resolve it easily by looking for 'Select' in 'System.Linq.Enumerable' instead of 'Queryable' by changing first argument of Expression.Call.
But, that's not quite that I want. I don't want all LineItems hauled in only to calculate Sum(), which would obviously be case with Enumerable. I want Queryable to work.
Also, for the last part of parsing - Sum(), I also need to go with Enumerable Sum(), because Queryable Sum() throws same Exception.
I have checked MSDN, both signatures of 'Select' function are identical, so i really cannot see why would one work and other not.
Any help or pointers would be aprreciated.
Regards,
The problem is that LineItems is an ICollection<LineItem>, while the first parameter to Queryable.Select requires an IQueryable<LineItem>. ICollection<T> only implements IEnumerable<T> which is why it works for Enumerable.Select.
You will need to change the type of expr to be IQueryable<LineItem>.
You should be able to do this with the Queryable.AsQueryable method. The following function creates an expression to sum the credit property values for a given Document instance:
public static Expression<Func<decimal?>> CreateSumLineItemsExpr(Document document)
{
var docExpr = Expression.Constant(document);
var itemsExpr = Expression.Property(docExpr, "LineItems");
Expression<Func<LineItem, decimal?>> selector = i => i.Credit;
var queryableExpr = Expression.Call(typeof(Queryable), "AsQueryable", new[] { typeof(LineItem) }, itemsExpr);
var selectExpr = Expression.Call(typeof(Queryable), "Select", new[] { typeof(LineItem), typeof(decimal?) }, queryableExpr, selector);
var sumExpr = Expression.Call(typeof(Queryable), "Sum", null, selectExpr);
return Expression.Lambda<Func<decimal?>>(sumExpr);
}
Related
I'd like to streamline my current view model mapping. I currently use Automapper ProjectTo to create my view model, then call a post mapping method to populate a StatusDescription string property from a Status enum whose value is in another int property in the view model class, StatusId.
I found this other answer, but I am not able to get it to work, and am having a hard time understanding what exactly it is doing due to my general lack of experience with expressions.
For reference, here is the expression method (note I had to comment out the defaultValue parameter as I was getting an error An expression tree may not contain a call or invocation that uses optional arguments:
public static Expression<Func<TSource, String>> CreateEnumToStringExpression<TSource, TMember>(
Expression<Func<TSource, TMember>> memberAccess)//,string defaultValue = ""
{
string defaultValue = "";
var type = typeof(TMember);
if (!type.IsEnum)
{
throw new InvalidOperationException("TMember must be an Enum type");
}
var enumNames = Enum.GetNames(type);
var enumValues = (TMember[])Enum.GetValues(type);
var inner = (Expression)Expression.Constant(defaultValue);
var parameter = memberAccess.Parameters[0];
for (int i = 0; i < enumValues.Length; i++)
{
inner = Expression.Condition(
Expression.Equal(memberAccess.Body, Expression.Constant(enumValues[i])),
Expression.Constant(enumNames[i]),
inner);
}
var expression = Expression.Lambda<Func<TSource, string>>(inner, parameter);
return expression;
}
I am calling it like this:
.ForMember(dest => dest.Status, option =>
option.MapFrom(src =>
EnumHelper.ExpressionHelper
.CreateEnumToStringExpression((ShippingContainerHeader s) => s.StatusId)
))
The error I am getting when I run this is:
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression..
A.) I think I am calling this wrong maybe? Shouldn't I be using the reference to src in the MapFrom method call in order to pass the actual value of StatusId to the expression method?
B.) Seems like there is an issue with the expression generation method as it is producing the ToString() error. There is not an explicit .ToString() call in that method, but it must be implied somehow.
C.) Understanding the code: I can follow all of the code within the CreateEnumToStringExpression method except what the parameter variable is and the final expression variable expression statement. Also the generic types I'm confused on...there are a lot of generic types and the call to the method from the linked example doesn't explicitly define any of them.
In Expression<Func<TSource, String>> I'm pretty sure TSource is the input, which is an int. String is the output, which would be the enum name.
In CreateEnumToStringExpression<TSource, TMember> and Expression<Func<TSource, TMember>> memberAccess: TSource again is int, TMember is the enum class type.
But do you not have to explicitly call these out when calling the method? Something like: CreateEnumToStringExpression<int, StatusesEnum>(new Expression<Func<int, ShippingContainerHeader>> { s => s.StatusId })
Just to help with the question I am trying to find a way to search a database column 'ID'for any row that has a match to a list of ints.
for example the db might have id's 1, 2, 3, 4, 5 6
and i have a list of ints { 1 , 3, 5 } and i want to find any of the ids in the list
I have so far...
//list of ints
dynamic constant = Expression.Constant(value);
List<int> ids = constant.Value;
Expression<Func<Invoice, bool>> test = (Invoice inv) => ids.Contains(inv.ID);
return Expression.Call(typeof(Enumerable), "Any", new Type[] { }, constant, test);
I then add this expression to any of the others and call
Expression.Lambda<Func<Invoice, bool>>(finalexpression, parameter);
The error I am getting is
No method 'Any' on type 'System.Linq.Enumerable' is compatible with the supplied arguments.
Any help is appreciated thanks in advance
Enumerable.Any is a generic method with one generic type argument. You should supply that argument to the Expression.Call overload used through Type[] typeArguments parameter:
return Expression.Call(
typeof(Enumerable), "Any", new Type[] { typeof(Invoice) } /* <== here */,
constant, test);
P.S. These two lines
var constant = Expression.Constant(value);
List<int> ids = constant.Value;
look suspicious (the second line does not compile at all). To make the sample working, the List<int> ids variable must be initialized somewhere else and the value variable should hold IEnumerable<Invoice> instance.
I'm attempting to write an expression tree which can allow dynamic use of a StartsWith() method on non-string valued columns using Entity Framework.
E.g. IntegerValuedColumn.StartsWith(5) would return 500, 5000, 555, 5123, etc
I am trying to write an expression tree based on this answer:
How do I query an integer column for "starts with" in Entity Framework?
Here is what I have so far:
MethodInfo stringConvert = typeof(SqlFunctions).GetMethod("StringConvert", new[] { typeof(double?) });
Expression castExpression = Expression.Convert(propertyExpression, typeof(double?));
Expression convertExpression = Expression.Call(null, stringConvert, castExpression);
MethodInfo trimStart = typeof(string).GetMethod("TrimStart");
Expression nullExpression = Expression.Constant(null, typeof(char[]));
Expression trimExpression = Expression.Call(convertExpression, trimStart, nullExpression);
MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
Expression methodExpression = Expression.Call(trimExpression, startsWith, constantExpression);
return methodExpression;
When I compile and run this expression, I get the following exception:
The method 'System.String TrimStart(Char[])' is only supported in LINQ to Entities when there are no trim characters specified as arguments.
In the original example, the expression is:
SqlFunctions.StringConvert((double)x.AccountNumber)
.TrimStart().StartsWith(searchTerm)
But what I get comes out as:
StringConvert(Convert(x.AccountNumber)).TrimStart(null).StartsWith(searchTerm)
I have removed the two lines dealing with TrimStart (nullExpression and trimExpression) and verified that the statement runs (excluding the thought that the error is being caused by the different language use). My theory based on the exception message is that the TrimStart() method wants to be called with zero parameters, but when I try that the Expression builder tells me that the incorrect number of parameters have been passed in so I figure I'm just missing something.
How would I go about calling the TrimStart method like TrimStart() instead of TrimStart(null) or TrimStart(new char[0]) using expression trees?
Given the following method signature:
string GetActionName<TController, T1, TResult>(Expression<Func<TController, Func<T1, TResult>>> expression)
How can I get the method name from the following implementation:
GetActionName<EventsController, int, IEnumerable<EventDto>>(c => c.GetEventsByIdLocation);
Where GetEventsById has the signature of:
IEnumerable<EventDto> GetEventsByIdLocation(int id)
My current attempt is giving me the exception:
Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'.
var convertExpression = (UnaryExpression)expression.Body;
var memberExpression = (MemberExpression)convertExpression.Operand;
return memberExpression.Member.Name;
The main problem is that in your call, you are not actually calling the method, but returning it. This caused the expression tree to contain a call to a method called CreateDelegate.
Once we have that in our methodCallExpression we need to extract the object, and read its value, which is your method.
Disclaimer: This worked for me with a scenario that I think was equal to yours. If it, however, is the best way to solve the larger problem I do not know. It seems to be fairly slow.
var unaryExpression = (UnaryExpression)expression.Body;
var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
var constantExpression = (ContantExpression)methodCallExpression.Object;
var methodInfo = (MethodInfo)constantExpression.Value;
return methodInfo.Name
Another option would be to provide an actual method call to the method, like so:
GetActionName<EventsController, IEnumerable<EventDto>>(
c => c.GetEventsByIdLocation(0));
Which would require you to change the method to:
string GetActionName<TController, TResult>(
Expression<Func<TController, TResult>> expression)
{
return ((MethodCallExpression)expression.Body).Method.Name;
}
This solution will probably perform a lot faster. Allthough I havn't done any benchmarks or anything. Also, this option does not tie you to only being able to get the names of methods that take one int parameter.
The following code is a helper I've created for a system which allows administrators to create their own queries on a database. It returns a lambda expression based on the method and value provided.
I am attempting to find a way to pass parameters to the method call - in this example I am using the StartsWith parameter of String, and attempting to set StringComparison.OrdinalIgnoreCase as a parameter.
There will be others parameters required too, depending on the type of the property specified. I'm hoping that understanding the method of supplying the string comparison property will enable me to add the rest later.
The underlying code works correctly, I just need it to be case-insensitive.
I have used this question as a guide, but the solution does not seem applicable here.
Here is the code:
public static class LambdaExpressionHelper<T> {
public static Expression<Func<T, bool>> Build(string propertyName, string method, string propertyValue) {
PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(propertyValue, m.Type);
MethodInfo mi = m.Type.GetMethod(method, new Type[] { m.Type }, );
// The below caused errors
//object classInstance = Activator.CreateInstance(typeof(T), null);
//object[] paramArray = new object[] { StringComparison.OrdinalIgnoreCase };
//mi.Invoke(classInstance, paramArray);
Expression call = Expression.Call(m, mi, c);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
return lambda;
}
}
// External code:
var lambda = LambdaExpressionHelper<MailingListMember>.Build("EmailAddress", "StartsWith", "RoRy#");
Thanks for any help.
In the general case this should work:
public static Expression<Func<T, bool>>
Build(string propertyName, string method, params object[] args)
{
var propertyInfo = typeof(T).GetProperty(propertyName);
var e = Expression.Parameter(typeof(T), "e");
var m = Expression.MakeMemberAccess(e, propertyInfo);
var mi = m.Type.GetMethod(method, args.Select(a => a.GetType()).ToArray());
var c = args.Select(a => Expression.Constant(a, a.GetType())).ToArray();
Expression call = Expression.Call(m, mi, c);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
return lambda;
}
The idea is that you accept any number of constant arguments, and use their types to select the appropriate overload when calling GetMethod. After that, you create the appropriate number of constant expressions from those arguments to pass to Expression.Call.
So with the above code, you could do:
var l1 = LambdaExpressionHelper<MailingListMember>.Build(
"EmailAddress", "StartsWith", "RoRy#");
var l2 = LambdaExpressionHelper<MailingListMember>.Build(
"EmailAddress", "StartsWith", "RoRy#", StringComparison.OrdinalIgnoreCase);
If you also need to get the value StringComparison.OrdinalIgnoreCase from the string "StringComparison.OrdinalIgnoreCase", I would factor this out into a separate method so that the interface of Build can remain generic. Exactly how to do it is covered in Getting Enum value via reflection (and I guess in lots of similar questions as well).
Note: I don't have convenient access to a compiler right now, so please excuse any mistakes.
I'm not sure i understand your question exactly, but hopefully this will help you on the way.
If you are using .NET 4.0 you can use the new keyword "dynamic" for much easier reflection code:
dynamic myDynamicObj = "Hello World!"; // or Activator.CreateInstance...
var doesIndeed = myDynamicObj.StartsWith("Hello", StringComparison.OrdinalIgnoreCase);
This does evaluate at run time so make sure spelling/case etc is correct.
Edit:
Assuming you always wanted to call methods with one String-arg returning bool, a possible solution would be to create a delegate type -
delegate bool CompareString(String str);
and than have the second argument of Build as that type:
Build(String .., CompareString cs, String ...)
But this does not work if you need to add extra arguments, as in the second arg of type StringComparison. A if/switch could be the answer there though, i.e if(CompareString is StartsWith)...
Sorry, not at a windows-computer so i can't test further.