Entity Framework - dynamic query - entity-framework

I'm creating a dynamic expression builder and trying to implement the 'like' function. Before writing my own I've searched for any existing function and found one close to my need. After several experiments I couldn't bring it to run for types other than string.
When I pass a parameter of type int then I get this error:
Method 'System.String ToString()' declared on type 'System.String' cannot be called with instance of type 'System.Int32'
My code looks like this:
private static MethodCallExpression GetLowerCasePropertyAccess(MemberExpression propertyAccess)
{
//return Expression.Call(Expression.Call(propertyAccess, "ToString", new Type[0]), typeof(string).GetMethod("ToLower", new Type[0]));
return Expression.Call(Expression.Call(propertyAccess, typeof(string).GetMethod("ToString", System.Type.EmptyTypes)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
}
private static readonly MethodInfo ContainsMethod = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
public static Expression<Func<T, bool>> Create<T>(string propertyName, ComparisonOperators comparisonOperator, dynamic comparedValue1, dynamic comparedValue2 = null)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "x");
MemberExpression memberExpression = Expression.MakeMemberAccess(parameterExpression, typeof(T).GetProperty(propertyName));
ConstantExpression constantExpression = Expression.Constant(comparedValue1, comparedValue1.GetType());
Expression expressionBody = null;
switch (comparisonOperator)
{
...
case ComparisonOperators.Contains:
//var indexOf = Expression.Call(memberExpression, "IndexOf", null, Expression.Constant(comparedValue1, typeof(string)), Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
//expressionBody = Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
expressionBody = Expression.Call(GetLowerCasePropertyAccess(memberExpression), ContainsMethod, Expression.Constant(comparedValue1.ToLower()));
break;
}
return Expression.Lambda<Func<T, bool>>(expressionBody, new ParameterExpression[] { parameterExpression });
}

