This question already has answers here:
Generate EF orderby expression by string [duplicate]
(6 answers)
Closed 7 years ago.
I am using .NET 4.51, EF 6
I make a number of calls to my repository layer where I need to do some basic ordering on a single field in either ascending or descending order such as:
The result of GetAllList() is a List<T>. Now unfortunately the Id field I have to sort by is not always called Id nor is the Text field. They can be other things such as MyId, SomeTextField and so on.
So I was wondering if there was a way I could do the OrderBy() and OrderByDescending() clauses by supplying a string for the field name something like:
_Repository.GetAllList().OrderBy(r => r."SomeTextField")
In this way I could move all this code to a common method.
Any pointers greatly appreciated.
This will work:
public static class LinqExtensions
{
private static PropertyInfo GetPropertyInfo(Type objType, string name)
{
var properties = objType.GetProperties();
var matchedProperty = properties.FirstOrDefault (p => p.Name == name);
if (matchedProperty == null)
throw new ArgumentException("name");
return matchedProperty;
}
private static LambdaExpression GetOrderExpression(Type objType, PropertyInfo pi)
{
var paramExpr = Expression.Parameter(objType);
var propAccess = Expression.PropertyOrField(paramExpr, pi.Name);
var expr = Expression.Lambda(propAccess, paramExpr);
return expr;
}
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> query, string name)
{
var propInfo = GetPropertyInfo(typeof(T), name);
var expr = GetOrderExpression(typeof(T), propInfo);
var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
return (IEnumerable<T>) genericMethod.Invoke(null, new object[] { query, expr.Compile() });
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string name)
{
var propInfo = GetPropertyInfo(typeof(T), name);
var expr = GetOrderExpression(typeof(T), propInfo);
var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
return (IQueryable<T>) genericMethod.Invoke(null, new object[] { query, expr });
}
}
Testing:
var r = new List<temp> {
new temp { a = 5 },
new temp { a = 1 },
new temp { a = 15 }
}.OrderBy("a");
Gives the correct result (1, 5, 15) - and will provide lazy execution for your use with EF
You will need to implement the overloads if needed.
Does it have to be a string? Why not just make a method that takes a Func key selector as a parameter.
public List<T> GetAllListOrdered<T,TProp>(SimpleOrderingDirectionEnum direction, Func<T,TProp> keySelector)
{
return direction == SimpleOrderingDirectionEnum.Ascending ? _Repository.GetAllList().OrderBy(keySelector).ToList() : _Repository.GetAllList().OrderByDescending(keySelector).ToList();
}
Then call it like
Func<ObjectToSortType, ObjectPropertyToSortBy> keySelector = r => r.Id;
GetAllListOrdered(SimpleOrderingDirectionEnum.Ascending, keySelector);
If the Rob's answer is not enough for you. Try Linq Dynamic. http://dynamiclinq.azurewebsites.net/
using System.Linq.Dynamic; //Import the Dynamic LINQ library
//The standard way, which requires compile-time knowledge
//of the data model
var result = myQuery
.Where(x => x.Field1 == "SomeValue")
.Select(x => new { x.Field1, x.Field2 });
//The Dynamic LINQ way, which lets you do the same thing
//without knowing the data model before hand
var result = myQuery
.Where("Field1=\"SomeValue\"")
.Select("new (Field1, Field2)");
Thanks to all. Rob, your solution was pretty close to what I ended up with.
Based on your insights I did some more searching and came across Marc Gravel's answer here Dynamic LINQ OrderBy on IEnumerable<T> (second post).
It added dynamic's as an additional bonus.
Related
I'm trying to convert the following using Expressions so I could engineer it to be more generic and work over different collections with differing properties:
var roleId = 1;
users.Where(u => u.Roles.Any(ur => ur.Id == roleId));
I have created an "Any" expression but I'm unsure how to move this within an Expression.Call to "Where":
var predicateParameter = Expression.Parameter(typeof(ListItem), "ur");
var left = Expression.Property(predicateParameter, "Id");
var right = Expression.Constant(roleId, typeof(int));
var expression = Expression.Equal(left, right);
var anyCheckFunction = Expression.Lambda<Func<ListItem, bool>>(expression, predicateParameter);
Expression<Func<UserDto, Expression<Func<int, int, int>>, Expression<Func<int>>>> power2 =
(o, f) => Expression.Lambda<Func<int>>(Expression.Invoke(f, Expression.Constant(o), Expression.Constant(o)));
var anyMethod2 = Expression.Call(
anyMethod,
users.First().UserRoles.AsQueryable().Expression,
anyCheckFunction);
Any help much appreciated.
You can use generic classes and methods.
For example:
public class Repository<T>
{
public List<T> GetByPredicate(Func<T, bool> predicate, IEnumerable<T> items)
{
return items.Where(predicate).ToList();
}
}
var repository = new Repository<User>();
var result = repository.GetByPredicate(u => u.Roles.Any(ur => ur.Id == roleId), users).ToList();
You can replace items with the database context, or you can use a collection that you fill in yourself.
Im pretty new to C#/Unity so forgive me.
Im writing a filtering system which will filter a list by any of the properties of the class at runtime.
Im planning on that building up some kind of whereclause to filter the lists (i know i could hit the server to get the list i need, but currently want to just filter the data i already have)
Say i have a list of class "MyClass" with 4 properties: "param1".."param4"
If i wanted to filter it normally by param1 and param2 i could do:
List<MyClass> myList = new List<MyClass>(existinglist);
myList = myList.Where(g => g.param1 == somevalue && g.param2 == someothervalue).ToList();
How could i generate the same where clause at runtime?
Thank you!
You can use a helper method which dynamically builds and compiles lambda based on the passed list of filters. I've used KeyValuePair<string, object> to represent the filter information (Key for the property name, Value - well, for the property value), but of course you can adjust it for another data structure (like custom class etc.)
public static class EnumerableExtensions
{
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, IEnumerable<KeyValuePair<string, object>> filters)
{
if (filters == null || !filters.Any()) return source;
var parameter = Expression.Parameter(typeof(T), "x");
var body = filters
.Select(filter => Expression.Equal(
Expression.PropertyOrField(parameter, filter.Key),
Expression.Constant(filter.Value)))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(predicate.Compile());
}
}
Sample usage:
var filters = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("param1", somevalue),
new KeyValuePair<string, object>("param2", someothervalue),
};
var myList = existinglist.Where(filters).ToList();
You can write an extension method like this:
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, string propName, object value)
{
var type = typeof(T);
var propInfo = type.GetProperty(propName,BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var parameterExpr = Expression.Parameter( type, "x" ); //x
var memberAccessExpr = Expression.MakeMemberAccess( parameterExpr, propInfo ); //x.Prop
var lambda = Expression.Lambda( Expression.Equal(memberAccessExpr, Expression.Constant(value)),
parameterExpr ); //x=>x.Prop==value
var mi = typeof(Enumerable)
.GetMethods()
.Where(m => m.Name == "Where")
.First(m => m.GetParameters().Count() == 2)
.MakeGenericMethod(type);
return (IEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
You can now use it as
var test = new[] { new { a = 1 }, new { a = 2 } }.Where("a", 1).ToList();
A lambda expression is just shorthand for a function. So you can replace that lambda with any function which takes a Myclass and returns a bool. Then write in that method the code to evaluate what you need to dynamically - perhaps using reflection if necessary.
myList = myList.Where(myFunction).ToList();
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.
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
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..
Create array of IDs using for loop and then simply run the below query
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId))).ToList()
I am having trouble creating an extension method for an IQueryable that will include the translation for a specified column in a Linq Query.
Suppose i have the query below.
I would like to call a method IncludeTranslation on the CFG_Article IQueryable specifying the column i want to get the translation for.
Could someone help me in the right direction.
var translations =
from t in UoW.CFG_TRANSLATION.GetAll()
select t;
var result = (
from a in UoW.CFG_ARTICLE.GetAll()
select new
{
a,
translation = translations
.Where(t=> t.TR_TEXT == a.AR_NAME).FirstOrDefault()
});
All i have come up so far is the code below but this does not compile.
public static IQueryable IncludeTranslation<T>(
this IQueryable<T> query,
Expression<Func<t, bool>> fieldToTranslate)
{
// this will get an IQueryable of CFG_TRANSLATION
var translations = GetTranslations();
var result = (
from q in query
select new
{
q,
translation = translations
.Where(t=> t.TR_TEXT == fieldToTranslate)
.FirstOrDefault()
});
// even better is to return all fields from query
// + the TR_TRANSLATION field from the translations table
return result;
}
Try this (I'm having to guess class names are CFG_ARTICLE & CFG_TRANSLATION - replace as required)
public static IQueryable IncludeTranslation(
this IQueryable<CGF_ARTICLE> query,
Func<CFG_ARTICLE, CFG_TRANSLATION, bool> fieldToTranslate)
{
var translations = GetTranslations();
var result =
from a in query
select new
{
a,
translation = translations
.Where(t => fieldToTranslate(a, t))
.FirstOrDefault()
};
return result;
}
calling like this
var result = query.IncludeTranslation(
(article, translation) => article.TR_TEXT == translation.AR_NAME);
I found an other way of returning the same result using a Generic Way.
public static IQueryable IncludeTranslation<S>(this IQueryable<S> source, Expression<Func<S, string>> keyField)
where S : class
{
IQueryable<CFG_TRANSLATION> translations = GetTranslations();
var trans = source.GroupJoin(translations, keyField, t => t.TR_TEXT, (s, t) => new { Source = s, Translations = t });
var result = trans.Select(t => new {
Source = t.Source,
Translation = t.Translations
.FirstOrDefault()
});
return result;
}
Maybe someone can use this as a sollution
this can be called as follow
var Result = QueryableTable.IncludeTranslation(t => t.FieldToTranslate);
If you're adding "and" conditions to a Linq query, it's easy to do it like so:
var q = MyTable;
if (condition1)
q = q.Where(t => t.Field1 == value1);
if (condition2)
q = q.Where(t => t.Field2 > t.Field3);
// etc.
Is there any clever way of doing the same thing, when you want to add "or" conditions?
You can use a PredicateBuilder and use it to build an Or based expression:
var predicate = PredicateBuilder.False<Product>();
predicate = predicate.Or (t => t.Field1 == value1);
predicate = predicate.Or (t => t.Field2 > t.Field3);
q = q.Where (predicate);
You can read more about it here:
http://www.albahari.com/nutshell/predicatebuilder.aspx
Replace the Product in PredicateBuilder.False<Product>() with your queried object.
Note that you start from a False predicate as you want to use Or. If You'd want an And predicate, Yuo should start from a True
Use the following:
var q = MyTable;
q = q.Where(
t => (condition1 && t.Field1 == value1) || (condition2 && t.Field2 > t.Field3));
var q = MyTable;
var conditions = new List<Func<T, bool>>();
if (condition1)
conditions.Add(t => ...);
if (condition2)
conditions.Add(t => ...);
q.Where(x => conditions.Any(y => y(x)));
There is one way to do this that involves using expression trees. This way you build the Boolean expression yourself. It's pretty straight forward the tricky part though is that you need to rebase the parameters because otherwise it is going to refer the original lambda expression. See below for an example:
static void Main(string[] args)
{
var source = new List<int> { 1, 2, 3 };
var any = new List<Expression<Func<int, bool>>>();
any.Add(x => x == 1);
any.Add(x => x == 3);
foreach (var item in source.AsQueryable().WhereDisjunction(any))
{
Console.WriteLine(item);
}
}
class RewriteSingleParameterUsage : ExpressionVisitor
{
public ParameterExpression Parameter { get; set; }
protected override Expression VisitParameter(ParameterExpression node)
{
return Parameter;
}
}
public static IQueryable<T> WhereDisjunction<T>(this IQueryable<T> source, IList<Expression<Func<T, bool>>> any)
{
switch (any.Count)
{
case 0: return source;
case 1: return source.Where(any[0]);
default:
var p = Expression.Parameter(any[0].Parameters[0].Type, any[0].Parameters[0].Name);
var rw = new RewriteSingleParameterUsage { Parameter = p };
var expr = rw.Visit(any[0].Body);
for (int i = 1; i < any.Count; i++)
{
expr = Expression.Or(expr, rw.Visit(any[i].Body));
}
return source.Where(Expression.Lambda<Func<T, bool>>(expr, p));
}
}
In the above example I'm being very harsh, I'm effectively replacing any parameter with this single new parameter that is being used to create the new expression. However, given the signature of this extension method it shouldn't really be possible to call this method with parameters such that it would cause an error. It is however going to be a problem if you involve more than one parameter.
This is the same answer I gave here
As Marc Gravell said it involves expression-tree combining.
This article shows you how to do that. It takes a bit of work to initially set it up. But its worth it.
Alternate solution is to use Predicate Builder. The article does not explain very well what is actually happening under-the-hood. But the above article explains it nicely