在LINQ to实体查询中选择具有记录的聚合(分组)统计信息
我有一个查询,我正试图从SQL(T-SQL)移植到LINQ,再移植到Entities 4.0(C#)。结果集包含标准“详细信息行”和聚合“统计”信息的组合 原始SQL使用标准的select left连接到聚合信息,类似于:在LINQ to实体查询中选择具有记录的聚合(分组)统计信息,linq,c#-4.0,linq-to-entities,group-by,outer-join,Linq,C# 4.0,Linq To Entities,Group By,Outer Join,我有一个查询,我正试图从SQL(T-SQL)移植到LINQ,再移植到Entities 4.0(C#)。结果集包含标准“详细信息行”和聚合“统计”信息的组合 原始SQL使用标准的select left连接到聚合信息,类似于: SELECT UserId, Name, Email, ISNULL(Stats.TotalPosts, 0) as TotalPosts, Stats.LastPost FROM Users LEFT OUTER JOIN (
SELECT
UserId,
Name,
Email,
ISNULL(Stats.TotalPosts, 0) as TotalPosts,
Stats.LastPost
FROM Users
LEFT OUTER JOIN
(
SELECT UserId, COUNT(*) as TotalPosts, MAX(DatePosted) as LastPost
FROM Articles
GROUP BY UserId
) as Stats ON Stats.UserId = Users.UserID
出于性能原因,SELECT语句中使用左连接而不是子查询-返回多个聚合统计信息(总文章数和最后一篇文章的日期)
我在C#4.0中将其转换为LINQ to Entities查询方面取得了一些成功,但我不完全确定join应该如何与group语句相结合。我想我是从SQL的角度来考虑这个问题,而不是正确地使用LINQ
我成功地将统计数据分解为一个单独的查询:
var stats =
(
from a in entities.Articles
group a by a.UserId into g
select new
{
UserId = g.Key,
TotalPosts = g.Count(),
LastUpdated = g.Max(i => i.DatePosted)
}
);
var query =
(
from u in entities.Users
join s in stats on u.UserId equals s.UserId
orderby u.Name
select new UserListing()
{
UserId = u.UserId,
Name = u.Name,
Email = u.Email,
TotalPosts = s.TotalPosts,
LastUpdated = s.LastUpdated
}
);
不幸的是,LINQ查询中使用的联接过滤掉了所有未提交任何文章的用户
通过包含DefaultIfEmpty切换到与外部联接等效的连接会导致其他问题-我只能为TotalPosts返回“null”,而不是0。即使在select中使用“TotalPosts=(s.TotalPosts==null)?0:s.TotalPosts”,也会引发异常,除非TotalPosts属性可为null
以这种方式组合详细信息行和聚合信息的最佳实践是什么
谢谢 试试这个:
var query =
(
from u in entities.Users
join s in stats on u.UserId equals s.UserId into g
from a in g.DefaultIfEmpty()
orderby u.Name
select new UserListing()
{
UserId = u.UserId,
Name = u.Name,
Email = u.Email,
TotalPosts = a.TotalPosts,
LastUpdated = a.LastUpdated
}
);
要获得外部联接,需要使用
DefaultIfEmpty
。
要解决空值问题,您可以尝试
TotalPosts = s.TotalPosts.GetValueOrDefault(),
或者,如果s.TotalPosts不知何故没有显示为int?
,您可以尝试类似的黑客攻击
TotalPosts = ((int?)s.TotalPosts).GetValueOrDefault(0),
一个选项是确保
stats
查询中的相应属性可为空。如果可能的话,LINQ to实体将进行必要的调整以实现这一目标。然后像往常一样执行左外连接
var stats =
(
from a in entities.Articles
group a by a.UserId into g
select new
{
UserId = g.Key,
TotalPosts = (int?)g.Count(),
LastUpdated = g.Max(i => i.DatePosted)
}
);
var query =
(
from u in entities.Users
join s in stats on u.UserId equals s.UserId into joinedStats
from s in joinedStats.DefaultIfEmpty() // do left outer join
orderby u.Name
select new UserListing()
{
UserId = u.UserId,
Name = u.Name,
Email = u.Email,
TotalPosts = s.TotalPosts, // null if doesn't contain stats
LastUpdated = s.LastUpdated // default DateTime if doesn't contain stats
}
);
我无法编译这个。我的LINQ知识不是很好,但是GetValueOrDefault不是一个“int”或“int”的扩展方法,这很有帮助,但是我仍然存在TotalPosts为null而不是零的问题(并且要求它被键入为“int?”。有没有办法在查询中解决这个问题,并滚动“stats”在同一条语句中?
TotalPosts
的类型为int?
,如果相应的用户没有任何数据,则其值为null
。这正是您想要的,对吗?当然可以将stats
查询与主查询合并,但它现在的方式更干净。我不会这样做如果我是你。@ShadowChaser:linq的工作方式是将查询合并在一起没有任何好处。与SQL不同,它不能提高性能。将它们分开实际上更清晰。很高兴听到我将它们分开的消息!查询工作得很好。我将TotalPosts保留为int?和null,而不是零,并在调用中处理它代码。虽然不完美,但仍然可以很好地工作。谢谢!