How merge expression in the Select method with Linq - c#

I am trying to make a generic to be used on the Selectors components. It should provide a default pattern result which is based on a type we have made called SelectorViewModel that has an Id, a Description and a Code. Today we have a method that does it using the following query:
var result = Queryable.Where(x => .... )
.OrderBy(x => ... )
.Select(x => SelectorViewModel(x.Id,
x.Name,
x.Code))
.ToList();
It works fine, but we will have a lot of these methods. The question is, how to make the fields defined on the Select method to be possible to pass as parameter to the SelectorViewModel? For sample:
public IEnumerable<SelectorViewModel> Selector<T>(.. idExpression, .. descriptionExpression, .. codeExpression)
{
var result = session.Query<T>
.Where(x => .... )
.OrderBy(x => ... )
.Select(x => SelectorViewModel(idExpression, // id
descriptionExpression, // description
codeExpression /*code*/))
.ToList();
return result;
}
I would like to have something like this on the use of this method:
var result = repository.Selector(x => x.Id, x => x.Name, x => x.Code);
And make these arguments be merged on the expression on the Select method. Is that possible?
Thank you.

you need something like this:
public IEnumerable<SelectorViewModel> Selector<T>(
Func<T, int> idExpression,
Func<T, string> descriptionExpression,
Func<T, string> codeExpression)
{
var result = session.Query<T>
.Where(x => .... )
.OrderBy(x => ... )
.Select(x => new SelectorViewModel(idExpression(x), descriptionExpression(x), codeExpression(x)))
.ToList();
return result;
}
However I prefer something more like a "Factory method", so that you pass a factory that convert T to SelectorViewModel (something like this):
public class SelectorViewModel
{
public static SelectorViewModel GetSelectorViewModel<T>(T input)
{
//create SelectorViewModel how do you prefer
return new SelectorViewModel(input.Id, input.Description, input.Code);
}
public SelectorViewModel(int id, string description, int code)
{
// TODO
}
}
public IEnumerable<SelectorViewModel> Selector<T>(Func<T, SelectorViewModel> factoryMethod)
{
var result = session.Query<T>
.Where(x => .... )
.OrderBy(x => ... )
.Select(x => factoryMethod(x))
.ToList();
return result;
}
this because (personally) I don't like to pass many functions, maybe complex such as:
x => x.property > 2 ? x.name.ToString() : (x.property < 0 ? x.description : x.description + "a")
and, at the end
the code is less readable
no/low support for debugging
long code line (length)
[ps: what I did above can be done with OOP (https://en.wikipedia.org/wiki/Factory_method_pattern)]

From your samples it's not quite clear what query provider are you targeting (EF or NHibernate), so the following is an universal helper method which composes selector expression using prototype expression and small ExpressionVisitor based parameter replacer:
public static class QueryableExtensions
{
public static IQueryable<SelectorViewModel> ToSelectorViewModel<T>(
this IQueryable<T> source,
Expression<Func<T, long>> idSelector,
Expression<Func<T, string>> descriptionSelector,
Expression<Func<T, string>> codeSelector
)
{
Expression<Func<long, string, string, SelectorViewModel>> prototype =
(id, description, code) => new SelectorViewModel { Id = id, Description = description, Code = code };
var parameter = Expression.Parameter(typeof(T), "e");
var body = prototype.Body
.ReplaceParameter(prototype.Parameters[0], idSelector.Body.ReplaceParameter(idSelector.Parameters[0], parameter))
.ReplaceParameter(prototype.Parameters[1], descriptionSelector.Body.ReplaceParameter(descriptionSelector.Parameters[0], parameter))
.ReplaceParameter(prototype.Parameters[2], codeSelector.Body.ReplaceParameter(codeSelector.Parameters[0], parameter));
var selector = Expression.Lambda<Func<T, SelectorViewModel>>(body, parameter);
return source.Select(selector);
}
}
The expression helper used:
public static partial 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);
}
}
}
Now assuming your method receives the same idSelector, descriptionSelector and codeSelector arguments as above, the usage would be:
var result = Queryable.Where(x => .... )
.OrderBy(x => ... )
.ToSelectorViewModel(idSelector, descriptionSelector, codeSelector)
.ToList();

Related

Pass expression to initializer

