Linq: Queryable.OrderBy() using a collection of Expressions - c#

I wonder how I can store orderby expressions in a list. This is what I wanted to write:
List<Expression<Func<Products,Object>>> list = new List<Expression<Func<Products,Object>>>()
{
p => p.Name,
p => p.Id
};
Then:
var expr = list[0];
myProducts.OrderBy( expr );
which works for p.Name, but does not work for p.Id (list[1]) as it drops follwing exception
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
What type of list do I have to use?

Here's my solution (using Reflection and based on DynamicLinq ideas):
Defining a ConvertableExpression class so we can intercept calls to our custom OrderBy():
public class ConvertableExpression<T>
{
public ConvertableExpression(Expression<Func<T, object>> expr)
{
this.Expression = expr;
}
public Expression<Func<T, object>> Expression { get; private set; }
}
Introducing an Extension-Method for easier casting from normal Expression:
public static class ExpressionExtensions
{
public static ConvertableExpression<T> AsConvertable<T>(this Expression<Func<T, object>> expr)
{
return new ConvertableExpression<T>(expr);
}
}
Extending IQueryable with Reflection-based implementation of OrderBy():
public static class QueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, ConvertableExpression<T> expr)
{
Expression queryExpr = source.Expression;
var exprBody = SkipConverts(expr.Expression.Body);
var lambda = Expression.Lambda(exprBody, expr.Expression.Parameters);
var quote = Expression.Quote(lambda);
queryExpr = Expression.Call(typeof(Queryable), "OrderBy", new[] { source.ElementType, exprBody.Type }, queryExpr, quote);
return (IOrderedQueryable<T>)source.Provider.CreateQuery(queryExpr);
}
private static Expression SkipConverts(Expression expression)
{
Expression result = expression;
while (result.NodeType == ExpressionType.Convert || result.NodeType == ExpressionType.ConvertChecked)
result = ((UnaryExpression)result).Operand;
return result;
}
}
Usage:
myProducts.OrderBy(expr.AsConvertable());

try this
List<Func<Products, Object>> list = new List<Func<Products, Object>>()
{
new Func<Products,Object>( p => p.Name),
new Func<Products,Object>( p => p.Id),
};

So it looks like the implementation of OrderBy for EF works by checking if < T > is a struct or object, and so you tell it to call OrderBy<..., object >(someStructTypeVariable)
As a workaround I would sugest you to store whole delegates instead of expresions.
Try this:
internal static class MyExtensions
{
public static IOrderedQueryable<TSource> OrderBy<TSource, TField>(this IQueryable<TSource> source, Expression<Func<TSource, TField>> selector, bool descending)
{
return descending
? source.OrderByDescending(selector)
: source.OrderBy(selector);
}
}
var orderers = new List<Func<IQueryable<Products>, IOrderedQueryable<Products>>>()
{
source => source.OrderBy(x => x.Id, true),
source => source.OrderBy(x => x.Id, false),
source => source.OrderBy(x => x.Name, false)
};
// To be replaced with entity source-collection.
IQueryable<Products> dummySource = new EnumerableQuery<MyType>(new List<Products>());
orderers[0](dummySource.Where(x => x.Id != 0));

Related

Dealing with "convert to object" in a MemberExpression

I have a class that looks like this:
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
}
I need to do this:
var memberExpressions = ConvertToMemberExpressions<MyClass>(o => o.Id, o => o.Name);
...
public static List<MemberExpression> ConvertToMemberExpressions<T>(params Expression<Func<T, object>>[] methodExpressions)
{
var results = new List<MemberExpression>();
foreach(var methodExpression in methodExpressions)
{
var memberExpression = methodExpression.Body as MemberExpression;
results.Add(memberExpression);
}
return results;
}
The problem is that because of Func<T, object> (to be able to include both int and string as parameters) my methodExpression looks like this: {o => Convert(o.Id, Object)}, which is not what I need. I need to reach o.Id.
This does not happen with strings: {o => o.Name}, no conversion here.
I use Func<T,object> to be able to take advantage of Intellisense and reach the props of MyClass. I have tried using Func<T, dynamic> instead, but the result is the same.
It could be solved using multiple overloads:
public static ConvertToMemberExpressions<TClass, T1>(Expression<Func<TClass,T1>>[] methodExpression1)
public static ConvertToMemberExpressions<TClass, T1, T2>(Expression<Func<TClass,T1>>[] methodExpression1, Expression<Func<TClass,T2>>[] methodExpression2)
...
...but it is a sacrifice I would like to avoid if possible.
The question:
Is it possible to build o => o.Id from o => Convert(o.Id, Object) ?
Just check whether your methodExpression.Body is a UnaryExpression with a NodeType of Convert:
public static List<MemberExpression> ConvertToMemberExpressions<T>(params Expression<Func<T, object>>[] methodExpressions)
{
var results = new List<MemberExpression>();
foreach (var methodExpression in methodExpressions)
{
var expr = methodExpression.Body;
if (expr is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert)
{
expr = unaryExpression.Operand;
}
if (expr is MemberExpression memberExpression)
{
results.Add(memberExpression);
}
else
{
throw new ArgumentException($"Unexpected expression type {expr.NodeType}");
}
}
return results;
}
When working with expressions, the debugger is your friend:

