EF4.2额外的左外连接到同一个表

时间:2012-01-03 19:10:35

标签: sql sql-server linq entity-framework optimization

我知道有一些关于此的问题,大多数与已解决的旧问题或多个表有关。在我看到的任何其他“左外连接”问题中都没有涉及此问题,我在同一查询中得到INNER JOINLEFT OUTER JOIN到同一个表。

表格大纲:

Users: id (PK)
       Name (VARCHAR) 
       ProfileImageUri (VARCHAR)
Locations: id (PK)
LocationBPNTips: id (PK)
                 TipText (VARCHAR)
                 CreatedAt (Datetime)
                 UserId (int) (FK to User.id, navigation property is called User)
                 LocationId (int) (FK to Location.id)

(还有更多,但不相关:))

在我的场景中,我通过投影对引用的表执行查询,我得到一个额外的左外连接,这是我运行的(注释的部分与问题无关,评论为了更清洁的SQL,EF做正确的排序(甚至比我想象的更好:))):

LocationBPNTips
     .Where(t => t.LocationId == 33)
     //.OrderByDescending(t => intList.Contains(t.UserId))
     //.ThenByDescending(t => t.CreatedAt)
     .Select(tip => new LocationTipOutput
     {
             CreatedAt = tip.CreatedAt,
             Text = tip.TipText,
             LocationId = tip.LocationId,
             OwnerName = tip.User.Name,
             OwnerPhoto = tip.User.ProfileImageUri
     }).ToList();

这就是生成的SQL

SELECT 
[Extent1].[LocationId] AS [LocationId], 
[Extent1].[CreatedAt] AS [CreatedAt], 
[Extent1].[TipText] AS [TipText], 
[Extent2].[Name] AS [Name], 
[Extent3].[ProfileImageUri] AS [ProfileImageUri]
FROM   [dbo].[LocationBPNTips] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[Id]
LEFT OUTER JOIN [dbo].[Users] AS [Extent3] ON [Extent1].[UserId] = [Extent3].[Id]
WHERE 33 = [Extent1].[LocationId]

如您所见,LEFT OUTER JOIN是在INNER JOIN

的同一个表格上完成的

我认为最佳代码将是(注意,我手动将Extent3重命名为Extent2,并添加了注释。这不是由EF生成的!) - 使用我当前的数据,运行速度提高约22%(排序,这个%应该更高,没有排序),因为不需要额外的连接..

SELECT 
[Extent1].[LocationId] AS [LocationId], 
[Extent1].[CreatedAt] AS [CreatedAt], 
[Extent1].[TipText] AS [TipText], 
[Extent2].[Name] AS [Name], 
[Extent2].[ProfileImageUri] AS [ProfileImageUri]
FROM   [dbo].[LocationBPNTips] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[Id]
--LEFT OUTER JOIN [dbo].[Users] AS [Extent3] ON [Extent1].[UserId] = [Extent3].[Id]
WHERE 33 = [Extent1].[LocationId]

我尝试过的不同查询(投影是这些中的匿名类型):

LocationBPNTips
     .Where(t => t.LocationId == 33)
     //.OrderByDescending(t => intList.Contains(t.UserId))
     //.ThenByDescending(t => t.CreatedAt)
     .Select(tip => new 
     {
             CreatedAt = tip.CreatedAt,
             Text = tip.TipText,
             LocationId = tip.LocationId,
             OwnerName = tip.User,
             OwnerPhoto = tip.User
     }).ToList()

SQL输出搞砸了,它以与上面相同的格式选择整个用户表两次,内部然后左外部。我认为我在理论上可以看到为什么会发生这种情况,因为我要求两次数据 - 尽管它可以在内存中完成,而不是由SQL进行额外的连接 - 但在我的情况下我没有要求数据两次,我只询问一次不同的列。我做了这个测试,看看双连接是否一致。

我也尝试过跑步:

LocationBPNTips
    .Where(t => t.LocationId == 33)
    .Select(tip => new 
    {
            CreatedAt = tip.CreatedAt,
            Text = tip.TipText,
            LocationId = tip.LocationId,
            OwnerName = tip.User.Name
    }).ToList()

这个按预期返回了干净的单内连接,但这不是我想做的事情

所以问题是:这是一个错误吗?我错误地使用了EF吗?

1 个答案:

答案 0 :(得分:0)

我已经看过类似的问题了。我们可以称之为bug或功能。简单的EF查询生成远非理想。 ADO.NET团队修复了每个主要版本的一些问题。我目前没有安装June CTP 2011以验证它是否也出现在下一版本的第一个预览中。

编辑:

根据this answer类似的问题在2011年6月CTP修复。