I'm not sure I fully understand what you're doing, but I think your error is caused by this line:
return Expression.Call(Expression.Call(propertyAccess, typeof(string).GetMethod("ToString", System.Type.EmptyTypes)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Which will always try and call the ToString method for a string type, so if you try and use an Int32 property, you're then trying to call String.ToString(), since the implementation of ToString() will be different for different types and the two implementations will not necessarily be compatible, you'll get the exception you're seeing:
Method 'System.String ToString()' declared on type 'System.String' cannot be called with instance of type 'System.Int32'
From what it looks like you're doing, I think this may be what you're after:
return Expression.Call(Expression.Call(propertyAccess, propertyAccess.Type.GetMethod("ToString", System.Type.EmptyTypes)), typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Which will use the correct implementation of ToString (with type obtained from propertyAccess.Type).

Linq to entities doesn't support .ToString method. For converting numeric values to string you need to use SqlFunctions.StringConvert method. I've fixed your code and now you can do like on string and numeric columns:
private static Expression GetConvertToStringExpression(Expression e)
{
// if property string - no cast needed
// else - use SqlFunction.StringConvert(double?) or SqlFunction.StringConvert(decimal?);
Expression strExpression = null;
if (e.Type == typeof(string))
strExpression = e;
var systemType = Nullable.GetUnderlyingType(e.Type) ?? e.Type;
if (systemType == typeof(int)
|| systemType == typeof(long)
|| systemType == typeof(double)
|| systemType == typeof(short)
|| systemType == typeof(byte)) // continue
{
// cast int to double
var doubleExpr = Expression.Convert(e, typeof (double?));
strExpression = Expression.Call(StringConvertMethodDouble, doubleExpr);
}
if (systemType == typeof (decimal))
{
// call decimal version of StringConvert method
// cast to nullable decimal
var decimalExpr = Expression.Convert(e, typeof (decimal?));
strExpression = Expression.Call(StringConvertMethodDecimal, decimalExpr);
}
return strExpression;
}
private static MethodCallExpression GetLowerCasePropertyAccess(Expression propertyAccess)
{
var stringExpression = GetConvertToStringExpression(propertyAccess);
if (stringExpression == null)
throw new Exception(string.Format("Not supported property type {0}", propertyAccess.Type));
return Expression.Call(stringExpression,
typeof (string).GetMethod("ToLower", Type.EmptyTypes));
}
private static readonly MethodInfo StringConvertMethodDouble = typeof (SqlFunctions).GetMethod("StringConvert",
new Type[] {typeof (double?)});
private static readonly MethodInfo StringConvertMethodDecimal = typeof(SqlFunctions).GetMethod("StringConvert",
new Type[] { typeof(decimal?) });

I have just made something like that:
public Expression<Func<T,bool>> BuildContainsExpression<T>(MemberExpression memberExp, object comparedValue)
{
var parameter = Expression.Parameter(memberExp.Member.DeclaringType, "x");
var method = typeof(string).GetMethod("Contains", types: new[] { typeof(string) });
var comparison = Expression.Equal(
Expression.Call(
method: method,
instance: memberExp,
arguments: Expression.Constant(comparedValue)),
Expression.Constant(true)
);
return Expression.Lambda<Func<T, bool>>(comparison, parameter);
}
And it builds an expression like below:
x.Language.Contains("tr")
(with my dynamic parameters)

Related

Build an Expression Tree for Entity Framework Many to Many relationship?

I'm working on a Code for the following structure:
3 Tables
Table "Book", "BookAuthor" and "Author".
I now want to build up an ExpressionTree to get the following Linq-result:
_ent.Book.Where(c => c.BookAuthor.Any(cd => cd.Author.AuthorName == "test"));
My Problem is the last Expression to get to cd.Author.AuthorName (or similar).
My current code:
private MethodCallExpression BuiltMethodCall(IQueryable _query, string CustTypeID, Type _ParentObjType,
Type _ChildObjType, string strChildObj, string strChildCol)
{
//This function will build a dynamic linq expression tree representing the ling calls of:
//Book.Where(c => c.BookAuthor.Any(cd => cd.Author = custTypeID))
ConstantExpression value = Expression.Constant(CustTypeID);
//Build the outer part of the Where clause
ParameterExpression parameterOuter = Expression.Parameter(_ParentObjType, "c");
MemberExpression propertyOuter = Expression.Property(parameterOuter, strChildObj);
//Build the comparison inside of the Any clause
ParameterExpression parameterInner = Expression.Parameter(_ChildObjType, "cd");
MemberExpression propertyInner = Expression.Property(parameterInner, strChildCol);
BinaryExpression comparison = Expression.Equal(propertyInner, value);
LambdaExpression lambdaInner = Expression.Lambda(comparison, parameterInner);
//Create Generic Any Method
Func<MethodInfo, bool> methodLambda = m => m.Name == "Any" && m.GetParameters().Length == 2;
MethodInfo method = typeof(Enumerable).GetMethods().Where(methodLambda).Single()
.MakeGenericMethod(_ChildObjType);
//Create the Any Expression Tree and convert it to a Lambda
MethodCallExpression callAny = Expression.Call(method, propertyOuter, lambdaInner);
LambdaExpression lambdaAny = Expression.Lambda(callAny, parameterOuter);
//Build the final Where Call
MethodCallExpression whereCall = Expression.Call(typeof(Queryable), "Where",
new Type[] { _query.ElementType }, new Expression[]
{
_query.Expression,
Expression.Quote(lambdaAny)
});
return whereCall;
}
If I understand correctly, you are asking how to create nested property accessor expression.
It's quite easy with the help of String.Split and Enumerable.Aggregate methods.
Here is a custom extension method which can be used as Expression.Property method replacement:
public static partial class ExpressionExtensions
{
public static MemberExpression Property(this Expression instance, string path)
{
return (MemberExpression)path.Split('.').Aggregate(instance, Expression.Property);
}
}
It handles correctly both simple and nested properties encoded in string with . separator (e.g. "BookAuthor" or "Author.AuthorName").
So instead of
MemberExpression propertyOuter = Expression.Property(parameterOuter, strChildObj);
and
MemberExpression propertyInner = Expression.Property(parameterInner, strChildCol);
you can safely use
MemberExpression propertyOuter = parameterOuter.Property(strChildObj);
and
MemberExpression propertyInner = parameterInner.Property(strChildCol);

Entity framework ordering using reflection

I have an extension method for ordering, the sortExpression can be something like "Description" or "Description DESC", it is working perfectly for columns at the same table:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
{
if (source == null)
throw new ArgumentNullException("source");
if (string.IsNullOrEmpty(sortExpression))
return source;
var parts = sortExpression.Split(' ');
var isDescending = false;
var propertyName = "";
var type = typeof(T);
if (parts.Length > 0 && parts[0] != "")
{
propertyName = parts[0];
if (parts.Length > 1)
isDescending = parts[1].ToLower().Contains("esc");
var prop = type.GetProperty(propertyName);
if (prop == null)
throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, type.Name));
var funcType = typeof(Func<,>)
.MakeGenericType(type, prop.PropertyType);
var lambdaBuilder = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
.MakeGenericMethod(funcType);
var parameter = Expression.Parameter(type);
var propExpress = Expression.Property(parameter, prop);
var sortLambda = lambdaBuilder
.Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });
var sorter = typeof(Queryable)
.GetMethods()
.FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { type, prop.PropertyType });
var result = (IQueryable<T>)sorter
.Invoke(null, new object[] { source, sortLambda });
return result;
}
return source;
}
Working Example:
var query = db.Audit.Include("AccessLevel").AsQueryable();
query = query.OrderBy("Description");
Please note that the "Description" column exists at the same table "Audit".
What I'm trying to do is to sort by a column in a relation table:
Like the following
var query = db.Audit.Include("AccessLevel").AsQueryable();
query = query.OrderBy("AccessLevel.Name");
Which is equivalent to:
query = query.OrderBy(o => o.AccessLevel.Name);
What is the required modification on my extension method ?
I solved it by using the following code:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, parts[0], "OrderBy");
}
public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
if (source == null)
throw new ArgumentNullException("source");
if (string.IsNullOrEmpty(property))
return source;
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IQueryable<T>)result;
}