Apply inner expression filter on IQueryable

Suppose I have the following objects:
public class Source { public string Name; }
public class Target { public string Name; }
public class Result
{
public Source SourceObj;
public Target TargetObj;
}
Now, obtaining IQueryable<Result> from somewhere I would like to prepare expression filter for it just having Target filter as expression: Expression<Func<Target, bool>> filter. The filter method signature looks like this:
public Expression<Func<Result, bool>> Filter(IQueryable<Result> collection, Expression<Func<Target, bool>> targetFilter)
{
in result expression: "in given collection select items where their property TargetObj satisfies targetFilter"
}
Any suggestions will be very appreciated. Thanks
I'm not entierly sure I understood your goal correctly, but here's what I think is probably the thing you want.
With the linq Select, you can map an element from your source collection. So here we are mapping the result to the target and then apply your target filter.
public IQueryable<Target> GetFilteredTargets(IQueryable<Result> collection, Expression<Func<Target, bool>> targetFilter)
{
return collection.Select(result => result.Target).Where(targetFilter);
}
If you really want an expression as a return for your Filter method, you don't need IQueryable<Result> collection.
public Expression<Func<Result, bool>> Filter(Expression<Func<Target, bool>> targetFilter)
{
Func<Target, bool> func = targetFilter.Compile();
Expression<Func<Result, bool>> resultExpression = r => func(r.TargetObj);
return resultExpression;
}
usage:
var results = new List<Result>()
{
new Result() { TargetObj = new Target() { Name = "A" }},
new Result() { TargetObj = new Target() { Name = "B" }},
new Result() { TargetObj = new Target() { Name = "C" }},
};
var expression = Filter(r => r.Name == "B");
var func = expression.Compile();
var filteredResults = results.Where(r => func(r));
The sample filtered result here would return the target with B. It would make more sense for me to use Linq Where directly based on my understanding of your goal.
EDIT: Since you've added a new factor into your question here is how you can add the target filter to the base filter.
public Expression<Func<Result, bool>> Filter(Expression<Func<Result, bool>> baseFilter, Expression<Func<Target, bool>> targetFilter)
{
Func<Result, bool> baseFunc = baseFilter.Compile();
Func<Target, bool> targetFunc = targetFilter.Compile();
Expression<Func<Result, bool>> resultExpression = r => baseFunc(r) && targetFunc(r.TargetObj);
return resultExpression;
}

How to find a row where all search terms are contained within selected columns using Entity Framework

For context, this question is related to this question and this question. In this case, the user can specify an array of phrases. I'd like to expand upon the previous answer by asking how I can create a generic way to find entities where all the words of any of the phrases are contained within any of the specified columns.
To give you a better idea of what I'm talking about, if I were going to write this as a non-generic method, it would look something like this:
var searchPhrases = new [] {"John Smith", "Smith Bob"};
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
contacts.Where(c =>
searchTermSets.Any(searchTerms =>
searchTerms.All(searchTerm =>
c.FullName.Contains(searchTerm)
|| c.FirstName.Contains(searchTerm)
|| c.LastName.Contains(searchTerm))));
What I'm trying to do is make an extension method where I can do something like this:
contact.WhereIn(
searchPhrases,
c => c.FullName,
c => c.FirstName,
c => c.LastName);
And the extension method signature would look something like this:
IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
I tried following the same pattern from the previous questions I linked to, but I didn't get very far. That call to All() is tripping me up.
Expression like the predicate for
contacts.Where(c =>
searchTermSets.Any(searchTerms =>
searchTerms.All(searchTerm =>
c.FullName.Contains(searchTerm)
|| c.FirstName.Contains(searchTerm)
|| c.LastName.Contains(searchTerm))));
can be build dynamically with Expression.Call to Enumerable.Any and Enumerable.All.
First we'll need a simple parameter replacer so we can bind all the passed Expression<Func<T, string>> to a single parameter:
public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
Then the implementation could be like this:
public static class QueryableExtensions
{
public static IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
{
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
var c = Expression.Parameter(typeof(T), "c");
var searchTerms = Expression.Parameter(typeof(string[]), "searchTerms");
var searchTerm = Expression.Parameter(typeof(string), "searchTerm");
var allCondition = propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, searchTerm))
.Aggregate(Expression.OrElse);
var allPredicate = Expression.Lambda<Func<string, bool>>(allCondition, searchTerm);
var allCall = Expression.Call(
typeof(Enumerable), "All", new[] { typeof(string) },
searchTerms, allPredicate);
var anyPredicate = Expression.Lambda<Func<string[], bool>>(allCall, searchTerms);
var anyCall = Expression.Call(
typeof(Enumerable), "Any", new[] { typeof(string[]) },
Expression.Constant(searchTermSets), anyPredicate);
var predicate = Expression.Lambda<Func<T, bool>>(anyCall, c);
return source.Where(predicate);
}
}
The problem is though that it doesn't work. If you try to run your non generic query, you'll get EntityCommandCompilationException with inner NotSupportedException saying
The nested query is not supported. Operation1='Case' Operation2='Collect'
The same will happen with the dynamically built query.
So what should we do? Well, taking into account that searchPhrases (thus searchTermSets and searchTerms) are known, we can treat them as constants, and all we need to get the desired result is to replace Any with Or expressions and All with And expressions.
The working solution looks like this (using the same parameter replacer):
public static class QueryableExtensions
{
public static IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
{
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
var c = Expression.Parameter(typeof(T), "c");
var body = searchTermSets
.Select(searchTerms => searchTerms
.Select(searchTerm => propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, Expression.Constant(searchTerm)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso))
.Aggregate(Expression.OrElse);
var predicate = Expression.Lambda<Func<T, bool>>(body, c);
return source.Where(predicate);
}
}

