Entity Framework Core两个外键-同一张表

5
我可以帮助您翻译IT技术方面的内容。以下是需要翻译的内容:

我在使用两个外键引用同一张表时遇到了问题。虽然外键id字段已被填充,但导航字段和列表(Team字段)却为空。

我的类如下:

public class Team
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Fixture> HomeFixtures { get; set; }
    public virtual ICollection<Fixture> AwayFixtures { get; set; }
}

public class Fixture
{
    public int Id { get; set; }

    public int HomeTeamId { get; set; }
    public int AwayTeamId { get; set; }

    public Team HomeTeam { get; set; }
    public Team AwayTeam { get; set; }
}

和我的dbContext

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Team> Teams { get; set; }
    public DbSet<Fixture> Fixtures { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Fixture>()
            .HasOne(f => f.HomeTeam)
            .WithMany(t => t.HomeFixtures)
            .HasForeignKey(t => t.HomeTeamId)
            .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);

        modelBuilder.Entity<Fixture>()
            .HasOne(f => f.AwayTeam)
            .WithMany(t => t.AwayFixtures)
            .HasForeignKey(t => t.AwayTeamId)
            .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
    }
}

我尝试为HomeTeam和AwayTeam属性添加[ForeignKey()]属性,但没有效果。 我还尝试将OnModelCreating方法改为另一种方式,即:

modelBuilder.Entity<Team>()
    .HasMany(t => t.HomeFixtures)
    .WithOne(f => f.HomeTeam)
    .HasForeignKey(f => f.HomeTeamId)
    .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);

对于客场比赛,情况也是一样的,但这会产生相同的行为。

无论我如何查询,似乎都没有关系,但最简单的情况是

Fixture fixture = await _context.Fixtures.SingleOrDefaultAsync(f => f.Id == id);

返回的fixture对象包含在数据库中有效且存在的团队ID,但是Team对象仍未填充。

有人知道我做错了什么吗? 这是一个全新的项目和全新的数据库,所以没有旧代码干扰。 我正在使用Visual Studio 2017rc与Entity Framework Core。


数据在数据库中存在吗?显示查询。 - Steve Greene
@SteveGreene 我已经在问题中添加了一个示例查询,但是它会发生在多个查询中。Fixture对象返回的TeamIds是有效的,并且与数据库中的Team记录匹配。 - Mog0
2
EF Core目前不支持惰性加载,请参见https://github.com/aspnet/EntityFramework/issues/3797 您需要对导航进行急切加载以使它们被填充。文档链接:https://learn.microsoft.com/en-us/ef/core/querying/related-data - Smit
正如Smit所说,你需要使用Includes()函数。 - Steve Greene
@Smit,我相信这个问题应该由你来回答。我建议你在回答中包含使用Eager Loading和Includes的解决方法示例,就像Steve提到的那样。 - Konstantin
1个回答

2

目前 EF Core 不支持延迟加载。相关问题跟踪在这里

这意味着默认情况下,导航属性不会被加载并保持为空。作为解决方法,可以使用急切加载或显式加载模式。

急切加载

急切加载是一种模式,您可以在运行查询时使用 Include API 请求您需要的引用数据。它的用法与 EF6 中的工作方式略有不同。要包含任何导航,必须在查询中的 include 方法中指定 lambda 表达式(或字符串名称)。

例如: await _context.Fixtures.Include(f => f.HomeTeam).FirstOrDefaultAsync(f => f.Id == id);

目前,不支持过滤的 include,因此您可以请求完全加载导航或排除其加载。因此,lambda 表达式不能太复杂。它必须是简单的属性访问。此外,要加载嵌套导航,您可以使用属性访问调用将它们链接在一起(如 a.b.c),或者当其在集合导航之后(因为您无法将它们链接在一起)时使用 ThenInclude

例如: await _context.Fixtures.Include(f => f.HomeTeam).ThenInclude(t=> t.HomeFixtures).FirstOrDefaultAsync(f => f.Id == id);

需要记住,include 表示从调用它的实体类型到填充路径上所有导航的路径。如果您在第二个或更高级别包括多个导航,则通常需要编写重复调用。这只是为了语法而已,查询将被优化并且不会进行重复工作。另外,使用字符串 include,您可以指定整个导航路径,而无需使用 ThenInclude。由于引用导航可以利用连接以单个查询获取所需的所有数据,并且集合导航可以在单个查询中加载所有相关数据,因此急切加载是最有效的方式。

显式加载

当您在内存中加载对象并需要加载导航时,在没有延迟加载的情况下,您需要自己调用 Load 方法。这些方法在 ReferenceEntryCollectionEntry 上定义。

例如:

Fixture fixture = await _context.Fixtures.SingleOrDefaultAsync(f => f.Id == id);
_context.Entry(fixture).Reference(f => f.HomeTeam).Load();

var team = await _context.Teams.SingleOrDefaultAsync(t => t.Id == id);
_context.Entry(team).Collection(f => f.HomeFixtures).Load();

为了进行参考导航,您需要在EntityEntry上使用Reference来获取ReferenceEntry。对于集合导航,等效的方法是Collection。然后,只需调用其中的Load方法以加载导航中的数据。如果需要,还有LoadAsync的异步版本。
希望这可以帮到您。

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