I would like to pass an expression that represents a variable to used when instantiating an object.
Instead of:
class MyObject : IMyInterface { ... }
var list = db.MyObjects.Where(x => !x.IsDeleted).ToList();
var anotherList = list.Select(x => new AnotherObject() {
Id = x.Id,
Value = x.Value
});
I would like to make this so that a list of objects of IMyInterface can be transformed into another type of list (AnotherObject as example) using defined expressions as so:
var list = db.MyObjects
.Where(x => !x.IsDeleted)
.ToAnotherObjectList(x => x.Id, x => x.Value);
...
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<IMyInterface> list,
Expression id,
Expression value)
{
return list.Select(x => new AnotherObject() { Id = id, Value = value }).ToList();
}
I'm not sure how to accomplish this. I know I can use reflection to create objects and set properties by a string but I'm not sure how to pass expressions.
UPDATE
Well, I thought I'd have to do some reflection but it's simpler than what I was thinking. Here's my solution that works in IRL.
public static IEnumerable<AnotherObject> ToAnotherObject<T>(this IEnumerable<T> list, Func<T, int> getId, Func<T, string> getValue, Func<T, bool> getSelected = null) where T : IMyInterface
{
return list.Select(x => new AnotherObject {
Display = getValue(x),
Id = getId(x),
Selected = getSelected != null && getSelected(x),
});
}
You could use a Func<TInput,TReturn> for that. For example:
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<T> list,
Func<T, int> getId,
Func<T, object> getValue)
{
return list.Select(x => new AnotherObject() { Id = getId(x), Value = getValue(x) }).ToList();
}
Call:
list.ToAnotherObjectList(i => i.Id, i=> i.Value);
In this example I used Funcs with one parameter (of type T) and return type int/object.

Is there a way to generically mock the DbSet.Find method with Moq?

