Динамическое добавление GroupBy к лямбда-выражению
Хорошо, я признаю, что я еще не совсем "получил" лямбда-выражения и деревья выражений LINQ; многое из того, что я делаю, это вырезать, вставлять и смотреть, что работает. Я просмотрел много документации, но я все еще не нашел свой момент "ага".
С этим, как говорится...
Я пытаюсь динамически добавить выражение GroupBy к моему выражению Linq. Я последовал за вопросом здесь: Нужна помощь в создании Linq.Expression для Enumerable.GroupBy
и попытался реализовать то, что я там увидел.
Во-первых, у меня есть классы сущностей для моей базы данных и таблица с именем ObjCurLocViewNormalized
У меня есть метод, который делает первоначальный вызов,
public IQueryable<ObjCurLocViewNormalized> getLocations()
{
IQueryable<ObjCurLocViewNormalized> res = (from loc in tms.ObjCurLocViewNormalized
select loc);
return res;
}
так что я могу позвонить:
IQueryable<MetAmericanLinqDataModel.ObjCurLocViewNormalized> locations = american.getLocations();
Пока проблем нет.
Теперь я хочу сгруппировать по произвольному столбцу с помощью следующего вызова:
var grouped = locations.addGroupBy(childLocationFieldName);
Прямо сейчас у меня есть метод:
static public System.Linq.IQueryable<System.Linq.IGrouping<string, TResult>> addGroupBy<TResult>(this IQueryable<TResult> query, string columnName)
{
var providerType = query.Provider.GetType();
// Find the specific type parameter (the T in IQueryable<T>)
var iqueryableT = providerType.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), null).FirstOrDefault();
var tableType = iqueryableT.GetGenericArguments()[0];
var tableName = tableType.Name;
var data = Expression.Parameter(iqueryableT, "query");
var arg = Expression.Parameter(tableType, tableName);
var nameProperty = Expression.PropertyOrField(arg, columnName);
var lambda = Expression.Lambda<Func<TResult, string>>(nameProperty, arg);
var expression = Expression.Call(typeof(Enumerable),
"GroupBy",
new Type[] { tableType, typeof(string) },
data,
lambda);
var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
var result = query.GroupBy(predicate).AsQueryable();
return result;
}
Все это компилируется нормально, но когда я запускаю его, я получаю сообщение об ошибке:
System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[System.String,MetAmericanLinqDataModel.ObjCurLocViewNormalized]]' cannot be used for return type 'System.String'
и ошибка исходит из этой строки:
var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg);
Я копирую и адаптирую этот код из успешной работы, которую я проделал в динамически добавленных выражениях Where к выражению. Так что я вроде как вонзаюсь в темноту.
Если кто-то может помочь пролить некоторый свет на это, очевидно, что опубликовать полный рабочий код и сделать все, что я думаю для меня, было бы здорово:), но если бы вы могли просто изложить, почему это неправильно, или как обернуть мою голову вокруг этих концепций это было бы здорово. Если вы можете указать на документацию, которая действительно может помочь преодолеть разрыв между основами лямбда-выражений и строить деревья динамических выражений, это было бы здорово. В моих знаниях явно большие дыры, но я думаю, что эта информация может быть полезна для других.
спасибо всем за ваше время, и, конечно, если я найду ответ в другом месте, я опубликую его здесь.
Еще раз спасибо.
дон
2 ответа
Решение должно быть довольно простым:
public static IQueryable<IGrouping<TColumn, T>> DynamicGroupBy<T, TColumn>(
IQueryable<T> source, string column)
{
PropertyInfo columnProperty = typeof(T).GetProperty(column);
var sourceParm = Expression.Parameter(typeof(T), "x");
var propertyReference = Expression.Property(sourceParm, columnProperty);
var groupBySelector = Expression.Lambda<Func<T, TColumn>>(propertyReference, sourceParm);
return source.GroupBy(groupBySelector);
}
Предполагая пример класса, как это:
public class TestClass
{
public string TestProperty { get; set; }
}
Вы вызываете это так:
var list = new List<TestClass>();
var queryable = list.AsQueryable();
DynamicGroupBy<TestClass, string>(queryable, "TestProperty");
Все, что вам нужно сделать, чтобы это работало, это следующее:
static public IQueryable<IGrouping<TValue, TResult>> addGroupBy<TValue, TResult>(
this IQueryable<TResult> query, string columnName)
{
var providerType = query.Provider.GetType();
// Find the specific type parameter (the T in IQueryable<T>)
const object EmptyfilterCriteria = null;
var iqueryableT = providerType
.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), EmptyfilterCriteria)
.FirstOrDefault();
Type tableType = iqueryableT.GetGenericArguments()[0];
string tableName = tableType.Name;
ParameterExpression data = Expression.Parameter(iqueryableT, "query");
ParameterExpression arg = Expression.Parameter(tableType, tableName);
MemberExpression nameProperty = Expression.PropertyOrField(arg, columnName);
Expression<Func<TResult, TValue>> lambda = Expression.Lambda<Func<TResult, TValue>>(nameProperty, arg);
//here you already have delegate in the form of "TResult => TResult.columnName"
return query.GroupBy(lambda);
/*var expression = Expression.Call(typeof(Enumerable),
"GroupBy",
new Type[] { tableType, typeof(string) },
data,
lambda);
var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
var result = query.GroupBy(predicate).AsQueryable();
return result;*/
}
И ты будешь называть тебя выражением следующим образом:
var grouped = locations.addGroupBy<string, ObjCurLocViewNormalized>(childLocationFieldName);
Первый универсальный параметр "string", используемый нами для указания явного типа элементов, по которым вы группируете. Например, вы можете группировать по полю "int" и вызов метода будет выглядеть следующим образом:
var grouped = locations.addGroupBy<int, ObjCurLocViewNormalized>(someFieldNameWithTheTypeOfInt);
Редактировать Просто, чтобы закончить это решение по- своему:
//return query.GroupBy(lambda);
MethodCallExpression expression = Expression.Call(typeof (Enumerable),
"GroupBy",
new[] { typeof(TResult), typeof(TValue) },
data,
lambda);
var result = Expression.Lambda(expression, data).Compile().DynamicInvoke(query);
return ((IEnumerable<IGrouping<TValue, TResult>>)result).AsQueryable();