NHibernate 3 LINQ - как создать действительный параметр для Average () - PullRequest
4 голосов
/ 07 марта 2011

Скажем, у меня есть очень простая сущность, подобная этой:

public class TestGuy
{
    public virtual long Id {get;set;}
    public virtual string City {get;set;}
    public virtual int InterestingValue {get;set;}
    public virtual int OtherValue {get;set;}
}

Этот придуманный пример объекта отображается с помощью NHibernate (с использованием Fluent) и отлично работает.

Время сделать несколько отчетов. В этом примере «testGuys» - это IQueryable с некоторыми уже примененными критериями.

var byCity = testGuys
    .GroupBy(c => c.City)
    .Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });

Это работает просто отлично. В NHibernate Profiler я вижу, что генерируется правильный SQL, и результаты такие же, как и ожидалось.

Вдохновленный моим успехом, я хочу сделать его более гибким. Я хочу сделать его настраиваемым, чтобы пользователь мог получить среднее значение OtherValue, а также InterestingValue. Не должно быть слишком сложно, аргумент для Average () выглядит как Func (так как значения в данном случае являются целочисленными). Очень просто. Разве я не могу просто создать метод, который возвращает Func на основе некоторого условия, и использовать его в качестве аргумента?

var fieldToAverageBy = GetAverageField(SomeEnum.Other);

private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)
{
    switch(someCondition)
    {
        case SomeEnum.Interesting:
            return tg => tg.InterestingValue;
        case SomeEnum.Other:
            return tg => tg.OtherValue;
    }

    throw new InvalidOperationException("Not in my example!");
}

А потом, в другом месте, я мог бы просто сделать это:

var byCity = testGuys
    .GroupBy(c => c.City)
    .Select(g => new { City = g.Key, Avg = g.Average(fieldToAverageBy) });

Ну, я думал, что смогу это сделать. Однако, когда я перечисляю это, NHibernate подбрасывает:

Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.

Так что я предполагаю, что за кулисами происходит какое-то преобразование или приведение или что-то подобное, что в первом случае принимает мою лямбду, но во втором случае превращается во что-то, что NHibernate не может преобразовать в SQL. *

Надеюсь, мой вопрос прост - как моя функция GetAverageField может вернуть что-то, что будет работать в качестве параметра для Average (), когда поддержка NHibernate 3.0 LINQ (метод .Query ()) переводит это в SQL?

Любые предложения приветствуются, спасибо!

1027 * EDIT *

Основываясь на комментариях Дэвида Б. в его ответе, я внимательно посмотрел на это. Мое предположение, что Func будет правильным возвращаемым типом, было основано на значении intellisense, которое я получил для метода Average (). Кажется, он основан на типе Enumerable, а не на Queryable. Это странно .. Нужно присмотреться к вещам.

Метод GroupBy имеет следующую возвращаемую подпись:

IQueryable<IGrouping<string,TestGuy>>

Это значит, что это должно дать мне IQueryable, хорошо. Однако затем я перехожу к следующей строке:

.Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });

Если я проверяю intellisense для данной переменной внутри нового определения объекта {}, он фактически отображается как имеющий тип IGrouping - NOT IQueryable>. Вот почему вызванный метод Average () является Enumerable и почему он не принимает параметр Expression, предложенный Дэвидом Б.

Так что каким-то образом моя групповая ценность где-то потеряла свой статус IQueryable.

Немного интересная заметка:

Я могу изменить выбор на следующее:

.Select(g => new { City = g.Key, Avg = g.AsQueryable<TestGuy>().Average(fieldToAverageBy) });

А теперь он компилируется! Черная магия! Однако это не решает проблему, так как NHibernate больше не любит меня и дает следующее исключение:

Could not parse expression '[-1].AsQueryable()': This overload of the method 'System.Linq.Queryable.AsQueryable' is currently not supported, but you can register your own parser if needed.

Меня сбивает с толку то, что это работает, когда я передаю лямбда-выражение методу Average (), но я не могу найти простой способ представления того же выражения в качестве аргумента. Я явно делаю что-то не так, но не вижу, что ...!?

Я в своем уме. Помоги мне, Джон Скит, ты моя единственная надежда! ;)

Ответы [ 2 ]

2 голосов
/ 22 марта 2011

Вы не сможете вызывать «локальный» метод в своем лямбда-выражении.Если бы это было простое не вложенное предложение, оно было бы относительно простым - вам просто нужно изменить это:

private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)

на следующее:

private Expression<Func<TestGuy,int>> GetAverageField(SomeEnum someCondition)

и затем передатьрезультат вызова в соответствующий метод запроса, например,

var results = query.Select(GetAverageField(fieldToAverageBy));

В этом случае, однако, вам нужно будет построить все дерево выражений для предложения Select - выражения создания анонимного типа,извлечение Ключа и извлечение средней части поля.Это не будет весело, если честно.В частности, к тому времени, как вы построите свое дерево выражений, оно не будет статически типизировано так же, как обычное выражение запроса, из-за невозможности выразить анонимный тип в объявлении.

Если вы используете .NET 4, динамическая печать может помочь вам, хотя вы, конечно, заплатите цену за отсутствие статической печати. ​​

Один вариант(хотя это может быть ужасно) будет пытаться использовать своего рода «шаблон» дерева выражений проекции анонимного типа (например, всегда используя одно свойство), а затем создать копию этого дерева выражений, вставив вместо этого правильное выражение.Опять же, это не будет весело.

Марк Гравелл может быть в состоянии помочь больше в этом - это действительно похоже на то, что должно быть возможным, но я впотеря в том, как сделать это элегантно в данный момент.

0 голосов
/ 08 марта 2011

А?параметр Queryable.Average не является Func<T, U>.Это Expression<Func<T, U>>

Способ сделать это:

private Expression<Func<TestGuy,int>> GetAverageExpr(SomeEnum someCondition)
{
switch(someCondition)
{
case SomeEnum.Interesting:
  return tg => tg.InterestingValue;
case SomeEnum.Other:
  return tg => tg.OtherValue;
}
throw new InvalidOperationException("Not in my example!");
} 

Затем:

Expression<Func<TestGuy, int>> averageExpr = GetAverageExpr(someCondition);
var byCity = testGuys
  .GroupBy(c => c.City)
  .Select(g => new { City = g.Key, Avg = g.Average(averageExpr) });
...