Converting Linq nested Any within Where to a Generic Expression - c#

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.

Related

Call OrderBy() with a field name as a string [duplicate]

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.

Dynamic Linq query Contains List

I am using dynamic Linq for generic search.
I have list of Ids:
List<int> idList = new List<int> { 1, 5, 6};
In plain Linq, I would write:
q = q.Where(a => idList.Contains(a.MyId));
But now I have to use System.Linq.Dynamic because I don't know in advance name of the column.
string someId = "CustomId";
q = q.Where("#0"+ ".Contains(" + someId + ")", idList.ToArray());
But this gives error:
"No applicable method 'Contains' exists in type 'Int32'"
How can I achieve this?
Is there some extension library that implements Contains for dynamic Linq or some other way.
You could write something like this that builds your query function dynamically:
public static Func<ObjT, bool> PropertyCheck<ObjT, PropT>(string propertyName, Expression<Func<PropT, bool>> predicate)
{
var paramExpr = Expression.Parameter(typeof(ObjT));
var propExpr = Expression.Property(paramExpr, propertyName);
return Expression.Lambda<Func<ObjT, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile();
}
Then, it could be used like this:
foos.Where(PropertyCheck<Foo, int>("MyId", x => idList.Contains(x)));
Of course, you could also just provide your own Where extension method that does all that at once:
public static IEnumerable<T> Where<T, PropT>(this IEnumerable<T> self, string propertyName, Expression<Func<PropT, bool>> predicate)
{
var paramExpr = Expression.Parameter(typeof(T));
var propExpr = Expression.Property(paramExpr, propertyName);
return self.Where<T>(Expression.Lambda<Func<T, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile());
}
foos.Where<Foo, int>("MyId", x => idList.Contains(x));
You could use the expressions to do this dynamic query, try something like this, for sample:
import these namespaces:
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
And try this:
// a reference parameter
var x = Expression.Parameter(typeof (YourType), "x");
// contains method
var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
// reference a field
var fieldExpression = Expression.Property(instance, "PropertyName");
// your value
var valueExpression = Expression.Constant(yourId);
// call the contains from a property and apply the value
var containsValueExpression = Expression.Call(fieldExpression, containsMethod, valueExpression);
// create your final lambda Expression
var filterLambda = Expression.Lambda<Func<YourType, bool>>(containsValueExpression, x);
// apply on your query
q = q.Where(finalLambda);
Obs: make sure your property has a method called contains.
If you look at the source of Dynamic LINQ then you can see that parsing in many cases depends on variable predefinedTypes.
In your case you need change this variable like this
static readonly Type[] predefinedTypes = {
....
,typeof(List<int>)
};
after that next code will be work
List<int> idList = new List<int> { 1, 5, 6};
....
string someId = "CustomId";
q = q.Where("#0.Contains(" + someId + ")", idList);
#Felipe Oriani in his 90% answer used the string.Contains method and the yourId single value, but asked was:
q = q.Where(a => idList.Contains(a.MyId));
which is member (property) access to the a.
So here is the final tested extension method:
/// <summary>
/// Creates lambda expression predicate: (TEntity entity) => collection.Contains(entity.property)
/// </summary>
public static Expression<Func<TEntity, bool>> ContainsExpression<TEntity, TProperty, TCollection>(
this TCollection collection,
Expression<Func<TEntity, TProperty>> property
)
where TCollection : ICollection<TProperty>
{
// contains method
MethodInfo containsMethod = typeof(TCollection).GetMethod(nameof(collection.Contains), new[] { typeof(TProperty) });
// your value
ConstantExpression collectionInstanceExpression = Expression.Constant(collection);
// call the contains from a property and apply the value
var containsValueExpression = Expression.Call(collectionInstanceExpression, containsMethod, property.Body);
// create your final lambda Expression
Expression<Func<TEntity, bool>> result = Expression.Lambda<Func<TEntity, bool>>(containsValueExpression, property.Parameters[0]);
return result;
}
The example:
List<int> idList = new List<int> { 1, 5, 6 };
Expression<Func<MyEntity,int>> idExpression = entity => entity.Id;
var contains = idList.ContainsExpression(idExpression)
IQueryable<MyEntity> q = DbContext.Set<MyEntity>().Where(contains);
Another way to skin this cat would be to convert the contains to ORs.
someArray.Constains(someField) is equivalent to:
someField == someArray[0] or someField == someArray[1] and so on.
It's not ideal but if the array is small it could work.

Method to add Linq expressions to a list of expressions

I have a list of Linq expressions like:
List<Expression<Func<Customer, bool>>>
I need to add a whole bunch of predicates from a search page, like:
x.Name.Contains(searchString)
x.Description.Contains(searchString)
...
I want to create a method so I don't end up with a mass of duplicated code. Something with a signature like:
void AddCustomerPredicate(List<Expression<Func<Customer, bool>>> predicates, Expression<Func<Customer, string>> prop, string searchString);
Which I would use like:
var predicates = new List<Expression<Func<Customer, bool>>>();
AddCustomerPredicate(predicates, x => x.Name, this.Name);
AddCustomerPredicate(predicates, x => x.Description, this.Description);
...
I've simplified the problem a bit, but that is the gist of it. I haven't done much work with expression trees and the like, so I'm not sure how to implement this method?
**EDIT**
I might have over simplified the problem too much. I know how to add to the list like predicates.Add(x => x.Name.Contains(this.searchString)), but I have various things I want to do on each search parameter before adding it to the list (e.g. check for null or empty). I therefore want to call a method on each search parameter as above, so all of that stuff can be contained in a single method.
What you really need is to have a method for translated Expression<Func<T, string>> to Expression<Func<T, bool>> :
public static Expression<Func<T, bool>> GetContainsExpression<T>(Expression<Func<T, string>> prop, string searchString)
{
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(searchString, typeof(string));
var containsMethodExp = Expression.Call(prop.Body, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, prop.Parameters[0]);
}
This is inspired by this other answer : How do I create an expression tree to represent 'String.Contains(“term”)' in C#?
var predicates = new List<Expression<Func<Customer, bool>>>();
predicates.Add(GetContainsExpression((Customer x) => x.Name, this.Name));
predicates.Add(GetContainsExpression((Customer x) => x.Description, this.Description));
I believe this will do what you want, and will also handle null property values (so calling Contains() does not throw):
private static void AddCustomerPredicate(
List<Expression<Func<Customer, bool>>> predicates,
Expression<Func<Customer, string>> accessor,
string searchString)
{
var x = accessor.Parameters[0];
var temp = Expression.Variable(typeof(string), "temp");
var predicate = Expression.Lambda<Func<Customer, bool>>(
Expression.Block(
new[] { temp },
Expression.Assign(
temp,
accessor.Body),
Expression.AndAlso(
Expression.IsFalse(
Expression.ReferenceEqual(
temp,
Expression.Default(typeof(string)))),
Expression.Call(
temp,
"Contains",
Type.EmptyTypes,
Expression.Constant(searchString)))),
x);
predicates.Add(predicate);
}

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

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()

Creating Extension method to include subquery

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);

Categories

Resources