比较数据表的有效方法

时间:2020-05-07 04:46:12

标签: c# performance linq

下面我用c#方法比较两个数据表并返回不匹配记录。

public DataTable GetTableDiff(DataTable dt1, DataTable dt2, string columnName)
{
    var StartTime = DateTime.Now;
    dt1.PrimaryKey = new DataColumn[] { dt1.Columns["N"] };
    dt2.PrimaryKey = new DataColumn[] { dt2.Columns["N"] };

    DataTable dtDifference = null;
    //Get the difference of two datatables
    var dr = from r in dt1.AsEnumerable()
             where !dt2.AsEnumerable().Any(r2 => r["N"].ToString().Trim().ToLower() == r2["N"].ToString().Trim().ToLower()
                 && r[columnName].ToString().Trim().ToLower() == r2[columnName].ToString().Trim().ToLower())
             select r;

    if (dr.Any())
    {
        dtDifference = dr.CopyToDataTable();
    }
    return dtDifference;
}

此代码有效,但是比较数据表中的10,000条记录需要花费1.24分钟。有什么方法可以使其更快?

N是主键,columnName是要比较的列。

谢谢。

2 个答案:

答案 0 :(得分:3)

首先,我想问一下您是否曾经在一个简单的for / foreach循环中尝试过此方法并比较了性能?

目前,您正在创建一个新的Enumerable,然后将其复制到数据表中。 如果使用for / foreach循环,则可以在同一迭代中进行比较和复制。

您还应该查看字符串比较。目前,您正在修整,然后转换为小写。由于字符串是不可变的,因此这将为每个字符串的每个操作分配新的内存。因此,在您的where语句中,您基本上每次重复执行此操作(最多)8次。

我还要问您是否真的需要Trim()?一个DT可能在字符串的前面有一个空格,而另一个没有吗?还是比较仍然是正确的?除非确实需要,否则不要修剪字符串。

然后,您应该使用不区分大小写的字符串比较,而不要转换ToLower。这样会更快。据MS称StringComparison.OrdinalIgnoreCase表现更好。

做这些,然后比较效果,看看有什么不同

另请参阅: https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings

更新

这引起了我的兴趣,所以我回去做了一些测试。 我在两个数据表中生成了10,000行随机(ish)数据,每两个数据表都会匹配,并执行您的比较与简化的循环比较(使用String比较):

  for (int i = 0; i < dt1.Rows.Count; i++)
  {
      if (dt1.Rows[i]["N"].ToString().Equals(dt2.Rows[i]["N"].ToString(), StringComparison.OrdinalIgnoreCase)
          && dt1.Rows[i][columnName].ToString().Equals(dt2.Rows[i][columnName].ToString(), StringComparison.OrdinalIgnoreCase))
      {
          dtDifference.Rows.Add(dt1.Rows[i].ItemArray);
      }
  }

您的代码= 66,000ms-> 75,000ms

对于循环代码= 12毫秒-> 20毫秒

差异很大!

然后,我使用for循环方法进行了比较,但对字符串使用了两种不同的字符串比较类型。使用我的字符串比较,对比您的。但是我为此必须测试一百万行,以取得显着差异。

此差异介于200毫秒至800毫秒之间

因此在这种情况下,字符串比较似乎不是主要因素。

因此,看来创建数据行的Linq查询占用了大多数时间,而不是比较行本身。

因此,切换到使用for循环,一切都会再次好起来!

答案 1 :(得分:1)

如果修剪了string中的DataTable值,则可以使用DataRowCollection.Find方法基于主键结合DataTable.CaseSensitive属性进行快速查找。但是由于并非如此,因此您不能使用ADO.NET的内置功能。幸运的是,使用ToLookup LINQ方法可以轻松完成同一件事,该方法返回一个只读的一对多词典。

public DataTable GetTableDiff(DataTable dt1, DataTable dt2, string columnName)
{
    IEqualityComparer<string> comparer = StringComparer.OrdinalIgnoreCase;

    var lookup = dt2.AsEnumerable().ToLookup(row => row["N"].ToString().Trim(),
        comparer);

    var diffList = dt1.AsEnumerable()
        .Where(r1 => !lookup[r1["N"].ToString().Trim()].Any(r2 => comparer.Equals(
            r1[columnName].ToString().Trim(), r2[columnName].ToString().Trim())))
        .ToList();

    if (diffList.Count == 0) return null;
    return diffList.CopyToDataTable();
}
相关问题