2010-11-04 73 views
0

我试图从SQL(T-SQL)移植到LINQ-to-Entities 4.0(C#)的查询。结果集包含标准“详细信息行”以及聚合“统计信息”的组合。在LINQ-to-Entities查询中选择记录的聚合(分组)统计信息

原来的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到实体查询,但我不完全知道如何加入配合集团声明。我想我正在考虑这个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加入的当量导致其他问题 - I只能返回为TotalPosts而不是0“空”即使“TotalPosts =(s.TotalPosts == NULL)0:S'。 TotalPosts“,除非TotalPosts属性可以为空,否则将引发异常。

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

谢谢!

回答

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

为了得到一个外连接,你需要使用DefaultIfEmpty。 为了解决这个问题,空你可以尝试

TotalPosts = s.TotalPosts.GetValueOrDefault(), 

,或者s.TotalPosts并不会自动转起来作为int?你可以尝试像你有

TotalPosts = ((int?)s.TotalPosts).GetValueOrDefault(0), 
+0

我无法得到这个编译。我的LINQ知识不是很好,但GetValueOrDefault不是“int”或“int?”的扩展方法 – ShadowChaser 2010-11-05 00:18:23

1

一种选择黑客是为了确保stats查询中的适当属性可以为空。如果可能的话,LINQ-to-entities将进行必要的调整以使其工作。然后照常执行左外连接。

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

这有帮助,但我仍然有问题的TotalPosts为null而不是零(和要求它被键入为“int?”。 有没有什么办法来解决查询中的问题,并滚动“stats “up into the same statement? – ShadowChaser 2010-11-05 00:19:56

+0

'TotalPosts'将是'int?'的类型,如果相应的用户没有任何数据,它的值就是'null'。这就是你想要的吗?当然你可以合并'stats'查询与主要的一个,但它是更清洁你现在的方式。我不会那样做,如果我是你。 – 2010-11-05 01:09:02

+0

@ShadowChaser:linq工作的方式没有优势滚动查询在一起。不能提高性能。让他们分开实际上更清楚。 – Hogan 2010-11-05 01:48:20