选择具有LINQ到实体查询中的记录的聚合(分组)统计信息

时间:2010-11-04 20:43:31

标签: linq c#-4.0 linq-to-entities group-by outer-join

我有一个查询我正在尝试从SQL(T-SQL)移植到LINQ到实体4.0(C#)。结果集包含标准“详细信息行”的组合以及聚合“统计信息”信息。

原始SQL使用标准选择左连接到聚合信息,类似于:

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查询有一些部分成功,但我不完全确定连接应该如何与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.即使使用“TotalPosts =(s.TotalPosts == null)?0:s.TotalPosts “在select中,除非TotalPosts属性可以为空,否则抛出异常。

以这种方式组合详细信息行和汇总信息的最佳做法是什么?

谢谢!

3 个答案:

答案 0 :(得分:1)

试试这个:

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
    }
);

答案 1 :(得分:1)

您拥有的一个选项是确保stats查询中的相应属性可以为空。如果可能的话,LINQ到实体将进行必要的调整以使其工作。然后像往常一样执行左外连接。

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
    }
);

答案 2 :(得分:0)

要获得外部联接,您需要使用DefaultIfEmpty。 要解决null问题,您可以尝试

TotalPosts = s.TotalPosts.GetValueOrDefault(),

或者如果s.TotalPosts以某种方式没有显示为int?你可能会尝试像

这样的黑客攻击
TotalPosts = ((int?)s.TotalPosts).GetValueOrDefault(0),