Linq-to-entities - Include() 方法无法加载

46
如果我使用联接(join)方法,Include() 方法将不再起作用,例如:
from e in dc.Entities.Include("Properties")
join i in dc.Items on e.ID equals i.Member.ID
where (i.Collection.ID == collectionID) 
select e

e.Properties没有加载

如果没有使用join,Include()可以正常工作

Lee


为什么您这样认为?执行后您没有对其赋值吗? - pocheptsov
我猜“Properties”不是你传递给Include的实际字符串。这意味着你省略了问题中最重要的部分。此外,我质疑为什么你要使用join;在Entity Framework中,导航属性通常是遍历关系的正确方式。 - Craig Stuntz
pocheptsov - 我知道 Properties 没有加载因为 Properties.IsLoaded 是 False。嗨 Craig - "Properties" 是正确的字符串。连接在不同的导航属性 Items 上。连接是存在的,因为我有一个 Item 对象的属性值(Collection.ID),但我想要与之相关的实体。Lee - Lee Atkinson
5个回答

55

更新:实际上,我最近添加了另一个技巧,涉及到这个问题,并提供了一种可能更好的备选解决方案。其思路是将Include()的使用延迟到查询结束时,有关详细信息,请参见:Tip 22 - 如何使include真正包含


在使用Include()时,Entity Framework已知存在限制。某些操作不支持Include。

看起来你可能遇到了其中之一的限制,为了解决这个问题,你应该尝试类似于以下的方法:

var results = 
   from e in dc.Entities //Notice no include
   join i in dc.Items on e.ID equals i.Member.ID
   where (i.Collection.ID == collectionID) 
   select new {Entity = e, Properties = e.Properties};

这将返回属性,如果实体与属性之间的关系是一对多(但不是多对多),则您会发现每个结果的匿名类型在以下位置具有相同的值:

anonType.Entity.Properties
anonType.Properties

这是Entity Framework中称为关系修复的功能的副作用。
请参阅我的EF提示系列中的Tip 1以获取更多信息。

1
这还是现状吗?如果你使用“select new Item {...};”,那么.Include()语句就不起作用了吗? - grimus
@grimus,将整个查询包装在“Include”中似乎在查询生成的元素类型不是实体类型时目前无法正常工作。然而,在“select”子句中获取导航属性值的原始建议仍然似乎可以解决这个问题。 - Sam

20

尝试这样做:

var query = (ObjectQuery<Entities>)(from e in dc.Entities
            join i in dc.Items on e.ID equals i.Member.ID
            where (i.Collection.ID == collectionID) 
            select e)

return query.Include("Properties") 

4

那么"实体"上与"项目.成员"相关联的导航属性的名称是什么呢(即导航的另一端)。您应该使用这个属性来替代join操作。例如,如果在"实体"中添加一个名为"成员"的属性,其基数为1,并且"成员"具有一个名为"项目"的属性,其基数为多个,您可以这样做:

from e in dc.Entities.Include("Properties")
where e.Member.Items.Any(i => i.Collection.ID == collectionID) 
select e

我猜测这里涉及到的是您的模型属性,但这应该能给您一个大致的想法。在大多数情况下,在LINQ to Entities中使用join是错误的,因为这表明您的导航属性可能没有正确设置,或者您没有使用它们。


1

所以,我意识到我来晚了,但我想添加我的发现。这应该是对Alex James帖子的评论,但由于我没有声望,所以只能在这里发布。

我的答案是:它似乎根本不能按照你的意图工作。Alex James提供了两个有趣的解决方案,但如果你尝试并检查SQL,那就太糟糕了。

我正在处理的示例是:

        var theRelease = from release in context.Releases
                         where release.Name == "Hello World"
                         select release;

        var allProductionVersions = from prodVer in context.ProductionVersions
                                    where prodVer.Status == 1
                                    select prodVer;

        var combined = (from release in theRelease
                        join p in allProductionVersions on release.Id equals p.ReleaseID
                        select release).Include(release => release.ProductionVersions);              

        var allProductionsForChosenRelease = combined.ToList();

这是两个示例中较简单的一个。如果没有包含,它会生成完全合理的 SQL 语句:
SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name]
    FROM  [dbo].[Releases] AS [Extent1]
    INNER JOIN [dbo].[ProductionVersions] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ReleaseID]
    WHERE ('Hello World' = [Extent1].[Name]) AND (1 = [Extent2].[Status])

