实体框架 - 预先加载相关实体

9

对不起,标题不够具体——我不知道如何简洁地描述这个问题。 我有旅行和位置两个实体之间存在多对多的关系——很直接,但是位置实体并不需要知道使用它们的旅行。我创建了以下实体来表示这种关系:

public class Trip
{
    public int TripId { get; set; }
    public virtual IList<TripLocation> TripLocations { get; set; }
}

public class TripLocation
{
    public int TripId { get; set; }
    public int LocationId { get; set; }

    public virtual Location Location { get; set; }
}

public class Location
{
    public int LocationId { get; set; }
    // Note: Intentionally no collection of Trips
}

我可以让 Trip 急切加载其 TripLocations,但我无法让 TripLocations 急切加载它们的 Locations。我尝试了许多流畅配置和在查询中包含的组合,例如:

IQueryable<Trip> query = from trip in context
                              .Include(r =>r.TripLocations)
                              .Include(r => r.TripLocations.Select(tl => tl.Location))
                         select ride;

非常感谢您的任何建议!


抱歉,应该是“旅行”。 - gruve
TripLocation是存在的,因为我不想在位置中有一组旅行,并且我无法弄清楚如何使用流畅的配置来进行多对多关系。由于@tyron的答案帮助我使其正常工作,我将回去看看是否可以按照您的建议进行操作。 - gruve
只需使用 modelBuilder.Entity<Trip>().HasMany(t => t.Locations).WithMany().Map(...);。在 Location 中没有 Trips 集合的情况下,使用不带参数的 WithMany() - Slauma
1
@tyron:我不确定我是否正确理解了你的意思,但我的意思是:如果您在联接表中没有其他属性,则可以创建一个多对多关系而无需中间实体(TripLocation)。如果您有这样的附加属性,则根本无法创建多对多关系,必须创建两个一对多关系并将联接表公开为模型中的实体:https://dev59.com/zGw05IYBdhLWcg3wwEYS#7053393 - Slauma
@Slauma:我理解了它们的区别,在我的项目中,我有额外的属性,但是在使用联接实体时遇到了一些问题。你提供的例子看起来非常好,我会仔细研究它。 - tyron
显示剩余4条评论
3个回答

14
我在这里重新创建了您的场景,并且能够通过单个查询获得所有结果。
var a = from trip in context.Trips.Include("TripLocations.Location")
        select trip;

这就是全部内容。这是针对我的数据库进行查询的结果:

SELECT 
[Project1].[TripId] AS [TripId], 
[Project1].[Name] AS [Name], 
[Project1].[C1] AS [C1], 
[Project1].[TripId1] AS [TripId1], 
[Project1].[LocationId] AS [LocationId], 
[Project1].[LocationId1] AS [LocationId1], 
[Project1].[Name1] AS [Name1]
FROM ( SELECT 
    [Extent1].[TripId] AS [TripId], 
    [Extent1].[Name] AS [Name], 
    [Join1].[TripId] AS [TripId1], 
    [Join1].[LocationId1] AS [LocationId], 
    [Join1].[LocationId2] AS [LocationId1], 
    [Join1].[Name] AS [Name1], 
    CASE WHEN ([Join1].[TripId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM  [dbo].[Trips] AS [Extent1]
    LEFT OUTER JOIN  (SELECT [Extent2].[TripId] AS [TripId], [Extent2].[LocationId] AS [LocationId1], [Extent3].[LocationId] AS [LocationId2], [Extent3].[Name] AS [Name]
        FROM  [dbo].[TripLocations] AS [Extent2]
        INNER JOIN [dbo].[Locations] AS [Extent3] ON [Extent2].[LocationId] = [Extent3].[LocationId] ) AS [Join1] ON [Extent1].[TripId] = [Join1].[TripId]
)  AS [Project1]
ORDER BY [Project1].[TripId] ASC, [Project1].[C1] ASC

更新:

如果您想继续使用lambda版本,这个方法可以解决问题:

IQueryable<Trip> query = from ride in context.Set<Trip>()
                             .Include(t=>t.TripLocations.Select(l=>l.Location))                                     
                         select ride;

更多关于MSDN博客上的信息。


谢谢!你的回答证实了我走在正确的轨道上,并让我从不同的角度看待实际代码(上面的例子只是我认为相关部分的实际代码)。 - gruve
1
感谢提供Lambda等效代码。 - angularsen

0

在你的关系属性(例如Location)上删除VIRTUAL关键字,这将禁用延迟加载并强制你进行急切加载。


0
关于Lambda表达式,您可以像@tyron所说的那样使用context.Set,也可以使用context.Trips。例如:
IQueryable<Trip> query = from ride in context.Trips
                             .Include(t=>t.TripLocations.Select(l=>l.Location))                                     
                         select ride;

为了使这段代码正常工作,您需要在 DbContext 类中定义一个类型为 DbSet 的属性,如下所示:
public DbSet<Trip> Trips { get; set; }

定义一个返回DbSet的属性很好,但同时它相当于访问context.Set。这只是一种代码风格,也可以结合使用。

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