EF Core 返回空关系,直到直接访问

Posted

技术标签:

【中文标题】EF Core 返回空关系,直到直接访问【英文标题】:EF Core returns null relations until direct access 【发布时间】:2022-01-05 03:51:48 【问题描述】:

我有一些类似下面的模型:

public class Mutant

    public long Id  get; set; 
    ...

    // Relations
    public long OriginalCodeId  get; set; 
    public virtual OriginalCode OriginalCode  get; set; 
    public int DifficultyLevelId  get; set; 
    public virtual DifficultyLevel DifficultyLevel  get; set; 

public class OriginalCode

    public long Id  get; set; 
    ...

    // Relations
    public virtual List<Mutant> Mutants  get; set; 
    public virtual List<OriginalCodeInputParameter> OriginalCodeInputParameters  get; set; 

DBContextOnModelCreating 中,我建立了这样的关系:

        modelBuilder.Entity<Mutant>()
            .HasOne(m => m.OriginalCode)
            .WithMany(oc => oc.Mutants)
            .HasForeignKey(m => m.OriginalCodeId)
            .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);

        modelBuilder.Entity<Mutant>()
            .HasOne(m => m.DifficultyLevel)
            .WithMany(dl => dl.Mutants)
            .HasForeignKey(m => m.DifficultyLevelId)
            .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);

现在当我请求 Mutants 时,OriginalCode 为空:

但只要我请求OriginalCodes,如下所示:

那么突变体的OriginalCode字段将不为空:

这是什么原因,我该如何解决?

【问题讨论】:

【参考方案1】:

EF Core 文档的Loading Related Data 部分解释了原因。

第一个行为是因为 EF Core 当前不支持延迟加载,因此通常您将获得导航属性的 null,直到您通过急切或显式加载专门加载它们。但是,Eager loading 部分包含以下内容:

提示 Entity Framework Core 将自动修复先前加载到上下文实例中的任何其他实体的导航属性。因此,即使您没有明确包含导航属性的数据,如果之前加载了部分或所有相关实体,该属性仍可能会被填充。

这解释了为什么导航属性在第二种情况下不为空。

现在,我不确定您要修复这两种行为中的哪一种,因此将尝试同时解决这两个问题。

第一个行为可以通过使用当前可用的加载相关数据的方法之一来“修复”,例如急切加载:

var mutants = db.Mutants.Include(m => m.OriginalCode).ToList();

第二种行为是“设计使然”,无法控制。如果您想避免这种情况,请确保使用全新的 DbContext 实例,仅用于执行单个查询以重试所需的数据。

更新:从 v2.1 开始,EF Core 支持Lazy Loading。但是默认情况下它没有启用,所以为了使用它,应该标记所有导航属性virtual,安装Microsoft.EntityFrameworkCore.Proxies 并通过UseLazyLoadingProxies 调用启用它,或者使用Lazy-loading without proxies - 两者都在EF Core中的示例中进行了解释文档。

【讨论】:

你可能猜到我想控制第一个行为。但是还有一个很大的问题。你提到的这种方式,我应该明确地解决要填充的关系,对吗? 确实如此。您必须使用几种 Include / ThenInclude 方法指定要“包含”的每一个。 AFAIK 有一些计划在未来自动实现,但目前这是唯一的选择。 :) 谢谢。非常有帮助的答案。 现在 EF Core 支持延迟加载:docs.microsoft.com/en-us/ef/core/querying/… 虽然我认为没有设置为默认值,但这意味着它不是推荐的解决方案!我认为如果使用得当,非惰性方法更难编写,但更适合运行时,这将使许多未来的数据库请求合二为一。【参考方案2】:

使用包管理器控制台安装 Microsoft.EntityFrameworkCore.Proxies

install-package Microsoft.EntityFrameworkCore.Proxies

然后在你的 Context 类中添加.UseLazyLoadingProxies():

namespace SomeAPI.EFModels

    public partial class SomeContext : DbContext
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        
            if (!optionsBuilder.IsConfigured)
            
                optionsBuilder
                    .UseLazyLoadingProxies()
                    .UseSqlServer(connectionString);
            

        
    

【讨论】:

以上是关于EF Core 返回空关系,直到直接访问的主要内容,如果未能解决你的问题,请参考以下文章

EF Core 返回空关系,直到直接访问

EF Core Eager 加载返回 null

即使在使用 Incude 之后,EF 核心导航属性也会返回空值

如何直接填充由 EF Core 生成的多对多关系连接表?

从查询 C#、EF Core 返回一个值

在启动时访问 EF Core 数据库以设置依赖注入的常量值