Использование пользовательских выражений в LINQ приводит к запросу для каждого использования - PullRequest
1 голос
/ 11 июля 2011

У меня следующая проблема: В нашей базе данных мы записываем билеты в службу поддержки и бронируем часы по билетам. Между ними отчет о посещении. Так и есть: ticket => visitreport => hours.

Часы имеют определенный «вид», который не определяется индикатором типа в записи часа, но компилируется путем проверки различных свойств часа. Например, час, который имеет клиента, но не является часом обслуживания, всегда является часом счета.

Последнее, что я хочу, это то, что определения этих «видов» бродят повсюду в коде. Они должны быть в одном месте. Во-вторых, я хочу иметь возможность рассчитать общее количество часов из различных коллекций часов. Например, сплющенная коллекция билетов с определенной датой и определенным клиентом. Или все регистрации, которые помечены как «решение».

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

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

    public static decimal TreeHours(this IEnumerable<Uren> h, FactHourType ht)
{
    IQueryable<Uren> hours = h.AsQueryable();
    ParameterExpression pe = Expression.Parameter(typeof(Uren), "Uren");
    Expression left = Expression.Property(pe, typeof(Uren).GetProperty("IsOsab"));
    Expression right = Expression.Constant(true, typeof(Boolean));
    Expression isOsab = Expression.Equal(Expression.Convert(left, typeof(Boolean)), Expression.Convert(right, typeof(Boolean)));

    left = Expression.Property(pe, typeof(Uren).GetProperty("IsKlant"));
    right = Expression.Constant(true, typeof(Boolean));
    Expression isCustomer = Expression.Equal(Expression.Convert(left, typeof(Boolean)), Expression.Convert(right, typeof(Boolean)));
    Expression notOsab;
    Expression notCustomer;
    Expression final;
    switch (ht)
    {
        case FactHourType.Invoice:
            notOsab = Expression.Not(isOsab);
            final = Expression.And(notOsab, isCustomer);
            break;
        case FactHourType.NotInvoice:
            notOsab = Expression.Not(isOsab);
            notCustomer = Expression.Not(isCustomer);
            final = Expression.And(notOsab, notCustomer);
            break;
        case FactHourType.OSAB:
            final = Expression.And(isOsab, isCustomer);
            break;
        case FactHourType.OsabInvoice:
            final = Expression.Equal(isCustomer, Expression.Constant(true, typeof(Boolean)));
            break;
        case FactHourType.Total:
            final = Expression.Constant(true, typeof(Boolean));
            break;
        default:
            throw new Exception("");
    }
    MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { hours.ElementType },
hours.Expression,
Expression.Lambda<Func<Uren, bool>>(final, new ParameterExpression[] { pe })
);
    IQueryable<Uren> result = hours.Provider.CreateQuery<Uren>(whereCallExpression);
    return result.Sum(u => u.Uren1);

}

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

Мне удалось опрашивать до конца. На шаге 1 я собираю необработанные данные. На шаге 2 я упорядочиваю данные, а затем выкладываю их на страницу. На шаге 3 данные преобразуются в JSon и отправляются клиенту. Итого часов по билету.

Проблема в том, что я получаю один запрос на часы для каждого билета . Это сотни запросов! Это слишком много ...

Я попробовал следующий подход:

DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Ticket>(t => t.Bezoekrapport);
options.LoadWith<Bezoekrapport>(b => b.Urens);
dc.LoadOptions = options;

Bezoekrapport - просто голландский, что означает «посещение». Когда я смотрю на запрос, который получает билеты, я вижу, что он присоединяется к отчету Bezoekrapport / visitreport, но не к часам, которые к нему привязаны.

Второй подход, который я использовал, - это ручное объединение часов в LINQ, но это также не работает.

Я должен сделать что-то не так. Каков наилучший подход здесь?

Следующие фрагменты кода показывают, как я получаю данные. После вызова toList () для strHours на последнем шаге я получаю град запросов. Два дня я пытался обойти это, но это просто не работает ... Что-то не так в моем подходе или в функции TreeHours.

Шаг 1:

IQueryable<RelationHoursTicketItem> HoursByTicket =
            from Ticket t in allTickets
            let RemarkSolved = t.TicketOpmerkings.SingleOrDefault(tr => tr.IsOplossing)
            let hours = t.Bezoekrapport.Urens.
                Where(h =>
                      (dateFrom == null || h.Datum >= dateFrom)
                      && (dateTo == null || h.Datum <= dateTo)
                      && h.Uren1 > 0)
            select new RelationHoursTicketItem
                       {
                           Date = t.DatumCreatie,
                           DateSolved = RemarkSolved == null ? (DateTime?)null :                                                                                              RemarkSolved.Datum,
                           Ticket = t,
                           Relatie = t.Relatie,
                           HoursOsab = hours.TreeHours(FactHourType.OSAB),
                           HoursInvoice = hours.TreeHours(FactHourType.Invoice),
                           HoursNonInvoice = hours.TreeHours(FactHourType.NotInvoice),
                           HoursOsabInvoice = hours.TreeHours(FactHourType.OsabInvoice),
                           TicketNr = t.Id,
                           TicketName = t.Titel,
                           TicketCategorie = t.TicketCategorie,
                           TicketPriority = t.TicketPrioriteit,
                           TicketRemark = RemarkSolved
                       };

Шаг 2

        sort = sort ?? "TicketNr";
        IQueryable<RelationHoursTicketItem> hoursByTicket = GetRelationHours(relation,     dateFrom, dateTo, withBranches);
        IOrderedQueryable<RelationHoursTicketItem> orderedResults;

        if (dir == "ASC")
        {
            orderedResults = hoursByTicket.OrderBy(sort);
}
        else
        {
            orderedResults = hoursByTicket.OrderByDescending(sort);
        }
        IEnumerable<RelationHoursTicketItem> pagedResults = orderedResults.Skip(start ?? 0).Take(limit ?? 25);
        records = hoursByTicket.Count();
        return pagedResults;

Шаг 3:

 IEnumerable<RelationHoursTicketItem> hours = _hourReportService.GetRelationReportHours(relation, dateFrom, dateTo, metFilialen, start, limit, dir, sort, out records);

        var strHours = hours.Select(h => new
               {
                   h.TicketNr,
                   h.TicketName,
                   RelationName = h.Relatie.Naam,
                   h.Date,
                   TicketPriority = h.TicketPriority.Naam,
                   h.DateSolved,
                   TicketCategorie = h.TicketCategorie == null ? "" : h.TicketCategorie.Naam,
                   TicketRemark = h.TicketRemark == null ? "" : h.TicketRemark.Opmerking,
                   h.HoursOsab,
                   h.HoursInvoice,
                   h.HoursNonInvoice,
                   h.HoursOsabInvoice
               });

1 Ответ

1 голос
/ 11 июля 2011

Не думаю, что ваш метод расширения TreeHours может быть преобразован в SQL с помощью LINQ за один раз. Таким образом, оцениваются при выполнении каждого конструктора строки, вызывая 4 вызовов базы данных в этом случае на строку.

Я бы упростил ваш запрос LINQ, чтобы вернуть вам необработанные данные из SQL, используя простой JOIN для получения всех билетов и часов. Затем я бы сгруппировал и отфильтровал часы по типу в памяти. В противном случае, если вам действительно нужно для выполнения ваших операций в SQL, тогда посмотрите на метод CompiledQuery.Compile. Это должно быть в состоянии обрабатывать не делать запросы на строку. Я не уверен, что вы получите switch, но вы можете преобразовать его, используя оператор ?:.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...