Linq, упорядочение IQueryable с помощью IOrderedEnumerable - PullRequest
1 голос
/ 21 января 2012

У меня есть сетка Telerik MVC, которую я настроил для пользовательского связывания с IQueryable, чтобы упростить сортировку вычисляемого столбца. Поведение сортировки по умолчанию, когда сетка сортируется по этому свойству, таково:

data = data.OrderBy(product => product.oneMthCost_IntOnly);

«data» - это IQueryable, product.oneMthCost_IntOnly не возвращается из базы данных, это вычисляемое свойство, которое вычисляется при вызове метода доступа «get» для этого свойства в «SearchViewModel»:

public class SearchViewModel
{
    public int ID  { get; set; }
    public string lend_name  { get; set; }
    public decimal? pDes_rate  { get; set; }
    public string pDes_details  { get; set; }
    public int? pDes_totTerm  { get; set; }
    public decimal? pDes_APR  { get; set; }
    public string pDes_revDesc  { get; set; }
    public string pMax_desc  { get; set; }
    public DateTime? dDipNeeded { get; set; }
    public DateTime? dAppNeeded { get; set; }

    public decimal oneMthCost_IntOnly
    {
        get { return ProductOperations.CalculateSingleYearInterestCost(SourcingModel.search.LoanAmt, (decimal)pDes_rate); }
    }
}

Чтобы объяснить, как возвращается SearchViewModel («данные»), она основана на модели данных сущностей, которая использует следующий отложенный запрос Linq в качестве основы для сетки, проецируемой в SearchViewModel.

    //Return the required products
    var model = from p in Product.Products
                where p.archive == false && ((Prod_ID == 0) || (p.ID == Prod_ID))
                select new SearchViewModel
                    {
                        ID = p.ID,
                        lend_name = p.Lender.lend_name,
                        pDes_rate = p.pDes_rate,
                        pDes_details = p.pDes_details,
                        pDes_totTerm = p.pDes_totTerm,
                        pDes_APR = p.pDes_APR,
                        pDes_revDesc = p.pDes_revDesc,
                        pMax_desc = p.pMax_desc,
                        dDipNeeded = p.dDipNeeded,
                        dAppNeeded = p.dAppNeeded
                    };

Используя поведение сетки по умолчанию, поэтому ниже:

data = data.OrderBy (product => product.CalculatedProp);

Выдает эту ошибку при сортировке этого столбца:

Указанный член типа 'oneMthCost_IntOnly' не поддерживается в LINQ to Entities. Только инициализаторы, члены сущности и сущности свойства навигации поддерживаются.

Что ж, это имеет смысл, дерево выражений не знает, каким будет это значение, пока оно не получит его с помощью метода доступа get. Итак, я полностью смирился с тем, что мне нужно будет материализовать весь набор объектов, выполнить вычисления для каждой строки, а затем отсортировать IQueryable по нему (к сожалению, бизнес-логика слишком сложна для дерева выражений, чтобы повернуть расчет в SQL, поэтому требуется метод C #). Поэтому я делаю следующее:

var calcdata = data.ToList().OrderBy(p => p.oneMthCost_IntOnly);

, которая материализует все данные, выполняет все вычисления и сортирует их в IOrderedEnumerable calcdata ... вот в чем проблема:

Как мне соединить "calcdata" с "data", чтобы отсортировать данные IQueryable по ключу IOrderedEnumerable "calcdata"? Перепривязка сетки к «calcdata» приводит к путанице при разбивке по страницам, однако, если вы предложите, что это лучший путь вперед, я тоже могу пойти по этому пути.

Извините, это немного затянуто, я просто хотел бы включить довольно много информации, чтобы дать вам наиболее полную картину.

Спасибо

Mark

Ответы [ 2 ]

1 голос
/ 23 января 2012

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

calcdata = data.ToList().OrderByDescending(p => p.oneMthCost_IntOnly);

Это материализует все строки данных при сортировке таким способом (я знаю, что это не очень эффективно, но это единственный способ выполнить такую ​​сортировку на данных, о которых база данных не знает). Проблема, с которой я столкнулся, заключалась в том, что при создании страницы IOrderedEnumerable создает IOrderedEnumerable, как показано ниже:

        //Then paging
        if (command.PageSize > 0)
        {
            calcdata = calcdata.Skip((command.Page - 1) * command.PageSize);
        }

        calcdata = calcdata.Take(command.PageSize);

Это испускает IEnumerable back (где я предполагал, что он испустит IOrderedEnumerable), поэтому пытался привести к этому типу, который затем прервал пейджинг. Выше работает, когда calcdata является IEnumerable, а не IOrderedEnumerable. Присоединение к IQueryable не требуется (зачем возвращаться в базу данных, когда у вас есть вся необходимая информация?), Поэтому мы привязываемся к IEnumerable.

Спасибо за вводную Crab Bucket, она привела меня к новым направлениям, которые помогли мне в конечном итоге решить эту проблему!

С уважением,

Mark

1 голос
/ 21 января 2012

ОК - это довольно высокий уровень и краткий ответ на ваш очень полный вопрос.

У нас возникли проблемы с заказом очень сложного набора данных. Как и вы, мы пытались их реализовать, и представление было ужасным. В итоге мы создали представление в базе данных со столбцами, по которым нам нужно было упорядочить. Мы включили это представление в модель Entity, которая затем ведет себя как таблица (некоторые предостережения, если честно).

Как только у нас появился вид в модели EF, мы могли бы использовать порядок без материализации. Вы также можете просмотреть нужное объединение.

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

Если вы не можете поместить View в свои базы данных, вы можете использовать такие же конструкции структуры сущностей, как QueryViews . Однако у них есть свои ограничения - например, больше не может обновлять модель автоматически, может присоединить к ним что-либо кроме QueryView. Я считаю, что это сработает, но вам нужно испачкать руки и внести поправки в модель EF xml напрямую.

Предостережения для просмотров

Нам пришлось немного поиграться с отображением сохраненных процессов в представлениях. Поскольку это было только для чтения, мы не хотели беспокоиться о вставках, но при удалении зависимых объектов я удалил удаление из представления. TBH - мы взломали его на этом этапе и включили фиктивный процесс, который затронул один ряд и эту работу. Должен быть лучший ответ, чем этот.

ОК - менее кратко, чем я ожидал, когда начну писать.

EDIT

Если вы хотите объединить две коллекции, вы можете использовать ключевое слово join. Так как один из базы данных, а другой из c #, тогда им обоим придется хранить IEnumerables - т.е. запрос EF, реализованный с помощью ToList(), и объект c # в соответствующей коллекции, т.е. общий список.

Тогда синтаксис может быть

var joinedCollections = from efItem in efCollection
                     join charpItem in charpCollection on csharpItem.CommonId equals efItem.CommonID
                     select select new {csharp.field1, efItem.field1};

Конечно, это предполагает наличие общего ключа между двумя коллекциями. Также эффективность может быть ужасной.

...