首页 > 解决方案 > 用 Linq 连接 4 个数据表

问题描述

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

我得到了前两个表来加入,创建了第三个对象,apptDetails它是IEnumerableDataRows 的一个。我无法将其恢复到 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#linqdatatable

解决方案


您应该更多地关注关注点分离:将数据(DataTables)的内部存储方法与数据处理分开(使用 LINQ 语句组合数据表中的数据)。

在您的情况下,请考虑为以下内容创建扩展函数DataTable:将 a 转换DataTableIEnumerable<Customer>andIEnumerable<Appointment>的函数,以及将IEnumerable<Customer>/ IEnumerable back into aDataTable` 转换为的函数。

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

请参阅揭秘的扩展方法

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,所以我将代码留给您。如需帮助,请参阅将 DataTable 转换为 IEnumerable将 IEnumerable 转换为 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 中获取您的客户和约会,您所要做的就是重写您的ToCustomersand ToAppointments,所有其他功能将保持不变。


推荐阅读