Как повторно использовать часть выражения Linq to Entity Select в сценарии только с кодом EF4.1 - PullRequest
2 голосов
/ 08 июня 2011

Вот очень упрощенная версия кода, который у меня есть:

class PrintJob : IEntity
{
    public string UserName { get; set; }
    public string Departmen { get; set; }
    public int PagesPrinted { get; set; }
}

class PrintJobReportItem
{
    public int TotalPagesPrinted { get; set; }
    public int AveragePagesPrinted { get; set; }
    public int PercentOfSinglePagePrintJobs { get; set; }
}

class PrintJobByUserReportItem : PrintJobReportItem
{
    public string UserName { get; set; }
}

class PrintJobByDepartmenReportItem : PrintJobReportItem
{
    public string DepartmentName { get; set; }
    public int NumberOfUsers { get; set; }
}

Тогда у меня есть 2 запроса:

var repo = new Repository(...);

var q1 = repo.GetQuery<PrintJob>()
    .GroupBy(pj => pj.UserName)
    .Select(g => new PrintJobByUserReportItem
    {
    #region this is PrintJobReportItem properties
        TotalPagesPrinted = g.Sum(p => p.PagesPrinted),
        AveragePagesPrinted = g.Average(p => p.PagesPrinted),
        PercentOfSinglePagePrintJobs = g.Count(p => p.PagesPrinted == 1) / (g.Count(p => p.PagesPrinted) != 0 ? g.Count(p => p.PagesPrinted) : 1) * 100,
    #endregion    
        UserName = g.Key
    });

var q2 = repo.GetQuery<PrintJob>()
    .GroupBy(pj => pj.Departmen)
    .Select(g => new PrintJobByDepartmenReportItem
    {
    #region this is PrintJobReportItem properties
        TotalPagesPrinted = g.Sum(p => p.PagesPrinted),
        AveragePagesPrinted = g.Average(p => p.PagesPrinted),
        PercentOfSinglePagePrintJobs = g.Count(p => p.PagesPrinted == 1) / (g.Count(p => p.PagesPrinted) != 0 ? g.Count(p => p.PagesPrinted) : 1) * 100,
    #endregion    
        DepartmentName = g.Key,
        NumberOfUsers = g.Select(u => u.UserName).Distinct().Count()
    });

Каковы были бы предложения по извлечению частей, в которых я присваиваю значения TotalPagesPrinted, AveragePagesPrinted и PercentOfSinglePagePrintJobs из этих двух запросов, чтобы его можно было использовать повторно и следовать принципу DRY.

Я использую только код EF 4.1, и переключение на другую технологию или подход не вариант. Также я не могу материализовать эти данные, мне нужно сохранить их как запрос, потому что мой компонент сетки добавит больше вещей для запроса позже, поэтому я не могу переключиться на Linq to Object.

Ответы [ 2 ]

2 голосов
/ 08 июня 2011

Я бы создал новый класс CLASSNAME с двумя свойствами

  • PrintJobReportItem тип
  • GROUPING IEnumerable<IGrouping<TKey, TSource>>

Затем создайте метод расширения

public static IQueryable<CLASSNAME> EXTENSIONNAME<TKey, TSource>(this IEnumerable<IGrouping<TKey, TSource>> source)
{
  return from g in source
         select new CLASSNAME
         {
           PrintJobReportItem = new PrintJobReportItem
                                {
                                  TotalPagesPrinted = g.Sum(p => p.PagesPrinted),
                                  AveragePagesPrinted = etc...,
                                  PercentOfSinglePagePrintJobs = etc...,
                                },
           GROUPING = g
         };
}

Тогда используйте так, я не проверял, но думаю, что это будет работать

var q1 = repo.GetQuery<PrintJob>()
    .GroupBy(pj => pj.UserName)
    .EXTENSIONNAME()
    .Select(g => new PrintJobByDepartmenReportItem
                 {
                    PrintJobReportItem = g.PrintJobReportItem,
                    DepartmentName = g.GROUPING.Key,
                    NumberOfUsers = g.GROUPING.Select(u => u.UserName).Distinct().Count()

                 });
0 голосов
/ 08 июня 2011

Самая простая вещь, которую я мог бы сделать, - это создать конструктор PrintJobByDepartmenReportItem, который принимает один параметр IEnumerable<IGrouping<string, PrintJob>> (который, я считаю, должен быть типом переменной g в вашем примере). Имейте в виду, что для этого также требуется определение конструктора без параметров, и ваши унаследованные классы также должны будут реализовать прототип конструктора для вызова конструктора базового класса с параметром:

Конструктор

public PrintJobReportItem()
{
}

public PrintJobReportItem(IEnumerable<IGrouping<string, PrintJob>> g)
{
    this.TotalPagesPrinted = g.Sum(i => i.GetEnumerator().Current.PagesPrinted);
    this.AveragePagesPrinted = g.Average(i => i.GetEnumerator().Current.PagesPrinted);
    this.PercentOfSinglePagePrintJobs = g.Count(i => i.GetEnumerator().Current.PagesPrinted == 1) * 100 / g.Count(i => i.GetEnumerator().Current.PagesPrinted > 1);
}

Унаследованный конструктор

public PrintJobByDepartmentReportItem(IEnumerable<IGrouping<string, PrintJob>> g) : base(g)
{
    this.DepartmentName = g.First().Key;
    this.NumberOfUsers = g.Select(i => i.GetEnumerator().Current.UserName).Distinct().Count();
}

Запросы

var q1 = repo.GetQuery<PrintJob>()
    .GroupBy(pj => pj.UserName)
    .Select(g => new PrintJobByUserReportItem(g));

var q2 = repo.GetQuery<PrintJob>()
    .GroupBy(pj => pj.Department)
    .Select(g => new PrintJobByDepartmentReportItem(g));

Это имеет один недостаток, предполагая, что вы всегда будете группировать по строковому члену, но вы можете предположительно GroupBy(i => i.MyProperty.ToString()), когда это уместно, или, возможно, изменить прототип для принятия IEnumerable<IGrouping<object, PrintJob>>.

...