开发者

How to reuse part of Linq to Entity Select expression in EF4.1 code only scenario

开发者 https://www.devze.com 2023-03-11 02:57 出处:网络
Here is very simplified version of code that i have: class PrintJob : IEntity { public string UserName { get; set; }

Here is very simplified version of code that i have:

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; }
}

Then i have 2 queries:

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()
    });

What would be suggestions for extracting parts where i assign values to TotalPagesPrinted, AveragePagesPrinted and PercentOfSinglePagePrintJobs out from those 2 queries, so that it can be reused and would follow DRY principle.

I'm using EF 4.1 code only approach and switching to another technology or approach is not an option. Also i cannot materialize that data, i need to keep it as query, beca开发者_StackOverflow社区use my grid component will add more things to query later, so i can't switch to Linq to Object.


I would create a new class CLASSNAME that has two properties

  • PrintJobReportItem type
  • GROUPING IEnumerable<IGrouping<TKey, TSource>>

Then create an extension method

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
         };
}

Then use like so, I haven't tested but I think it would work

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()

                 });


The most straightforward thing I could think to do is create a PrintJobByDepartmenReportItem constructor that accepts a single IEnumerable<IGrouping<string, PrintJob>> parameter (which I believe should be the type of variable g in your sample). Keep in mind this also requires a parameter-less constructor definition, and your inherited classes would also need to implement a constructor prototype to call the base class constructor with the parameter:

Constructor

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);
}

Inherited Constructor

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();
}

Queries

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));

This does have the one downside of assuming you will always be grouping by a string member, but you could presumably GroupBy(i => i.MyProperty.ToString()) when appropriate or possibly change the prototype to accept IEnumerable<IGrouping<object, PrintJob>>.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号