Dynamic linq query expression tree for sql IN clause using Entity framework
Asked Answered
S

2

8

I want to create a dynamic linq expression for sql IN clause in EF 6.0 with code first approch. Note that i am new to Expressions. What i want to achive is

select * from Courses where CourseId in (1, 2, 3, 4)
//CourseId is integer

The normal linq query looks like this. But i want to query it dynamically

string[] ids = new string[]{"1", "2", "3", "4"};
var courselist = DBEntities.Courses.Where(c => ids.Contains(SqlFunctions.StringConvert((decimal?)c.CourseId)))

There are two ways to make dynamic expression.
1) one ways is to loop through ids and make expressions
The below code will create the following expression in debug view

{f => ((StringConvert(Convert(f.CourseId)).Equals("23") Or StringConvert(Convert(f.CourseId)).Equals("2")) Or StringConvert(Convert(f.CourseId)).Equals("1"))}

Dynamic Expression is

var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");                   
MethodInfo mi = null;
MethodCallExpression mce = null;
if (property.Type == typeof(int))
{
   var castProperty = Expression.Convert(property, typeof(double?));
   var t = Expression.Parameter(typeof(SqlFunctions), "SqlFunctions");
   mi = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });   
   mce = Expression.Call(null,mi, castProperty);
}
mi = typeof(string).GetMethod("Equals", new Type[]{ typeof(string)});            
BinaryExpression bex = null;
if (values.Length <= 1)
{
   return Expression.Lambda<Func<T, bool>>(Expression.Call(mce, mi,     Expression.Constant(values[0]), param));
}
//var exp1 = Expression.Call(mce, mi, Expression.Constant(values[0]));
for (int i = 0; i < values.Length; i++)
{               
   if (bex == null)
   {
      bex = Expression.Or(Expression.Call(mce, mi, Expression.Constant(values[i])), Expression.Call(mce, mi, Expression.Constant(values[i + 1])));
      i++;
   }
   else             
      bex = Expression.Or(bex, Expression.Call(mce, mi, Expression.Constant(values[i])));

}//End of for loop
return Expression.Lambda<Func<T, bool>>(bex, param);

2) The 2nd way that i tried (debug view)

{f => val.Contains("23")} //val is parameter of values above
The dynamic expression for above that i tried is

var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId"); 
var micontain = typeof(Enumerable).GetMethods().Where(m => m.Name == "Contains" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
var mc = Expression.Call(micontain, Expression.Parameter(values.GetType(), "val"), Expression.Constant("2"));//NOTE: I haven't use CourseId for now as i am getting conversion error
return Expression.Lambda<Func<T, bool>>(mc, param);

I get the following errors

  • LINQ to Entities does not recognize the method 'System.String StringConvert(System.Nullable`1[System.Double])' method, and this method cannot be translated into a store expression when i use the first methodology. I know i can't use ToString with EF thats why I used SqlFunctions but it is not working for me.
  • The parameter 'val' was not bound in the specified LINQ to Entities query expression using 2nd methodology

I am trying this from last 4 days. I googled it but didn't find any suitable solution. Please help me.

Slickenside answered 23/10, 2013 at 6:38 Comment(0)
S
10

After a lot of struggle I found solution to my question. I want to achieve this sql query

select * from Courses where CourseId in (1, 2, 3, 4)

Using Linq to Entities, but I want to pass in(1,2,3,4) list dynamically to linq query. I created an Extension class for that purpose.

public static class LinqExtensions
{
   public static Expression<Func<T, bool>> False<T>() { return f => false; } 
   public static Expression<Func<T, bool>> In<T, TValue>(this Expression<Func<T, bool>> predicate,string propertyName, List<TValue> values)
   {            
           var param = predicate.Parameters.Single();
           MemberExpression property = Expression.PropertyOrField(param, propertyName);            
           var micontain = typeof(List<TValue>).GetMethod("Contains");            
           var mc = Expression.Call(Expression.Constant(values), micontain, property);
           return Expression.Lambda<Func<T, bool>>(mc, param);
   }
}

Use of LinqExtensions

var pred = LinqExtensions.False<Course>(); //You can chain In function like  LinqExtensions.False<Course>().In<Course, int>("CourseId", inList);
var inList= new List<int>(){1, 2, 3}; //Keep in mind the list must be of same type of the Property that will be compared with. In my case CourseId is integer so the in List have integer values
pred =pred.In<Course, int>("CourseId", inList); //TValue is int. As CourseId is of type int.
var data = MyEntities.Courses.Where(pred);

I hope this might be beneficial for some one

Slickenside answered 28/11, 2013 at 9:12 Comment(0)
L
0

have you seen the type of

var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId)))

above statement would not return actual list of courses. The query is not executed yet. It just returns IQuereable. The query is executed when you actually call .ToList() method on it

so, your solution is..

  1. Create array of IDs using for loop and then simply run the below query

  2. var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId))).ToList()

Lipinski answered 23/10, 2013 at 7:5 Comment(1)
Well i know that it will return IQuerable not actual list. but you didn't get my question. I want to do same thing ids.Contains(c.CourseId) dynamically using Expression treeSlickenside

© 2022 - 2024 — McMap. All rights reserved.