Linq parametrizable group by with 2 properties

I'm trying to do a LinQ request with a group by where a parameter is parametrizable by an Expression ( (Expression<Func<CompanyModel,TKey>> myGroupingProperty) and the other one is hard coded. But even if my code compile I get an error that linq does not support lambdas. Would you have any idea how to do this request?
Here is the code sample:
public List<timelineResult> getTimelinebyCompany<TKey>(Expression<Func<CompanyModel,TKey>> myGroupingProperty, Filter item)
{
int cntToBeSureThatTheQueryExecuteAtLeastOneTime = 0;
List<timelineResult> toto = new List<timelineResult>();
using (var db = new fintechDbContext())
{
while (cntToBeSureThatTheQueryExecuteAtLeastOneTime == 0)
{
toto = (from p in db.companyDBSET
select p).GroupBy(p=> new {p.Founded_Year, myGroupingProperty})
.Select(o => new timelineResult{ year = o.Key.Founded_Year, cluster = o.myGroupingProperty.ToString(), count = o.Count() })
.OrderBy(o => o.year).ToList();
cntToBeSureThatTheQueryExecuteAtLeastOneTime++;
}
}
return toto;
}
What you are seeking for is doable, but not the way you tried because the passed lambda expression cannot be used directly inside another lambda expression.
You should start first by creating a generic class to hold the grouping key. It's similar to system provided Tuple, but has parameterless constructor and simple property get/setters to conform to the EF projection rules:
public class GroupKey<K1, K2>
{
public K1 Key1 { get; set; }
public K2 Key2 { get; set; }
}
Then you need to build dynamically lambda expression like this
Expression<Func<T, K1, K2>> keySelector = x =>
new GroupKey<K1, K2> { Key1 = x.Prop1, Key2 = x.Prop2 };
In order to do that, you'll need some Expression helpers:
public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
and you can encapsulate the grouping part in a custom extension method:
public static class QueryableExtensions
{
public static IQueryable<IGrouping<GroupKey<K1, K2>, T>> GroupByPair<T, K1, K2>(this IQueryable<T> source, Expression<Func<T, K1>> keySelector1, Expression<Func<T, K2>> keySelector2)
{
var parameter = keySelector1.Parameters[0];
var key1 = keySelector1.Body;
var key2 = keySelector2.Body.ReplaceParameter(keySelector2.Parameters[0], parameter);
var keyType = typeof(GroupKey<K1, K2>);
var keySelector = Expression.Lambda<Func<T, GroupKey<K1, K2>>>(
Expression.MemberInit(
Expression.New(keyType),
Expression.Bind(keyType.GetProperty("Key1"), key1),
Expression.Bind(keyType.GetProperty("Key2"), key2)),
parameter);
return source.GroupBy(keySelector);
}
}
Finally, the essential part of your method becomes like this:
toto = db.companyDBSET
.GroupByPair(p => p.Founded_Year, myGroupingProperty)
.Select(g => new timelineResult
{
year = g.Key.Key1,
cluster = g.Key.Key2.ToString(),
count = g.Count()
})
.OrderBy(o => o.year)
.ToList();

Using given properties as strings

I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}

Categories