LINQ 两个数据表加入

LINQ two datatables join

我被这个问题困住了。 我可以使用 foreach 解决这个问题,但必须有更好的解决方案。

我有两个数据table。 首先有一个名为 "Por" 的列 第二个有两列名为 "FirstPor" 和 "LastPor"。 我的目标是根据这样的条件使用 LINQ 创建新数据table。

foreach ( DataRow row in FirstDatatable.Rows )
{
    foreach ( DataRow secondRow in SecondDatatable.Rows )
    {
       if ( row["Por"] >= secondRow["FirstPor"] && row["Por"] <= secondRow["LastPor"] )
            FinalDataTable.Rows.Add(row);
    }
}

我是 LINQ 的新手,这对我来说可能是个问题。我可以通过 Parallel.Foreach 做到这一点,但我认为 LINQ 可能会快得多。条件很简单。从第一个 table ("Por" 列)获取每个数字并检查它属于第二个 table ( "Por" >= "FirstPor" && "Por" <= "LastPor" )。我认为对于每天都在做这件事的人来说这很简单。 是的,还有一个任务。列是STRING类型,所以需要在LINQ语句中进行转换。


是的,我刚刚将我的并行代码修改为混合 LINQ/Parallel,看来我已经完成了。我使用了 James 和 Rahul 写的东西并将其放入我的代码中。现在,该过程需要 52 秒来估计 421 000 行 :) 好多了。

public class Range
{
        public int FirstPor { get; set; }
        public int LastPor { get; set; }
        public int PackageId { get; set; }
}

var ranges = (from r in tmpDataTable.AsEnumerable()
          select new Range
                          {
                              FirstPor = Int32.Parse(r["FirstPor"] as string),
                              LastPor = Int32.Parse(r["LastPor"] as string),
                              PackageId = Int32.Parse(r["PackageId"] as string)
                          }).ToList();

Parallel.ForEach<DataRow>(dt.AsEnumerable(), row => 
{

int por = Int32.Parse(row["Por"].ToString());

                lock (locker)
                {
                    row["PackageId"] = ranges.First(range => por >= range.FirstPor && por <= range.LastPor).PackageId;
                }

                worker.ReportProgress(por);

            });

(前方未经测试的代码......)

var newRows = FirstDatatable.Rows
             .Where(row =>SecondDatatable.Rows
                 .Any(secondRow => row["Por"] >= secondRow["FirstPor"] 
                                && row["Por"] <= secondRow["LastPor"]);

FinalDataTable.Rows.AddRange(newRows);

但是,如果您真正关心速度,我的第一个建议是转储数据表,并使用列表。我猜想 SecondDatatable 基本上是固定的,并且可能每天更改不到一次。因此,less 为此创建一个漂亮的内存结构:

class Range
{
     public int FirstPor {get; set;}
     public int LastPor {get; set;}
 }

 var ranges = (from r in SecondDatatable.Rows
               select new Range
                 { 
                     FirstPor = Int32.Parse(r["FirstPor"]),
                     LastPor = Int32.Parse(r["LastPor"])
                  }).ToList();

那么我们的代码就变成了:

var newRows = FirstDatatable.Rows
         .Where(row =>ranges
             .Any(range => row["Por"] >= range.FirstPor
                            && row["Por"] <= range.LastPor).ToList();

这本身应该会大大加快速度。

现在,如果成功,它将扫描范围,直到找到匹配的范围。如果失败,它必须在放弃之前扫描整个列表。因此,我们需要做的第一件事是加快速度,对范围列表进行排序。然后我们只需要扫描到范围的低端高于我们正在寻找的值的点。这应该将任何范围外的那些行的处理时间缩短一半。

试试这个:-

DataTable FinalDataTable = (from x in dt1.AsEnumerable()
                           from y in dt2.AsEnumerable()
                           where x.Field<int>("Por") >= y.Field<int>("FirstPor")
                                && x.Field<int>("Por") <= y.Field<int>("LastPor")
                           select x).CopyToDataTable();

这是完整的 Working Fiddle,(我已经用您现有的代码和我的 LINQ 代码测试了一些示例数据)您可以在编辑器中复制粘贴相同的代码并进行测试,因为 DotNet Fiddle不支持 AsEnumerable。