如何简化这个LINQ to Entities Query来从中制作一个不太可怕的SQL语句? (包含Distinct,GroupBy和Count)

时间:2011-11-13 23:36:57

标签: sql performance linq wcf-ria-services

我有这个SQL表达式:

SELECT Musclegroups.Name, COUNT(DISTINCT Workouts.WorkoutID) AS Expr1
FROM   Workouts INNER JOIN
       Series ON Workouts.WorkoutID = Series.WorkoutID INNER JOIN
       Exercises ON Series.ExerciseID = Exercises.ExerciseID INNER JOIN
       Musclegroups ON Musclegroups.MusclegroupID = Exercises.MusclegroupID
GROUP BY Musclegroups.Name

由于我正在开发一个在WCF Ria LinqToEntitiesDomainService中使用EF的项目,我必须使用LINQ查询(如果这不是必须的话请通知我)。 我做了这个表达:

var WorkoutCountPerMusclegroup = (from s in ObjectContext.Series1
                                 join w in ObjectContext.Workouts on s.WorkoutID equals w.WorkoutID
                                 where w.UserID.Equals(userid) && w.Type.Equals("WeightLifting")
                                 group s by s.Exercise.Musclegroup into g                                              
                                 select new StringKeyIntValuePair
                                 {
                                      TestID = g.Select(n => n.Exercise.MusclegroupID).FirstOrDefault(),                                                  
                                      Key = g.Select(n => n.Exercise.Musclegroup.Name).FirstOrDefault(),
                                      Value = g.Select(n => n.WorkoutID).Distinct().Count()
                                 });

StringKeyIntValuePair只是我制作的自定义实体类型,因此我可以将信息发送到Silverlight客户端。这就是为什么我需要为它设置一个“TestID”,因为它是一个实体,它需要一个。

问题是,这个linq查询产生了这个可怕的SQL语句: http://pastebay.com/144532

我想有更好的方法可以查询这些信息,也许更好的linq表达式。或者是否可以以某种方式使用普通SQL进行查询?

修改

我意识到TestID是不必要的,因为名为“Key”的其他属性(Im分组的那个)成为组的键,所以它也是一个键。在此之后,我的查询如下所示:

var WorkoutCountPerMusclegroup = (from s in ObjectContext.Series1
                                          join w in ObjectContext.Workouts on s.WorkoutID equals w.WorkoutID
                                          where w.UserID.Equals(userid) && w.Type.Equals("WeightLifting")
                                          group w.WorkoutID by s.Exercise.Musclegroup.Name into g                                              
                                          select new StringKeyIntValuePair
                                          {                                                                                                   
                                              Key = g.Key,
                                              Value = g.Select(n => n).Distinct().Count()
                                          });

这会生成以下SQL:http://pastebay.com/144545

这似乎远远超过了半生不熟的linq查询的前一个sql语句。 但这还不错吗?或者这是LinqToEntities功能的边界,如果我想要更清晰的sql,我应该创建另一个使用LinqToSQL或其他东西运行的DomainService?

或者最好的方法是使用返回Rowsets的存储过程?如果是这样,是否有最佳实践异步执行此操作,如简单的WCF Ria DomainService查询?

1 个答案:

答案 0 :(得分:1)

我也想了解最佳做法。

编译lambda表达式linq可能需要花费很多时间(3-30s),尤其是使用group by然后FirstOrDefault(对于左内连接,意味着只从组中的第一行获取值) )。

生成的sql执行可能不是那么糟糕,但使用DbContext进行编译,无法使用.NET 4.0进行预编译。

作为一个例子,例如:

 var q = from a in DbContext.A
     join b ... into bb from b in bb.DefaultIfEmtpy()
     group new { a, b } by new { ... } into g
     select new 
     { 
        g.Key.Name1
        g.Sum(p => p.b.Salary)
        g.FirstOrDefault().b.SomeDate
     };

我们在一个案例中添加的每个FirstOrDefault导致+ 2s编译时间,这加起来3次= 6s仅用于编译未加载数据(花费少于500ms)。这基本上会破坏您的应用程序的可用性。用户将无缘无故地等待多次。

到目前为止,我们发现加速编译的唯一方法是将lambda表达式与对象表达式混合(可能不是正确的表示法)。

示例2:重构前面的示例1.

var q = (from a in DbContext.A
     join b ... into bb from b in bb.DefaultIfEmtpy()
     select new { a, b })
     .GroupBy(p => new { ... })
     .Select(g => new 
        { 
           g.Key.Name1
           g.Sum(p => p.b.Salary)
           g.FirstOrDefault().b.SomeDate
        });

在我们的案例中,上面的示例编译速度比示例1快得多,但仍然不够快,因此我们在响应关键区域的唯一解决方案是恢复到本机SQL(对于实体)或使用视图或存储过程(在我们的例子中是Oracle PL / SQL)。

一旦我们有时间,我们将测试预编译是否适用于.NET 4.5和/或.NET 5.0 for DbContext。

希望这有帮助,我们可以获得其他解决方案。