【问题标题】:Order By on a modified Entity Framework object对修改后的实体框架对象排序
【发布时间】:2010-01-25 20:59:16
【问题描述】:

我从名为 oProducts 的 Products 表中获取了一个实体框架对象集合。我正在遍历集合并从单独的函数中设置 Quantity 字段。在我用 HTML 写出对象的显示之前,我想按数量对它们进行排序。因此,我正在使用 LINQ 创建一个新集合并尝试从我刚刚修改的对象集合中进行排序。修改后的值肯定在对象集合中并正确输出,但排序顺序不适用于修改后的字段。有谁知道我忽略了什么或更好的尝试方法?

Dim oProducts = From p in ctx.Products _
                Where p.Active = True _
                Select p

For Each prod in oProducts
    prod.Quantity = GetQuantity(prod.ProductID)
Next

Dim oOrderedProducts = From p in oProducts _
                       Order By p.Quantity Ascending _
                       Select p

For Each prod in oOrderedProducts
    Response.Write(prod.Quantity.ToString())
    '*** The Quantities are stored in the collection properly but it doesn't order by the Quantity field.
Next

【问题讨论】:

    标签: vb.net linq entity-framework sql-order-by


    【解决方案1】:

    您需要记住几件事:

    1. 执行From p in ctx.Products _总是从数据库中选择。
    2. LINQ 是懒惰的。在您迭代之前,它不会执行数据库选择,如果您迭代两次,它将执行两次数据库选择。
    3. 从数据库中读取的数据与ObjectContext 中已有数据的组合方式将根据您执行的查询的MergeOption 而有所不同。但它总是会发生在 SQL 执行之后。

    考虑到这些想法,让我们考虑一下您的代码:

    此位 (1) 创建一个IQueryable<Product>,可用作迭代或定义其他查询的基础。请注意,它实际上并不执行数据库查询。

    Dim oProducts = From p in ctx.Products _  // (1)
                    Where p.Active = True _
                    Select p
    

    下一位 (2) 迭代上面定义的 IQueryable。

    For Each prod in oProducts                          // (2)
        prod.Quantity = GetQuantity(prod.ProductID)
    Next
    

    这样做会导致从数据库中进行选择。在循环内部,你改变了返回的对象。在 LINQ to Objects 中,这不会产生任何影响,因为下次您迭代 IQueryable 时,它​​将重新执行。但是,Entity 对象是特殊的。改变它们标志着实体在上下文中发生了变化。稍后调用SaveChanges() 会将这些修改保存到数据库中。

    但是,重要的是要注意,您并没有通过这样做更改IQueryable 变量oProducts 根本。它仍然是对每次迭代时都会执行的查询的引用。 这不是一个列表。如果你想要一个列表,你需要打电话给ToList()ToArray()

    以下查询 (3) 基于原始 oProducts 创建一个新的 IQueryable。迭代时,这将生成新的 SQL,它的作用与原始查询大致相同,除了您添加的排序。同样,oProducts 是一个查询,而不是一个列表。

    Dim oOrderedProducts = From p in oProducts _                 // 3
                           Order By p.Quantity Ascending _
                           Select p
    

    最后,您的代码会根据您在上面定义的 IQueryable 执行一个新的数据库查询。当它执行这个查询时,它将从数据库中检索到的信息与您在上面从对象上下文中所做的突变的知识合并。因为排序基于您定义的查询,所以它使用数据库中的信息在数据库服务器上执行。由于来自数据库的信息与来自对象上下文的信息合并,您仍然可以在 (2) 中看到您对上述数据执行的突变。

    For Each prod in oOrderedProducts                        
        Response.Write(prod.Quantity.ToString())
        '*** The Quantities are stored in the collection properly but it doesn't order by the Quantity field.
    Next
    

    【讨论】:

      【解决方案2】:

      您不需要使用 3 个连续的 LINQ 表达式,将它们组合成一个,使用投影,可以是匿名类型(就像我在这里所做的那样)或真实的类型。 这在下面应该可以工作

      var oProducts = From p in ctx.Products
                      Where p.Active == True
                      orderby p.Quantity Ascending
                      select new 
                      {
                          Quantity = GetQuantity(p.ProductID)
                          // ... assign other properties too
                      }
      
      foreach ( var prod in oProducts )
      {
        // do your output
      }
      

      【讨论】:

      • 您在这里遇到的问题和 Jay 一样。 LINQ to Entities 不知道如何将GetQuantity 转换为 SQL,因此会产生运行时错误。
      • 啊,你是对的 - 我的错误,是的,你需要重新考虑如何编写 LINQ where/select 以便它可以成功访问数据库,我忽略了 orderby 相同的事实我试图在投影中设置的变量
      【解决方案3】:

      这个怎么样?

      Dim oProducts = From p in ctx.Products _
                      Where p.Active = True _
                      Select p
      
      For Each prod in oProducts
          prod.Quantity = GetQuantity(prod.ProductID)
      Next
      
      Dim oOrderedProducts = oProducts.OrderBy(Function(p) p.Quantity)
      

      【讨论】:

      • 此代码会产生运行时错误,因为 LINQ to Entities 将无法转换 GetQuantity。一种解决方法是先致电AsEnumerable()
      • 呃,是的,L2E注册失败;正在考虑 Linq To Sql。现在,如果在创建 oProducts 时调用 ToList() 或 ToArray 会发生什么?
      • 然后就可以了。 为什么它会起作用,但是,它比转换为 SQL 更复杂;我在回答中解释了这一点。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-09
      • 1970-01-01
      • 1970-01-01
      • 2011-11-26
      • 2011-03-04
      • 1970-01-01
      相关资源
      最近更新 更多