【问题标题】:How to optimise linq manipulation to get rows如何优化 linq 操作以获取行
【发布时间】:2016-03-29 06:41:54
【问题描述】:

我有序列号列表,并希望从数据表中提取与这些数字相对应的行并针对 ID。我正在使用以下 LINQ 查询:

//list of serial numbers
var serialNumAlreadyExisted = [1,2,3];
var varID = 2;

//get the corresponding rows for these serial numbers
var duplicateRows = (from row in dt.AsEnumerable()
    where row.Field<int>("ID") == varID && 
    serialNumAlreadyExisted.Any(sr => sr == row.Field<string>("SERIAL_NUMBER"))
    select row).ToList();

上述代码适用于 1-2K 行,但如果数据表中有 50K 序列号和 50K 条记录,则需要大量时间。

有什么办法可以优化它并减少处理时间?

【问题讨论】:

  • serialNumAlreadyExisted.Contains(row.Field&lt;string&gt;("SERIAL_NUMBER")) 怎么样?
  • 你可以join 这两个,这可能是我会做的,但我怀疑性能改进在内存集上会那么明显。事实上,我很惊讶您注意到内存中的所有内容都存在差异。我记得DataTables 是急切加载的吗?这些天有他们的懒惰版本吗?如果加载数据需要更长的时间,我肯定会相信,但我假设这已被排除?
  • 尝试使用DataTable.Select 方法。例如(测试性能的硬编码示例):DataRow[] dupes = dt.Select(String.Format("Id = 2 AND SERIAL_NUMBER IN ('1','2','3')"));

标签: c# performance linq datatable


【解决方案1】:

我不确定您是否打算将序列号作为intstring 的列表,但从字面上看,一次性转换为string 可能有助于提高性能,像这样:

var serialStringsAlreadyExisted = serialNumAlreadyExisted.Select(x => x.ToString()).ToList();

然后就可以进行join了,应该比Contains或者Any效率更高:

var duplicateRows = (
    from row in dt.AsEnumerable()
    where row.Field<int>("ID") == varID
    join serial in serialStringsAlreadyExisted on row.Field<string>("SERIAL_NUMBER") equals serial
    select row)
    .ToList();

编辑

刚刚进行了快速速度测试。与一百万行的原始代码相比,使用join 所用时间大约减少了一半。

如果将 serialNumAlreadyExisted 中的项目数增加到 20,则使用 join 所需的时间接近基线方法的 20%。

【讨论】:

    【解决方案2】:

    随着行数的增加,您会发现它的性能会变慢,这是因为当前正在执行线性搜索,因为它尝试使用以下代码查找数字:

    serialNumAlreadyExisted.Any(sr => sr == row.Field<string>("SERIAL_NUMBER"))
    

    当前复杂度为O(n^2), to make it O(n),您是否可以根据var serialNumAlreadyExisted = [1,2,3]; 创建字典

    假设下面是代码,你可以做得更好,尤其是字典值

    var testDictionary = serialNumAlreadyExisted.ToDictionary(x=>x,x=>x);
    

    最终代码会变成如下:

    var duplicateRows = 
        (from row in dt.AsEnumerable()
        where row.Field<int>("ID") == varID && 
              testDictionary.ConatinsKey(row.Field<string>("SERIAL_NUMBER"))
       select row).ToList();
    

    【讨论】:

      【解决方案3】:

      我建议将HashSetContains 调用组合起来,这样可以提供O(1) 访问权限。 Any 在这种情况下会通过集合。在最坏的情况下,它需要 3 次比较。

      HashSet<int> serialNumAlreadyExisted = new HashSet<int>();
      
      serialNumAlreadyExisted.Add(1);
      serialNumAlreadyExisted.Add(2);
      serialNumAlreadyExisted.Add(3);
      
      var duplicateRows = 
          (from row in dt.AsEnumerable()
          where row.Field<int>("ID") == varID && 
                serialNumAlreadyExisted.Contains(row.Field<string>("SERIAL_NUMBER"))
         select row).ToList();
      

      【讨论】:

        【解决方案4】:

        首先,您是针对数据表的 linqing,这意味着您的记录都在内存中。因此,如果您将逻辑移动到存储过程并将serialNumAlreadyExisted 与目标表连接起来,您的性能将会有很大的提高。但是,当您使用datatabale 标签时,似乎这不是一个选项。

        其次,serialNumAlreadyExisted 是一个 int 数组,那么如何通过string 比较呢?

        但是,在您给定的简短 sn-p 中,没有太多选择。使用AnyContains 或加入内存数据,并没有太大的不同。将HashSet 用于serialNumAlreadyExisted 会有所帮助。但是,再说一遍,将连接移动到存储过程中,您会看到不同之处。

        【讨论】:

          猜你喜欢
          • 2013-12-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多