【问题标题】:Joining 4 DataTables with Linq用 Linq 连接 4 个数据表
【发布时间】:2019-06-19 18:14:50
【问题描述】:

我有 4 个要加入的数据表,但不知道如何有效地加入。

我将前两个表加入,创建了 apptDetails 的第三个对象,它是 DataRows 的 IEnumerable。我无法将其恢复为 DataTable,因此我可以对其进行更多连接。我在apptDetails.CopyToDataTable() 上遇到错误:'IEnumerable' does not contain a definition for 'CopyToDataTable' and no accessible extension method 'CopyToDataTable' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)

DataTable customer = ETL.ParseTable("customer");
DataTable appointments = ETL.ParseTable("appointments");
IEnumerable apptDetails = from t1 in customer.AsEnumerable()
    join t2 in appointments.AsEnumerable() on Convert.ToInt32(t1["customerId"]) equals Convert.ToInt32(t2["customerId"])
    into tableGroup
     select new
       {
        customerId = t1["customerId"],
        TotalAppointments = tableGroup.Count(),
        appointment_missed = Convert.ToInt32(t1["MissedAppt"]),
        appointment_show_rate = (
                                    tableGroup.Count()>0 ? 
                                        Math.Round((1 - ((double)Convert.ToInt32(t1["MissedAppt"]) / (double)tableGroup.Count())),2)
                                        : 0
                                )

        };
DataTable dt = apptDetails.CopyToDataTable();

我最初只是使用var apptDetails,但看起来我需要更多的类型转换,所以我尝试了以下操作:

 IEnumerable<DataRow> apptDetails
 IEnumerable<EnumerableRowCollection> apptDetails
 as well as:
 DataTable dt = apptDetails.CopyToDataTable<DataRow>();
 DataTable dt = apptDetails.CopyToDataTable<EnumerableRowCollection>();

我需要加入客户表和约会表,然后将新列也添加到一个平面表中。我在做这件事时缺少什么,或者有更好的方法吗?

性能是一个因素,因为我们正在谈论 20,000 名客户和 80,000 次约会,而且在此之后还会有 2-3 个表加入,所以我想充分了解使用 Linq 执行此操作的“正确”方式。

【问题讨论】:

    标签: c# linq datatable


    【解决方案1】:

    您应该在separation of concern 上做更多工作:将数据(数据表)的内部存储方法与数据处理(使用 LINQ 语句组合数据表中的数据)分开。

    在您的情况下,考虑为DataTable 创建扩展函数:将DataTable 转换为IEnumerable&lt;Customer&gt;IEnumerable&lt;Appointment&gt; 的函数,以及转换IEnumerable&lt;Customer&gt; / IEnumerableback into aDataTable` 的函数。

    如果您这样做,将更容易识别模式和重用代码。此外,如果您更改数据存储,例如从 DataTable 更改为 CSV 文件、数据库或其他任何内容,您所要做的就是编写一个函数来使其成为 IEnumerable / IQueryable,并且您的 LINQ 查询仍然有效。

    Extension methods demystified

    static class DataTableExtensions
    {
         public static IEnumerable<Customer> ToCustomers(this DataTable table)
         {
              ... // TODO: implement
         }
         public static DataTable ToDataTable(this IEnumerable<Customer> customers)
         {
              ... // TODO implement
         }
    
         // similar functions for Appointments and AppointmentDetails:
         public static IEnumerable<Appointment> ToAppointments(this DataTable table) {...}
         public static DataTable ToDataTable(this IEnumerable<Appointment> appointments) {...}
         public static IEnumerable<AppointmentDetails> ToAppointmentDetails(this DataTable table) {...}
         public static DataTable ToDataTable(this IEnumerable<AppointmentDetail> appointmentDetails) {...}
    

    您比我更了解 DataTables,所以我将代码留给您。如需帮助,请参阅 Convert DataTable to IEnumerableConvert IEnumerable to DataTable

    我们需要为您的 LINQ 查询编写一个函数。您可以将其保留为一堆 LINQ 语句,但是,如果您为此编写一个函数,它会看起来更整洁、更易读、更易测试、更可重用(毕竟:您现在知道如何编写扩展函数:

    public static IEnumerable<AppointmentDetail> ToAppointmentDetails(
        this IEnumerable<Customer> customers,
        IEnumerable<Appointment> appointments)
    {
        return customers.GroupJoin(appointments,     // GroupJoin customer and appointments
            customer => customer.CustomerId,         // from every customer take the customerId,
            appointment => appointment.CustomerId,   // from every appointment take the CustomerId,
            // from every Customer with all his matching Appointments make one new AppointmentDetail 
            (customer, appointments => new AppointmentDetail 
            {
                CustomerId = customer.CustomerId,
                TotalAppointments = appointments.Count(),
                MissedAppointments = appointments
                     .Where(appointment => appointment.IsMissed)
                     .ToList(),
                ...
            });
    }
    

    现在把所有东西放在一起:

    用法:

    DataTable customerTable = ...
    DataTable appointmentTable = ...
    IEnumerable<Customer> customers = customerTable.ToCustomers();
    IEnumerable<Appointment> appointments = appoitnmentTable.ToAppointments();
    
    IEnumerable<AppointmentDetail> appointmentDetails = customers.ToAppointmentDetails(appointments);
    
    DataTable appointmentDetailTables = appointmentDetails.ToDataTable(appointmentDetails);
    

    现在看起来是不是更整洁了?

    请注意,只有最后一条语句实际上会进行任何枚举。所有早期的语句只创建一个 IEnumerable,没有进行枚举。这与连接 LINQ 语句非常相似。事实上,如果你真的想要,并且你可以说服你的项目负责人相信代码会更好地可读、可测试、可维护(我对此表示怀疑),你可以在一个语句中重写它,类似于连接 LINQ 语句。不要以为这会提高处理速度:

    DataTable appointmentDetailTable = customerTable.ToCustomers()
        .ToAppointmentDetails(appointmentTable.ToAppointments())
        .ToDataTable();
    

    因为您将关注点分开,所以这段代码的可重用性要高得多。微小的更改不会对您的代码产生太大影响如果您决定从数据库而不是从 DataTable 中获取客户和约会,您所要做的就是重写您的 ToCustomersToAppointments,所有其他功能将保持不变。

    【讨论】:

    • 谢谢 Harald,我明天会看看这个
    【解决方案2】:

    不知道为什么这在其他方法不起作用时有效,但使用这个:

    DataTable apptDetails = (from t1 in customer.AsEnumerable()
    join t2 in appointments.AsEnumerable() on Convert.ToInt32(t1["customerId"]) equals Convert.ToInt32(t2["customerId"])
    into tableGroup
     select new
       {
        customerId = t1["customerId"],
        TotalAppointments = tableGroup.Count(),
        appointment_missed = Convert.ToInt32(t1["MissedAppt"]),
        appointment_show_rate = (
                                    tableGroup.Count()>0 ? 
                                        Math.Round((1 - ((double)Convert.ToInt32(t1["MissedAppt"]) / (double)tableGroup.Count())),2)
                                        : 0
                                )
        }).CopyToDataTable();
    

    在实施此页面的信息后工作: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/implement-copytodatatable-where-type-not-a-datarow

    我还是做不到:

    DataTable dt = apptDetails.CopyToDataTable();
    

    但它的工作方式是相反的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-12
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多