I'm currently using an extension method to generically mock DbSets as a list:
public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var mockDbSet = new Mock<DbSet<T>>();
mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
mockDbSet.Setup(x => x.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
mockDbSet.Setup(x => x.Remove(It.IsAny<T>())).Returns<T>(x => { if (sourceList.Remove(x)) return x; else return null; } );
return mockDbSet.Object;
}
However, I can't figure out a way to mock the Find method, which searches based on the table's primary key. I could do it at a specific level for each table because I can inspect the database, get the PK, and then just mock the Find method for that field. But then I can't use the generic method.
I suppose I could also go add to the partial classes that EF auto-generated to mark which field is the PK with an attribute or something. But we have over 100 tables and it makes the code more difficult to manage if you're relying on people to manually maintain this.
Does EF6 provide any way of finding the primary key, or does it only know dynamically after it's connected to the database?
After pondering this for awhile, I think I've found the "best" solution currently available. I just have a series of if statements that directly checks the type in the extension method. Then I cast to the type I need to set the find behavior and cast it back to generic when I'm done. It's only pseudo-generic, but I can't think of anything else better.
if (typeof(T) == typeof(MyFirstSet))
{
mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MyFirstSet>).FirstOrDefault(y => y.MyFirstSetKey == (Guid)x[0]) as T);
}
else if (typeof(T) == typeof(MySecondSet))
{
mockDbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns<object[]>(x => (sourceList as List<MySecondSet>).FirstOrDefault(y => y.MySecondSetKey == (Guid)x[0]) as T);
}
...
As far as I can tell, there is no 'best practice' answer to this question, but here's how I've approached it. I've added an optional parameter to the AsDbSet method which identifies the primary key, then the Find method can be mocked up easily.
public static DbSet<T> AsDbSet<T>(this List<T> sourceList, Func<T, object> primaryKey = null) where T : class
{
//all your other stuff still goes here
if (primaryKey != null)
{
mockSet.Setup(set => set.Find(It.IsAny<object[]>())).Returns((object[] input) => sourceList.SingleOrDefault(x => (Guid)primaryKey(x) == (Guid)input.First()));
}
...
}
I've written this on the assumption of a single guid being used as primary key as that seemed to be how you're working, but the principle should be easy enough to adapt if you need more flexibility for composite keys, etc.
I ended in the following class:
public static class DbSetMocking
{
#region methods
public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>( this IReturns<TContext, DbSet<TEntity>> setup, ICollection<TEntity> entities, Func<object[], TEntity> find = null )
where TEntity : class where TContext : DbContext
{
return setup.Returns( CreateMockSet( entities, find ).Object );
}
private static Mock<DbSet<T>> CreateMockSet<T>( ICollection<T> data, Func<object[], T> find )
where T : class
{
var queryableData = data.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup( m => m.Provider ).Returns( queryableData.Provider );
mockSet.As<IQueryable<T>>().Setup( m => m.Expression ).Returns( queryableData.Expression );
mockSet.As<IQueryable<T>>().Setup( m => m.ElementType ).Returns( queryableData.ElementType );
mockSet.As<IQueryable<T>>().Setup( m => m.GetEnumerator() ).Returns( queryableData.GetEnumerator() );
mockSet.SetupData( data, find );
return mockSet;
}
#endregion
}
Which can be used:
private static MyRepository SetupRepository( ICollection<Type1> type1s, ICollection<Type2> type2s )
{
var mockContext = new Mock<MyDbContext>();
mockContext.Setup( x => x.Type1s ).ReturnsDbSet( type1s, o => type1s.SingleOrDefault( s => s.Secret == ( Guid ) o[ 0 ] ) );
mockContext.Setup( x => x.Type2s ).ReturnsDbSet( type2s, o => type2s.SingleOrDefault( s => s.Id == ( int ) o[ 0 ] ) );
return new MyRepository( mockContext.Object );
}
I'm using now Entity Framework Core 2, and this solution works fine to me.
At first, I will find the primary key by using the class name with the suffix “Id”. (If you follow other convention you must change it to fit your necessity.)
//Find primary key. Here the PK must follow the convention "Class Name" + "Id"
Type type = typeof(T);
string colName = type.Name + "Id";
var pk = type.GetProperty(colName);
if (pk == null)
{
colName = type.Name + "ID";
pk = type.GetProperty(colName);
}
Now that you know the Pk, you can support the Find with the following code
dbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns((object[] id) =>
{
var param = Expression.Parameter(type, "t");
var col = Expression.Property(param, colName);
var body = Expression.Equal(col, Expression.Constant(id[0]));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return queryable.FirstOrDefault(lambda);
});
So, the complete code for generically mock supporting DbSet.Find you can see below:
public static DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
//Find primary key. Here the PK must follow the convention "Class Name" + "Id"
Type type = typeof(T);
string colName = type.Name + "Id";
var pk = type.GetProperty(colName);
if (pk == null)
{
colName = type.Name + "ID";
pk = type.GetProperty(colName);
}
dbSet.Setup(x => x.Find(It.IsAny<object[]>())).Returns((object[] id) =>
{
var param = Expression.Parameter(type, "t");
var col = Expression.Property(param, colName);
var body = Expression.Equal(col, Expression.Constant(id[0]));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return queryable.FirstOrDefault(lambda);
});
return dbSet.Object;
} //GetQueryableMockDbSet
My solution was to add a parameter to specify the key of the entity:
public static Mock<DbSet<TEntity>> Setup<TContext, TEntity, TKey>(this Mock<TContext> mockContext,
Expression<Func<TContext, DbSet<TEntity>>> expression, List<TEntity> sourceList,
Func<TEntity, TKey> id)
where TEntity : class
where TContext : DbContext
{
IQueryable<TEntity> data = sourceList.AsQueryable();
Mock<DbSet<TEntity>> mock = data.BuildMockDbSet();
// make adding to and searching the list work
mock.Setup(d => d.Add(It.IsAny<TEntity>())).Callback(add(sourceList));
mock.Setup(d => d.Find(It.IsAny<object[]>())).Returns<object[]>(s => find(sourceList, id, s));
// make context.Add() and Find() work
mockContext.Setup(x => x.Add(It.IsAny<TEntity>())).Callback(add(sourceList));
mockContext.Setup(x => x.Find<TEntity>(It.IsAny<object[]>()))
.Returns<object[]>(s => find(sourceList, id, s));
mockContext.Setup(x => x.Find(typeof(TEntity), It.IsAny<object[]>()))
.Returns<Type, object[]>((t, s) => find(sourceList, id, s));
mockContext.Setup(expression).Returns(mock.Object);
return mock;
}
private static Action<TEntity> add<TEntity>(IList<TEntity> sourceList)
where TEntity : class
{
return s => sourceList.Add(s);
}
private static TEntity find<TEntity, TKey>(IList<TEntity> sourceList, Func<TEntity, TKey> id, object[] s) where TEntity : class
{
return sourceList.SingleOrDefault(e => id(e).Equals(s[0]));
}
You can use it as
mockContext.Setup(m => m.Users, users, x => x.UsedId);
The BuildMockDbSet comes from the MockQueryable library (available from NuGet).
Edit: By the way, if you really do not want to specify the key everytime you call the above function, and you know most of your keys are of type int, you can create another overload like:
public static Mock<DbSet<TEntity>> Setup<TContext, TEntity>(this Mock<TContext> mockContext,
Expression<Func<TContext, DbSet<TEntity>>> expression, List<TEntity> sourceList)
where TEntity : class
where TContext : DbContext
{
return Setup(mockContext, expression, sourceList, x => x.GetKey<int>());
}
where GetKey is implemented by the extension methods:
public static object? GetKey(this object entity)
{
PropertyInfo keyInfo = entity.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(KeyAttribute))).SingleOrDefault();
if (keyInfo == null)
return null;
return keyInfo.GetValue(entity);
}
public static TKey GetKey<TKey>(this object entity)
{
return (TKey)GetKey(entity);
}
So now you can call it simply as
var mockUsers = mockContext.Setup(m => m.Users, users);

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

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

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

Categories