【问题标题】:Updating an item property within IEnumerable but the property doesn't stay set?更新 IEnumerable 中的项目属性但该属性未保持设置?
【发布时间】:2012-02-24 15:02:11
【问题描述】:

我有两个表:Transactions 和 TransactionAgents。 TransactionAgents 有一个 Transactions 的外键,称为 TransactionID。很标准。

我也有这个代码:

BrokerManagerDataContext db = new BrokerManagerDataContext();

var transactions = from t in db.Transactions
                   where t.SellingPrice != 0 
                   select t;

var taAgents = from ta in db.TransactionAgents
               select ta;

foreach (var transaction in transactions)
{
    foreach(var agent in taAgents)
    {
        agent.AgentCommission = ((transaction.CommissionPercent / 100) * (agent.CommissionPercent / 100) * transaction.SellingPrice) - agent.BrokerageSplit;
    } 
}

dataGridView1.DataSource = taAgents;

基本上,TransactionAgent 有一个名为 AgentCommission 的属性/列,对于我的数据库中的所有 TransactionAgent,它都是 null。

我的目标是执行您在 foreach(var agent in taAgents) 中看到的数学运算,以修补每个代理的值,使其不为空。

奇怪的是,当我在 agent.AgentCommission = (formula) 上运行此代码和断点时,它显示正在为 AgentCommissions 计算值并且正在更新对象,但在它显示在我的数据网格中(仅用于测试)之后,它没有显示它计算的值。

所以,对我来说,该属性似乎没有被永久设置在对象上。更重要的是,如果我将这个新更新的对象通过更新持久化回数据库,我怀疑计算出的 AgentCommission 会在那里设置。

如果没有以相同的方式设置我的表,是否有人可以查看代码并了解我为什么不保留属性的价值?

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    IEnumerable<T>s 不保证更新的值将在枚举中持续存在。例如,List 将在每次迭代中返回相同的对象集,因此如果您更新属性,它将在迭代中保存。但是,IEnumerables 的许多其他实现每次都返回一组新对象,因此所做的任何更改都不会保留。

    如果您需要存储和更新结果,请使用 .ToList()IEnumerable<T> 下拉到 List<T> 或使用 .Select() 将其投影到新的 IEnumerable<T> 并应用更改。

    要将其专门应用于您的代码,它看起来像这样:

    var transactions = (from t in db.Transactions
                        where t.SellingPrice != 0 
                        select t).ToList();
    
    var taAgents = (from ta in db.TransactionAgents
                    select ta).ToList();
    
    foreach (var transaction in transactions)
    {
        foreach(var agent in taAgents)
        {
            agent.AgentCommission = ((transaction.CommissionPercent / 100) * (agent.CommissionPercent / 100) * transaction.SellingPrice) - agent.BrokerageSplit;
        } 
    }
    
    dataGridView1.DataSource = taAgents;
    
    

    【讨论】:

    • 哦。很高兴知道。那么,在我遍历它们之前,我需要 .ToList() 我的可枚举对象?
    • 是的,这样这些值将本地存储在 List 中,而不是临时存储在废弃的对象中。
    • -1 这个答案简直是错误。您绝对可以更改通过IEnumerable 访问的对象属性。 List 本身实现了IEnumerable....
    • 再看一遍,你是对的 - 我想更正确的描述是 IEnumerable保证更新其值将在枚举中持续存在。
    • 我确实为此发疯了 :) 非常感谢。
    【解决方案2】:

    具体来说,问题在于每次访问 IEnumerable 时,它​​都会枚举集合。在这种情况下,集合是对数据库的调用。在第一部分中,您从数据库中获取值并更新它们。在第二部分中,您将再次从数据库中获取值并将其设置为数据源(或者,您将枚举器设置为数据源,然后从数据库中获取值)。

    使用 .ToList() 或类似方法将结果保存在内存中,并且每次都访问同一个集合。

    【讨论】:

    • 哇。所以这个问题比 eow0o83hf 描述的要严重得多。我知道它有点“只读”,但可枚举本质上是在读取集合时清除值。
    • 与其说是清除了值,不如说是从不同的集合中读取。 (简单来说(但在技术上并不正确),它会在您每次枚举时创建一个新的“集合”)
    【解决方案3】:

    假设您使用的是 LINQ to SQL,如果 EnableObjectTracking 为 false,则每次运行查询时都会构造新对象。否则,您每次都将获得相同的对象实例,并且您的更改将继续存在。然而,就像其他人展示的那样,不是让查询执行多次,而是将结果缓存在一个列表中。您不仅会得到想要的工作,而且会减少数据库往返次数。

    【讨论】:

      【解决方案4】:

      我发现我必须在列表中找到我想要修改的项目,提取副本,修改副本(通过增加其计数属性),从列表中删除原件并添加修改后的副本。 var x = stats.Where(d => d.word == s).FirstOrDefault(); var statCount = stats.IndexOf(x); x.count++; stats.RemoveAt(statCount); stats.Add(x);

      【讨论】:

      • 这是非常低效和糟糕的编码实践。你真的不应该为此使用 LINQ,尤其是列表。只需使用普通运算符[]。
      【解决方案5】:

      使用 lambda 重写您的 LINQ 表达式很有帮助,这样我们就可以更明确地考虑代码。

      //Original code from question
      var taAgents = from ta in db.TransactionAgents
                     select ta;
      
      //Rewritten to explicitly call attention to what Select() is actually doing
      var taAgents = db.TransactionAgents.Select(ta => new TransactionAgents(/*database row's data*/)});
      

      在重写的代码中,我们可以清楚地看到Select() 正在构造一个基于从数据库返回的每一行的新对象。此外,这种对象构造每次都会发生IEnumerable taAgents 被迭代。

      所以,更具体的解释一下,如果数据库中有5行TransactionAgents,在下面的例子中,TransactionAgents()构造函数一共被调用了10次。

      // Assume there are 5 rows in the TransactionAgents table
      var taAgents = from ta in db.TransactionAgents
                     select ta;
      
      //foreach will iterate through the IEnumerable, thus calling the TransactionAgents() constructor 5 times
      foreach(var ta in taAgents)
      {
        Console.WriteLine($"first iteration through taAgents - element {ta}");
      }
      // these first 5 TransactionAgents objects are now out of scope and are destroyed by the GC
      
      
      //foreach will iterate through the IEnumerable, thus calling the TransactionAgents() constructor 5 MORE times
      foreach(var ta in taAgents)
      {
        Console.WriteLine($"second iteration through taAgents - element {ta}");
      }
      
      // these second 5 TransactionAgents objects are now out of scope and are destroyed by the GC
      

      我们可以看到,我们的所有 10 个 TransactionAgents 对象都是由我们的 Select() 方法中的 lambda创建的,而 不是存在于foreach 语句的范围之外。

      【讨论】:

      • 不完全是。等效的方法语法是db.TransactionAgents.Select(ta=> ta)
      猜你喜欢
      • 2014-01-20
      • 1970-01-01
      • 2017-10-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多