Linq to Sql - 具有父表关系的双重分组

Posted

技术标签:

【中文标题】Linq to Sql - 具有父表关系的双重分组【英文标题】:Linq to Sql - Double Group By with Parent Table Relation 【发布时间】:2009-09-30 14:01:26 【问题描述】:

我在获取 linq to sql 查询时遇到问题,想知道是否有人可以提供帮助。我有这些表:“订单”、“订单产品”、“产品”和“用户”。我想详细了解每个“用户”订购的产品数量(按 ProductID 包含在“OrderProducts”中)。用户的用户 ID 包含在“订单”表中,“订单产品”订单 ID 与“订单”中的订单 ID 相关。最终结果应该是这样的:

用户 1 用户 2 用户 3 产品1 100 75 17 产品2 24 78 18 产品3 82 48 35

因此,它同时按 UserID 和 ProductID 分组,并按该 userID 取所有分组 ProductID 的数量之和。我希望这是有道理的。非常感谢任何帮助,谢谢!

这是一个简化的数据库细分: 产品 - 产品ID - 产品名称

订单 - 订单号 - 用户 ID

订购产品 - 订单号 - 产品ID - 产品数量

用户 - 用户 ID - 用户名

【问题讨论】:

【参考方案1】:

下面的查询将为您提供UsersProductsCount 的列表:

from op in OrderProducts
group op by new op.Order.User, op.Product into g
select new

  g.Key.User.UserName,
  g.Key.Product.ProductName,
  Count=g.Sum(op => op.ProductQty)

它不会以您想要的确切格式为您提供结果,但它可以让您只使用 Linq 完成大部分工作。

还请注意,这不会提取任何额外数据 - (UsersProducts 计数为 0)。这可能是也可能不是您想要的。

[编辑] 这是另一个查询(实际上是两个):

from p in Products
from u in Users
let i = (from op in OrderProducts
         where op.Order.User == u && op.Product == p
         select op.ProductQty)
let quant = i.Count() == 0 ? 0 : i.Sum(v=>v)
select new p.ProductName, u.UserName, Quantity = quant
//group new u.UserName, Quantity = quant by p into g
//select new g.Key.ProductName, Users = g.AsEnumerable()

按原样,这应该会给您直接的结果(产品名称、用户名称、数量表)。注释掉的部分将改为按产品分组,这可能是您想要的(取决于您呈现结果的方式)。

【讨论】:

嗯,这看起来很有希望,但我还没有机会搞砸它。我将如何修改它以返回零? (即用户已订购此产品 0 次)。最后,我将指定要在结果中包含哪些产品和用户,因此我必须将它们设为 0。到目前为止,谢谢! 另外,如果有人有清理我的查询的提示,请告诉我! 嘿,谢谢伙计!我一直在做另一个项目,然后又回到了这个。不过,我必须在 VB.NET 中完成这个项目,而将您的 linq 语句转换为 vb.net 时只有一个问题。我不知道 i.Sum(v=>v) 会转换成什么。到目前为止,我已经得到了这个:From p In Products _ From u In Users _ Let i = (From op In OrderProducts _ Where op.Order.User.UserID = u.UserID And op.Product.ProductID = p.ProductID _选择 op.ProductQty) _ Let Qty = _ If(i.Count() = 0, 0, i.Sum()) _ Select New With p.ProductName, u.Username, Qty 非常感谢 Ryan!跨度> 使用i.Sum(Function(v) v)) 而不仅仅是i.Sum()。确保您也考虑使用组(注释掉的部分),以防它更符合您的需要。 太棒了!非常感谢,我想你现在已经在这里回答了我的一些问题,我应该雇用你,哈哈!谢谢一百万!【参考方案2】:

如果您在数据库中(因此在 DBML 中)设置了适当的关系,您可能需要考虑这样的事情(未测试):

<table>
<tr>
  <asp:Repeater ID="_users" runat="server">
    <ItemTemplate>
      <td><%# Eval("UserName") %></td>
    </ItemTemplate>
  </asp:Repeater>
</tr>
<asp:Repeater ID="_products" runat="server">
  <ItemTemplate>
    <tr>
      <td><%# Eval("Product.ProductName") %></td>
      <asp:Repeater ID="_users" runat="server" DataSource='<%# GetUsersProducts(Eval("ProductID")) %>'>
        <ItemTemplate>
          <td><%# Eval("count") %></td>
        </ItemTemplate>
      </asp:Repeater>
   </ItemTemplate>
</asp:Repeater>
</table>

代码隐藏:

protected void Page_Load(object sender, EventArgs e)

   _users.DataSource = context.Users.OrderBy(u => u.UserName);
   _users.DataBind();

   _products.DataSource = context.Products;
   _products.DataBind();

protected object GetUsersProducts(string ProductID)

  var prodord = from op in context.OrderProducts where op.ProductID == ProductID select op;
  return from u in context.Users.OrderBy(u2 => u2.UserName) select new 
    count = prodord.Where(op => op.Order.User.UserID == u.UserID).Sum(op => op.ProductQty)
  ;

您显然希望更改您的标记和一些对象类型,但我希望这能让您继续前进。我尽量使用自动生成的类对象结构来避免创建不可读的 LINQ 查询。

【讨论】:

这个问题似乎是它将为每个用户的每个产品运行单独的查询。如果有 2500 种产品和 500 名用户,那么数据库的访问次数为 125 万次……您将等待一天的结果。【参考方案3】:

您要求的结果形状称为 pivot。它具有数据驱动的列,而不是在查询运行之前声明的列。

这里的技巧是将结果投影到“属性”可以在运行时变化的类型中。我做了类似的事情here

以下是针对您的特定问题的一个小技巧:

// grab relationally-shaped results from database
var queryResult = 
(
  from u in myDataContext.Users
  from o in u.Orders
  from op in o.OrderProducts
  select new
  
    UserName = u.UserName,
    ProductName = op.ProductName,
    Quantity = op.Quantity
  
).ToList()

// now use a LinqToObjects query to pivot
List<XElement> pivotedResults =
(
  from x in queryResult
  group by x.ProductName into g
  select new XElement(g.Key,
    from y in g
    group by y.UserName into subg
    select new XAttribute(subg.Key, subg.Sum(z => z.Quantity))
).ToList();

【讨论】:

以上是关于Linq to Sql - 具有父表关系的双重分组的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Linq to SQL 中插入具有一对一关系的子对象

SQL:索引/分组具有双重清除条件的事件

LINQ to SQL 关系不更新集合

Linq to SQL:如何在没有分组的情况下进行聚合?

Linq to SQL - 分组和计数

Linq to Entity 具有多个左外连接