但是,使用OMG:
SELECT 
[Project1].[Id1] AS [Id], 
[Project1].[Id] AS [Id1], 
[Project1].[Name] AS [Name], 
[Project1].[C1] AS [C1], 
[Project1].[Id2] AS [Id2], 
[Project1].[Status] AS [Status], 
[Project1].[ReleaseID] AS [ReleaseID]
FROM ( SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Id] AS [Id1], 
    [Extent3].[Id] AS [Id2], 
    [Extent3].[Status] AS [Status], 
    [Extent3].[ReleaseID] AS [ReleaseID],
    CASE WHEN ([Extent3].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM   [dbo].[Releases] AS [Extent1]
    INNER JOIN [dbo].[ProductionVersions] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ReleaseID]
    LEFT OUTER JOIN [dbo].[ProductionVersions] AS [Extent3] ON [Extent1].[Id] = [Extent3].[ReleaseID]
    WHERE ('Hello World' = [Extent1].[Name]) AND (1 = [Extent2].[Status])
)  AS [Project1]
ORDER BY [Project1].[Id1] ASC, [Project1].[Id] ASC, [Project1].[C1] ASC

完全是垃圾。需要注意的关键点是,它返回未被status=1限制的表的外部连接版本。

这会导致错误的数据被返回:

Id  Id1 Name        C1  Id2 Status  ReleaseID
2   1   Hello World 1   1   2       1
2   1   Hello World 1   2   1       1

请注意,尽管我们进行了限制,但在那里返回了2的状态。它根本不起作用。 如果我做错了什么,我会很高兴找出来,因为这让Linq变得荒谬。我喜欢这个想法,但执行起来似乎现在无法使用。

出于好奇,我尝试使用LinqToSQL dbml而不是LinqToEntities edmx来生成上述混乱的内容:

SELECT [t0].[Id], [t0].[Name], [t2].[Id] AS [Id2], [t2].[Status], [t2].[ReleaseID], (
    SELECT COUNT(*)
    FROM [dbo].[ProductionVersions] AS [t3]
    WHERE [t3].[ReleaseID] = [t0].[Id]
    ) AS [value]
FROM [dbo].[Releases] AS [t0]
INNER JOIN [dbo].[ProductionVersions] AS [t1] ON [t0].[Id] = [t1].[ReleaseID]
LEFT OUTER JOIN [dbo].[ProductionVersions] AS [t2] ON [t2].[ReleaseID] = [t0].[Id]
WHERE ([t0].[Name] = @p0) AND ([t1].[Status] = @p1)
ORDER BY [t0].[Id], [t1].[Id], [t2].[Id]

稍微更简洁了一点 - 奇怪的计数子句,但总体上仍然失败。
有人真正在实际业务应用程序中使用过这些东西吗?我真的开始怀疑了...... 请告诉我我错过了什么明显的东西,因为我真的想喜欢Linq!

-1

尝试使用更冗长的方式 做更多或更少相同的事情 来获得相同的结果,但需要进行更多的数据调用:

var mydata = from e in dc.Entities
             join i in dc.Items 
                 on e.ID equals i.Member.ID 
             where (i.Collection.ID == collectionID) 
             select e;

foreach (Entity ent in mydata) {
    if(!ent.Properties.IsLoaded) { ent.Properties.Load(); }
}

你还是得到了相同(意外的)结果吗? 编辑:更改了第一句话,因为它是不正确的。感谢指正的评论!

1
这完全不是一回事。你的代码将导致 n+1 次数据库查询,其中 n 是检索到的行数。请改为在 1 次数据库查询中包含结果。 - Craig Stuntz
我知道我做的不是同一件事 - 这只是说我最终会得到相同的结果的懒惰方式。这样做的主要原因是为了看看是否会以这种方式加载任何属性。如果没有,问题不太可能出现在.Include()语法中,而是出现在数据中(例如,相关的属性记录可能丢失...)。 - Tomas Aschan
你好Tomas,如果我使用Properties.Load()来加载属性,它会正确地加载它们。如果我在一个不包含连接的查询中使用Include("Properties"),例如:from e in dc.Entities.Include("Properties") where (e.ID = id) select e;它可以正常工作。Lee - Lee Atkinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接