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】:下面的查询将为您提供Users
、Products
和Count
的列表:
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 完成大部分工作。
还请注意,这不会提取任何额外数据 - (Users
或 Products
计数为 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 - 具有父表关系的双重分组的主要内容,如果未能解决你的问题,请参考以下文章