Search code examples
c#linq-to-entitiesexpressionentity-framework-4.1ef4-code-only

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


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, because my grid component will add more things to query later, so i can't switch to Linq to Object.


Solution

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