Расширение на IQueryable не имеет перевода на SQL

Я два полных дня возился с простой проблемой.

У меня есть база данных с объектами Product и ProductLocationHistory. ProductLocationHistory имеет ссылку либо на хранилище, либо на контакт, либо на отношение. Каждый раз, когда продукт перемещается, он получает новую запись, чтобы можно было отследить прошлое продукта. Таким образом, текущее местоположение является последней записью, определяемой полем DateCreation в ProductLocationHistory.

Пример:

var storehousesWithBorrowedItems = productService.GetAllProducts()
    .Select(p => p.ProductLocationHistories
        .SingleOrDefault(plh => plh.DateCreation == p.ProductLocationHistories.Max(grp => grp.DateCreation)))
    .Select(plh => plh.Storehouse)
    .Distinct();

Это все склады, на которых в данный момент есть товар.

Конечно, очень неудобно записывать это в код все время, когда мне нужно определить текущее местоположение продукта. На мой взгляд, ссылка на текущую историю ProductLocationHistory в Product абсолютно нежелательна из-за возможных проблем с согласованностью. Я бы предпочел что-то вроде:

Product.ProductLocationHistories.Current();

Итак, я попытался:

public static ProductLocationHistory Current(this EntitySet<ProductLocationHistory> plhs)
    {
        return plhs.SingleOrDefault(plh => plh.DateCreation == plhs.Max(grp => grp.DateCreation));
    }

Это не работает с запрашиваемыми, так как я получаю «Текущий не поддерживает перевод в sql», и поскольку комбинация Product и ProductLocationHistory часто является «началом» запроса, я хочу оставаться IQueryable, а не сразу в IEnumerable и иметь запрос для каждого продукта, чтобы определить текущее местоположение! Не говоря уже о том, что последует дальше... Часто используется текущая запись в журнале для любого объекта, и не имеет большого значения, насколько сложна функция .Current(), пока она работает и остается доступной для запросов. Я надеялся, что моя функция .Current(...) будет работать, поскольку базовый код можно запрашивать, но я все равно получаю исключение. Я не получаю исключения, когда код встроен, как в первом примере.

Я рассмотрел такие возможности, как Func, ProductLocationHistory>>, а также с помощью Expression‹...>, но не смог найти пример того, что ищу. Решение вида Product.CurrentProductLocationHistory() может быть даже лучше. Абсолютно лучшее решение было бы еще более общим и имело бы форму:

Current<T> (IQueryable<T> collection, string field) { return entity with max field of collection } 

Помощь будет очень признательна, я пробовал это в течение длительного времени, и я уверен, что это должно быть возможно, поскольку внутренние функции самого LINQ - Any, First, Count, Max - также остаются доступными для запросов, если это необходимо.

Обновить

В настоящее время выполняются следующие работы:

 Expression<Func<Product, ProductLocationHistory>> expression = IQueryable.Current(null);
        var ken = productService.GetAllProducts()
            .Where(p => p.OnLoan)
            .Select(expression)
            .Where(plh => plh.Storehouse != null)
            .Select(plh => plh.Storehouse)
            .Distinct();

public static Expression<Func<Product, ProductLocationHistory>> Current(this EntitySet<ProductLocationHistory> productLocationHistories)
    {
        Expression<Func<Product, ProductLocationHistory>> expression = p => p.ProductLocationHistories
                                         .SingleOrDefault(plh => plh.DateCreation == p.ProductLocationHistories.Max(plhs => plhs.DateCreation));
        return expression;
    }

Шаг в правильном направлении, но я еще не полностью удовлетворен. Я хочу иметь возможность использовать p.ProductLocationHistories().Current(), поэтому мои поиски продолжаются.

Спасибо уже Кирилл! Это первый раз, когда я вижу код C#, переведенный в SQL! Хороший шаг в правильном направлении!


person Paul1977    schedule 16.11.2012    source источник
comment
Имея этот метод расширения, вы можете вызвать .Current(), если я не ошибаюсь. Можете ли вы показать образец соглашения о вызовах, которое вы хотели бы использовать? Кроме того, вероятно, намного быстрее использовать OrderByDescending(pld => pld.DateCreation).FirstOrDefault(), чем запрашивать max, а затем использовать SingleOrDefault.   -  person jessehouwing    schedule 19.11.2012
comment
Я попробую решение link, как только смогу перейти на ASP.NET 4.5. В настоящее время мы приобретаем Visual Studio 2012. Позже я могу обновить этот вопрос.   -  person Paul1977    schedule 16.01.2013


Ответы (1)


Вы можете установить поле с выражением:

Expression<Func<ProductLocationHistory, bool>> currentSelector = plh => plh.DateCreation == p.ProductLocationHistories.Max(grp => grp.DateCreation)

и использовать его где угодно:

var storehousesWithBorrowedItems = productService.GetAllProducts()
    .Select(p => p.ProductLocationHistories
        .SingleOrDefault(currentSelector ))
    .Select(plh => plh.Storehouse)
    .Distinct();
person Kirill Bestemyanov    schedule 17.11.2012
comment
Насколько я помню, это работает, но не так удобно, как хотелось бы. .Current() очень часто используется в журналах, и я готов сделать все возможное, чтобы исправить это. Позор Microsoft за то, что она не включила возможность писать свои собственные запрашиваемые функции. Я думаю, что это большое упущение. В остальном LINQ делает мою жизнь намного проще, чем раньше, благодаря динамическим текстовым строкам SQL br. - person Paul1977; 16.01.2013