【问题标题】:Better performance on updating objects with linq使用 linq 更新对象的性能更好
【发布时间】:2010-09-21 06:14:04
【问题描述】:

我有两个自定义对象列表,如果另一个列表中有与另一对字段匹配的对象,我想更新一个列表中所有对象的字段。

这段代码更好地解释了问题并产生了我想要的结果。然而,对于较大的 20k 列表和具有匹配对象的 20k 列表,这需要相当长的时间(31 秒)。通过使用通用列表 Find(Predicate) 方法,我可以将这一点提高约 50%。

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
namespace ExperimentFW3
{
    public class PropValue
    {
        public string Name;
        public decimal Val;
        public decimal Total;
    }
    public class Adjustment
    {
        public string PropName;
        public decimal AdjVal;
    }
    class Program
    {
        static List<PropValue> propList;
        static List<Adjustment> adjList;

        public static void Main()
        {
            propList = new List<PropValue>{
                new PropValue{Name = "Alfa", Val=2.1M},
                new PropValue{Name = "Beta", Val=1.0M},
                new PropValue{Name = "Gamma", Val=8.0M}
            };
            adjList = new List<Adjustment>{
                new Adjustment{PropName = "Alfa", AdjVal=-0.1M},
                new Adjustment{PropName = "Beta", AdjVal=3M}
            };

            foreach (var p in propList)
            {
                Adjustment a = adjList.SingleOrDefault(
                    av => av.PropName.Equals(p.Name)
                    );
                if (a != null)
                    p.Total = p.Val + a.AdjVal;
                else
                    p.Total = p.Val;
            }
        }
    }
}

期望的结果是:Alfa total=2,Beta total=4,Gamma total=8

但我想知道这是否可以做得更快。即使在结果集中循环超过 20k 项时,内部加入这两个列表也需要很少的时间。

var joined = from p in propList
             join a in adjList on p.Name equals a.PropName
             select new { p.Name, p.Val, p.Total, a.AdjVal };

所以我的问题是,是否可以像使用 T-SQL 那样做一些事情?在调整值上使用 ISNULL(val,0) 从左连接进行 UPDATE。

【问题讨论】:

    标签: c# linq performance optimization linq-to-objects


    【解决方案1】:

    该连接应该相当快,因为​​它将首先循环遍历所有adjList 以创建查找,然后对于propList 中的每个元素,它将只使用查找。这比您在较大代码中的 O(N * M) 方法更快 - 尽管可以通过在循环之前在 adjList 上调用 ToLookup(或 ToDictionary,因为您只需要一个值)来轻松修复。

    编辑:这是使用ToDictionary 修改后的代码。未经测试,请注意...

    var adjDictionary = adjList.ToDictionary(av => av.PropName);
    foreach (var p in propList)
    {
        Adjustment a;
        if (adjDictionary.TryGetValue(p.Name, out a))
        {
            p.Total = p.Val + a.AdjVal;
        }
        else
        {
            p.Total = p.Val;
        }
    }
    

    【讨论】:

    • 感谢您的快速回复!我最近开始研究 linq 并试图在代码中努力模拟 t-sql。您未经测试的代码以毫秒为单位测量,因此我得到了我所要求的性能提升。
    • 很高兴它有帮助......虽然我不确定为什么这个问答现在是一个社区维基:(
    • 发帖时有一个“社区维基”复选框。有时即使您从未单击它也会启用它。
    • 奇数。我知道还有其他触发器可以自动将某些内容制作为 wiki,但这听起来像是一个基本错误:(
    【解决方案2】:

    如果 adjList 可能有重复的名称,您应该在推送到字典之前对项目进行分组。

    Dictionary<string, decimal> adjDictionary = adjList
      .GroupBy(a => a.PropName)
      .ToDictionary(g => g.Key, g => g.Sum(a => a.AdjVal))
    
    propList.ForEach(p => 
      {
        decimal a;
        adjDictionary.TryGetValue(p.Name, out a);
        p.Total = p.Val + a;
      });
    

    【讨论】:

      【解决方案3】:

      我知道我发布此内容迟了,但我认为有人会喜欢下面更清晰的简短答案,该答案在 adjList 中处理每次查找的多个记录。创建 LookUp 将允许对多个项目进行快速查找,如果 LookUp 中没有记录,则会返回一个空列表。

      var adjLookUp = adjList.ToLookUp(a => a.PropName);
      foreach (var p in propList) 
          p.Total = p.Val + adjLookUp[p.Name].Sum(a => a.AdjVal);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-18
        • 2014-01-27
        • 1970-01-01
        • 2010-11-01
        • 1970-01-01
        相关资源
        最近更新 更多