Sort using Expression builder

I am trying to build generic sort method using Expressions.
I came up with the following method. For some reasons the code is breaking at the statement when a nested property is used in the sort expression.
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(sortParam, sortColumn), typeof(object)), sortParam);
private static IQueryable<T> SortQuery<T>(IQueryable<T> query, string sortColumn)
{
if (!string.IsNullOrEmpty(sortColumn))
{
var sortParam = Expression.Parameter(typeof(T), "x");
Expression expr = sortParam;
foreach (var prop in sortColumn.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(sortParam, sortColumn), typeof(object)), sortParam);
return query.OrderBy(sortExpression);
}
return null;
}
Any idea where I am doing it wrong ?
Try:
private static readonly MethodInfo OrderBy = (from x in typeof(Queryable).GetMethods()
where x.Name == "OrderBy"
let pars = x.GetParameters()
where pars.Length == 2
select x).Single();
private static IQueryable<T> SortQuery<T>(IQueryable<T> query, string sortColumn)
{
if (!string.IsNullOrEmpty(sortColumn))
{
var sortParam = Expression.Parameter(typeof(T), "x");
Expression expr = sortParam;
foreach (var prop in sortColumn.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
var lambda = Expression.Lambda(expr, sortParam);
var orderBy = OrderBy.MakeGenericMethod(typeof(T), expr.Type);
return (IQueryable<T>)orderBy.Invoke(null, new object[] { query, lambda });
}
return null;
}
You have to use reflection to invoke the Queryable.OrderBy. In general, casting to object doesn't work very well with Entity Framework/LINQ to SQL, so you can try it, but there is only a very little chance it will work.

EF6 Convert IQueryable to DbQuery

In Entity framework 6, when running the overload of the Include method that uses lambda expression to the context:
Context.SomeEntity.Include(x => x.MyOtherEntity))
it returns an IQueryable, whereas when we use the one that uses string:
Context.SomeEntity.Include("MyOtherEntity")
it returns a DbQuery.
I need to return a DbQuery and don't want to use the string overload so that I can get inclusion errors at compile time.
How can I return a DbQuery after using the include with the lambda?
I believe you can't convert an IQueryable to a DbQuery. However, you can use this code to pass in an expression and get the required string.
Be sure to write some unit tests for this and adapt the method for your needs. I have not tested it properly yet.
public static string GetMemberName<T>(this Expression<Func<T>> expression)
{
MemberExpression memberExp;
if (!TryFindMemberExpression(expression.Body, out memberExp))
return string.Empty;
var memberNames = new Stack<string>();
do
{
memberNames.Push(memberExp.Member.Name);
}
while (TryFindMemberExpression(memberExp.Expression, out memberExp));
return string.Join(".", memberNames.ToArray());
}
private static bool TryFindMemberExpression(Expression exp, out MemberExpression memberExp)
{
memberExp = exp as MemberExpression;
if (memberExp != null)
{
return true;
}
if (IsConversion(exp) && exp is UnaryExpression)
{
memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
if (memberExp != null)
{
return true;
}
}
return false;
}
private static bool IsConversion(Expression exp)
{
return (exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked);
}

mvc4 expression tree for contains integer

I want to implement expression tree for integers as in below query:
Select * From TableName Where PKID In (1,2,3,4,5)
The relevant extracted code:
protected Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue, string propertyType)
{
var parameter = Expression.Parameter(typeof(T), "type");
switch (propertyType)
{
case "string":
case "string?":
var stringProperty = Expression.Property(parameter, propertyName);
var stringValue = Expression.Constant(propertyValue.Trim(), typeof(string));
MethodInfo stringMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var stringContainsMethod = Expression.Call(stringProperty, stringMethod, stringValue);
return Expression.Lambda<Func<T, bool>>(stringContainsMethod, parameter);
case "int?":
var intProperty = Expression.Property(parameter, propertyName);
var intValues = Expression.Constant(propertyValue.Trim(), typeof(string));
MethodInfo intMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var intContainsMethod = Expression.Call(intProperty, intMethod, intValues); // this line gives error
return Expression.Lambda<Func<T, bool>>(intContainsMethod, parameter);
}
}
// Calling above function for string works. Products is a table in Database and Entity framework generated the class based on database first.
var filterLambda = GetExpression<Products>("ProductName", "chicken", "string?"); // No error here, works great
// Calling above function for int gives error
var filterLambda = GetExpression<Products>("ProductId", "1,2,3,4,5", "int?"); // Error here
I get error:
Method 'Boolean Contains(System.String)' declared on type 'System.String' cannot be called with instance of type 'System.Int32'
Of course it is pretty much clear that the column ProductId is integer and the value is string. Integer doesnt have contains so I thought I should use strings contains method. Please can any one tell me how do I solve this?