【问题标题】:Iterating through a list, but can't access distinct item values遍历列表,但无法访问不同的项目值
【发布时间】:2021-11-21 18:38:46
【问题描述】:

我正在使用PDFsharp 创建发票。

这是我的 SQL 表 (tblTripsPerMonth):

我已将其绑定到 ObservableCollection (PaidTrips)。

目标是为LicenseHolderID 列中的每个不同的“公司名称”创建一 (1) 个 PDF。 为此,我转换为List,并按LicenseHolderID 分组。

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

之后,我使用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
}

通过将licenseHolderID-string 添加到 PDFsharps document.Save() 的文件路径中,我能够创建一个 PDF 文档,每个不同的 LicenseHolderID 一个。

但我无法在 PDF 文档中填写我需要的所有信息。 我有汇总金额 - ,因为我绝对需要显示完整的发票金额。 但是——我还需要详细说明。我确实没有有每个VehicleID 的金额。

对于“CompanyName1”,应该是 AG4203000002 和 AG4203000003,以及它们对应的行数据。

我确实这样做了......

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

...为了将VehicleID 不同的值彼此分开。 然后我将string vehicle 放入PDFsharps 中,将文本绘制到PDF 上(DrawString):

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

这为每个LicenseHolderID 提供了正确数量的VehicleID,但在一个长字符串中...不是最佳的。

这让我想到了我的问题: A):我需要携带与VehicleID 中不同车辆相关的其他列的行数据,所以我可以在我的 PDF 中填写详细信息,而不仅仅是汇总值,以及 B):如果 A) 的解决方案涉及摆脱冗长的、hacky 字符串(vehicle),如果不是,那将是最佳选择 - 那也没关系。

更新(从@Dai 的回答中添加代码):

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

这是错误:

CS1061:IEnumerable> 不包含“Key”的定义,并且没有可访问的扩展方法“Key”接受“IEnumerable>”类型的第一个参数找到(您是否缺少 using 指令或程序集引用?)

【问题讨论】:

  • 这是在使用 WPF 的 交互式 桌面应用程序中,还是“无头”后台服务或 Web 应用程序?因为如果是后者,使用ObservableCollection 的理由为零。另外,为什么你的桌子有tbl 前缀? (您应始终避免对数据库对象使用 Systems Hungarian Notation 前缀,否则当您将 TABLE 更改为 VIEW 或反之亦然时,您的系统最终看起来很傻,此外还有更多)。
  • 提示:将 .GroupBy(p =&gt; new {p.LicenseHolderID } ) 更改为 GroupBy( p =&gt; p.LicenseHolderID )。您不需要单成员匿名类,并且 (imo) 匿名类不应再使用,因为它们(通常,在大多数情况下)不如 ValueTuples。
  • 你有没有正确配置的DbContext可以用来直接查询Vehicles表等行程信息?您的 tblTripsPerMonth 表似乎是非规范化的(即设计不正确),因此您应该修复它...
  • 另一个提示:一般来说你不需要使用ToArray,而是使用ToList(因为它在所有情况下都更快),如果你打算使用ToArray或@987654361 @ 然后将结果分别存储到T[]List&lt;T&gt;,而不是IEnumerable&lt;T&gt;,因为当您使用IEnumerable&lt;T&gt; 时,CLR 无法对List&lt;T&gt;Array 的成员进行快速非虚拟调用(例如,Linq 的 .Count() 扩展总是比 List&lt;T&gt;.Count 慢,有时甚至需要 O(n) 才能运行,eww)。
  • @DStanley 它 is 较慢:在 .NET 6 之前(或者可能是 .NET 7,如果它再次被踢),Linq .Count() 扩展都不是内联的,它只是检查IList&lt;T&gt;.CountICollection&lt;T&gt;.Count 但不是IReadOnlyCollection&lt;T&gt;.Count(也不是IReadOnlyList&lt;T&gt;.Count),否则它将遍历整个集合以获取计数-这是因为在Linq 中检查IReadOnlyCollection 也是相对 昂贵(见此线程:github.com/dotnet/runtime/issues/42254)另见本文:blog.slaks.net/2015-01-12/…

标签: c# list foreach ienumerable pdfsharp


【解决方案1】:

我需要在VehicleId 中携带与不同车辆有关的其他列的行数据,以便我可以在我的 PDF 中填写详细信息,而不仅仅是汇总值。

如果我对您的理解正确,对于每个公司 (LicenseHolderId),您希望它们的相关 Trip 对象,但按 VehicleId 分组 - 这很简单,只需添加另一个 GroupBy - 您可以在一个内部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" );
        }
    }
}
  • 我添加了一些格式说明和字符,它们仅适用于文本模式(控制台应用程序),不适用于 PDF 渲染,但如果您好奇的话:

    • 我使用制表符 (\t) 来表示缩进,以便对相关数据进行可视化分组。
    • 我使用了格式说明符 :00 来确保 Month 值显示为带有前导零的 2 位值。
    • 我使用了格式说明符 ,11:C2 来确保 PaymentNet 值被格式化为具有 2 位小数的货币值,并且始终在左侧填充到至少 11 个字符的宽度。
  • 这会给你这样的输出(如下)。

  • 请注意,虽然在您的源数据中每个 VehicleId 只关联一个行程,但我上面的代码允许单个VehicleId 有多个行程相同,并且不同,LicenseHolderId 值,尽管下面的示例打印输出仅显示每辆车 1 次行程。

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

但请注意,上面的代码很糟糕,因为它跨越了关注点(它做了两件事:它遍历一个重要的对象图,并将数据呈现到您的 PDF 库中)。

更好的设计会分离图形遍历(可能是单个扩展方法),从而使 PDF 呈现代码更简单,但是我无法在不了解更多信息的情况下为您提供任何代码示例关于您的数据库设计以及您是否/如何使用 EF。

【讨论】:

  • 多么棒的答案 - 非常感谢您花时间写出如此深思熟虑的回复。一个打嗝!我在String vehicleId = groupedByVehicle.Key; 中的Key 下得到一个红色的波浪线——我玩过它,但想不出解决方法。我已将我的新代码添加到我原来的问题中,你能看一下吗?另外,出于好奇,您为什么使用String 而不是string
  • @OleM 我的错。将groupedByVehicle.Key 更改为vehicleGroup .Key
  • 太棒了,再次感谢您。接受你的回答。在我匆匆离开之前还有一件事;就像 - 例如 - gfx.DrawString(vehicleId /* more code */) 一样,它会将所有 vehicleIds 添加到彼此的顶部。知道如何将它们分开或以其他方式组织它们吗?
  • @OleM 我不熟悉 PDFSharp,所以我不能说 - 但我确实觉得你手动生成 PDF 很奇怪 - 大多数时候人们只是使用 Blink/ Chromium+Puppeteer 将 HTML+CSS 转换为 PDF(并不是我曾经提倡使用这种迂回(和占用内存)的方式,但是开发人员的生产力收益是 massive 因为你可以与手写 PDF 呈现代码所需的几天到几周相比,在几个小时内完成一个 HTML+CSS 模板。
  • 这似乎与DrawString的位置(X,Y)有关。我慢慢开始意识到这不是#1 做事的方式。但我太深了哈哈。下一次,我肯定会按照这些思路进行调查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-18
  • 1970-01-01
  • 1970-01-01
  • 2019-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多