Search code examples
c#listforeachienumerablepdfsharp

Iterating through a list, but can't access distinct item values


I am building invoices with PDFsharp.

This is my SQL table (tblTripsPerMonth):

enter image description here

I've bound it to an ObservableCollection (PaidTrips).

The goal is to create one (1) PDF for each distinct "CompanyName" in the LicenseHolderID column. To achieve this, I convert to a List, and group by LicenseHolderID.

var paidTrips = PaidTrips
    .GroupBy(p => new {
        p.LicenseHolderID
})
.ToList();

After that, I iterate over the list with a foreach-loop:

foreach (var trip in paidTrips) {
    
    // I grab the distinct name
    string licenseholder = trip.Key.LicenseHolderID.ToString();
    
    // I summarize many of the columns
    decimal totalPayment = trip.Sum(x => x.Payment);
    decimal totalPaymentNet = trip.Sum(x => x.Payment);
    decimal totalOrderFee = trip.Sum(x => x.Payment);
    decimal totalPaymentFee = trip.Sum(x => x.Payment);
    
    // I grab the first value of some other columns, which won't change
    string licenseholderInvoiceID = trip.Select(x => x.LicenseHolderInvoiceID).FirstOrDefault().ToString();
    string ridelRegionInvoiceID = trip.Select(x => x.RidelRegionInvoiceID ).FirstOrDefault().ToString();

// Creating PDF document using PDFsharp:
// PDFsharp code
// PDFsharp code
// PDFsharp code
}

I am able to create a PDF-document, one for each distinct LicenseHolderID, by adding the licenseHolderID-string to the file-path of PDFsharps document.Save().

But I am not able to fill the PDF-document with all the information I need. I have the summarized amounts - good, because I absolutely need to display the full invoice amount. But -- I also need to go into detail. I do not have amounts per VehicleID.

For "CompanyName1", that'd be AG4203000002 and AG4203000003, with their corresponding row data.

I did do this...

IEnumerable<string> vehicleIds = trip
    .Select(x => x.VehicleID)
    .Distinct()
    .ToArray();
string vehicle = string.Join(", ", vehicleIds);

...in order to separate the VehicleID distinct values from eachother. I then put the string vehicle into PDFsharps way of drawing text onto the PDF (DrawString):

gfx.DrawString(vehicle, /* font and color customization */);

Which gives me the correct amount of VehicleID for each LicenseHolderID, but in one, long string... Not optimal.

Which brings me to my question: A): I need to bring along the row data of the other columns pertaining to the distinct vehicles in VehicleID, so that I can fill in details in my PDFs, and not just the summarized values, and B): if the solution to A) involves getting rid of the long, hacky string (vehicle), that'd be optimal, if not - that's okay too.

UPDATE (adding code from @Dai's answer):

// PaidTrip = My holder class, which I've bound to a ObservableCollection (PaidTrips)
var paidTrips = PaidTrips.ToList(); // I was unsure about this one
IEnumerable<IGrouping<String, PaidTrip>> tripsGroupedByCompany = paidTrips.GroupBy(pt => pt.LicenseHolderID);

foreach (IGrouping<String, PaidTrip>> companyGroup in tripsGroupedByCompany) {
    
    string licenseHolderId = companyGroup.Key;
    gfx.DrawString(/* code goes foo */);

    // I tried adding Key here, but that gave me a squiggly under "t.VehicleID"
    var groupedByVehicle = companyGroup.GroupBy(t => t.VehicleID);
    
    foreach (IGrouping<String, PaidTrip> vehicleGroup in groupedByVehicle) {

        // This is where I get a red squiggly; under Key
        String vehicleId = groupedByVehicle.Key;

        gfx.DrawString(/* code goes foo */);
        
        foreach (PaidTrip trip in vehicleGroup) {
      
            gfx.DrawString(/* code goes foo */);
        }
    }
}

This is the error:

CS1061: IEnumerable<IGrouping<string, PaidTrip>> does not contain a definition for 'Key' and no accessible extension method 'Key' accepting a first argument of type 'IEnumerable<IGrouping<string, PaidTrip>>' could be found (are you missing a using directive or an assembly reference?)


Solution

  • I need to bring along the row data of the other columns pertaining to the distinct vehicles in VehicleId, so that I can fill in details in my PDFs, and not just the summarized values.

    If I understand you correctly, for each company (LicenseHolderId) you want their relevant Trip objects, but grouped by VehicleId - that's straightforward, just add another GroupBy - and you can iterate over them in an inner foreach:

    List<Trip> paidTrips = ...
    
    IEnumerable< IGrouping<String,Trip> > tripsGroupedByCompany = paidTrips.GroupBy( pt => pt.LicenseHolderId );
    
    foreach( IGrouping<String,Trip> companyGroup in tripsGroupedByCompany )
    {
        String licenseHolderId = companyGroup.Key;
    
        gfx.DrawString( "Company: " + licenseHolderId + "\r\n" );
    
        var groupedByVehicle = companyGroup.GroupBy( t => t.VehicleId );
        foreach( IGrouping<String,Trip> vehicleGroup in groupedByVehicle )
        {
            String vehicleId = vehicleGroup.Key;
            
            gfx.DrawString( "\tVehicle: " + vehicleId + "\r\n" );
    
            foreach( Trip trip in vehicleGroup )
            {
                gfx.DrawString( $"\t\tTrip: {trip.Year}-{trip.Month:00}. {trip.PaymentNet,11:C2}\r\n" );
            }
        }
    }
    
    • I added some formatting instructions and characters which only really apply to text-mode (console applications), not PDF rendering, but if you're curious:

      • I used tab characters (\t) to indicate indent so related data is visually grouped.
      • I used formatting specifier :00 to ensure the Month value is displayed as a 2-digit value with a leading zero.
      • I used formatting specifier ,11:C2 to ensure the PaymentNet value is formatted as a Currency value with 2 decimal places, and is always left-padded to at-least 11 characters width.
    • This will give you output like this (below).

    • Note that while in your source-data each VehicleId has only a single trip associated with it, my code above allows a single VehicleId to have multiple trips with both the same, and different, LicenseHolderId values, though the sample print-out below only shows 1 trip per Vehicle.

    Company: CompanyName1
        Vehicle: AG4203000002
            Trip: 2021-07. $107,088.68
    
        Vehicle: AG4203000003
            Trip: 2021-07. $138,761.32
    
    Company: CompanyName2:
        Vehicle: AG4203000004
            Trip: 2021-07. $129,264.15
    
        Vehicle: AG4203000005
            Trip: 2021-07.  $87,273.58
    

    But note that this code above is bad because it crosses concerns (it does 2 separate things: it traverses a non-trivial object-graph, and it renders data to your PDF library).

    A better design would separate out the graph-traversal (perhaps to a single extension method) thus making the PDF rendering code much simpler, however I cannot give you any code examples of this without knowing more about your database design and if/how you're using EF.