简体   繁体   中英

C# LINQ statement with joins, group by and having then mapped into list object

I have a model called ElectricityBillSiteExceeding that looks like this:

public class ElectricityBillSiteExceeding
{
    public string GroupInvoiceNumber { get; set; }
    public int ElectricityBillMainId { get; set; }
    public string SiteNo { get; set; }
    public decimal BillSiteTotal { get; set; }
    public decimal MaximumAmount { get; set; }
}

I want to create a list of this type and use it to feed a grid on one of my pages, the purpose is to show which site has bills that exceed the max amount allowed.

I have written the SQL which will give me this dataset, it looks like this:

SELECT SUM(ElectricityBillSiteTotal),
       ebs.ElectricityBillMainId, 
       SiteNo, 
       ebm.GroupInvoiceNumber, 
       es.MaximumAmount
FROM dbo.ElectricityBillSites ebs
LEFT JOIN dbo.ElectricityBillMains ebm 
ON ebs.ElectricityBillMainId = ebm.ElectricityBillMainId
LEFT JOIN dbo.ElectricitySites es 
ON ebs.SiteNo = es.SiteNumber
GROUP BY ebs.ElectricityBillMainId, SiteNo, ebm.GroupInvoiceNumber, es.MaximumAmount
HAVING SUM(ElectricityBillSiteTotal) <> 0 AND SUM(ElectricityBillSiteTotal) > es.MaximumAmount

I'm now in my repository trying to write the method which will go to the database and fetch this dataset so that I can power my grid for the user to see.

This is where I'm struggling. I have written a basic LINQ statement to select from a couple of tables, however I'm unsure how I can incorporate the group by and having clause from my SQL and also how I can then turn this IQueryable object into my List<ElectricityBillSiteExceeding> object.

What I have so far

public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
    var groupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites

    join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals
    billMains.ElectricityBillMainId

    join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber

    //TODO: group by total, mainId, siteNo, GroupInv, MaxAmt and Having no total = 0 and total > max

    select new 
    {
        groupInv = billMains.GroupInvoiceNumber,
        mainId = billMains.ElectricityBillMainId,
        siteNo = billSites.SiteNo,
        total = billSites.ElectricityBillSiteTotal,
        max = sites.MaximumAmount
    };

    //TODO: Map the result set of the linq to my model and return

    throw new NotImplementedException();
}

Can anyone point me in the right direction here?

Before getting into grouping , you need to be aware that the default join in LINQ is always an INNER JOIN . Take a look at the MSDN page How to: Perform Left Outer Joins . However, in the solution I'm presenting below, I'm using INNER JOIN s since you are using fields from the other tables in your grouping and having clauses.

For reference on grouping using LINQ, check out How to: Group Query Results on MSDN .

A solution specific to your case is going to look something like:

public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
    var qryGroupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
                                        join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals billMains.ElectricityBillMainId
                                        join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
                                        where billSites.ElectricityBillSiteTotal != 0 && billSites.ElectricityBillSiteTotal > sites.MaximumAmount
                                        group new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount }
                                        by new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount } into eGroup
                                        select eGroup.Key;
    var inMemGroupedBillSitesThatExceed = qryGroupedBillSitesThatExceed.AsEnumerable();
    var finalResult = inMemGroupedBillSitesThatExceed.Select(r => new ElectricityBillSiteExceeding()
    {
        BillSiteTotal = r.ElectricityBillSiteTotal,
        ElectricityBillMainId = r.ElectricityBillMainId,
        GroupInvoiceNumber = r.GroupInvoiceNumber,
        MaximumAmount = r.MaximumAmount,
        SiteNo = r.SiteNo,
    });
    return finalResult.ToList();
}

The correct Linq query for your sql is the following. See Left Join to understand the DefaultIfEmpty and also the notes there about the use of ?. in the following group by .

(About the having - in linq you just provide a where after the group by )

var result = from ebs in ElectricityBillSites
             join ebm in ElectricityBillMains on ebs.ElectricityBillMainId equals ebm.ElectricityBillMainId into ebmj
             from ebm in ebmj.DefaultIfEmpty()

             join es in ElectricitySites on ebs.SiteNo equals es.SiteNumber into esj
             from es in esj.DefaultIfEmpty()

             group new { ebs, ebm, es } by new {  ebs.ElectricityBillMainId, ebs.SiteNo, ebm?.GroupInvoiceNumber, es?.MaximumAmount } into grouping
             let sum = grouping.Sum(item => item.ebs.ElectricityBillSiteTotal)
             where sum > 0 && sum > grouping.Key.MaximumAmount

             orderby sum descending

             select new ElectricityBillSiteExceeding
             {
                 GroupInvoiceNumber = grouping.Key.GroupInvoiceNumber,
                 ElectricityBillMainId = grouping.Key.ElectricityBillMainId,
                 SiteNo = grouping.Key.SiteNo,
                 BillSiteTotal = sum,
                 MaximumAmount = grouping.Key.MaximumAmount
             };

The error you get:

An expression tree lambda may not contain a null propagating operator

By reading this I conclude that you have an older versino of the provider and thus replace the group by code from the code above with the following:

let GroupInvoiceNumber = ebm == null ? null : ebm.GroupInvoiceNumber
let MaximumAmount = es == null ? 0 : es.MaximumAmount
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, GroupInvoiceNumber, MaximumAmount } into grouping

This probably will be enough. You could use AutoMapper. It will trivialize mapping to classes.

var resultList = groupedBillSitesThatExceed
        .AsEnumerable() //Query will be completed here and loaded from sql to memory
        // From here we could use any of our class or methods
        .Select(x => new ElectricityBillSiteExceeding
        {
            //Map your properties here
        })
        .ToList(); //Only if you want List instead IEnumerable
return resultList;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM