在 Entity Framework 6 查询中取消引用可能为空的引用

Posted

技术标签:

【中文标题】在 Entity Framework 6 查询中取消引用可能为空的引用【英文标题】:Dereference of a possibly null reference in Entity Framework 6 query 【发布时间】:2022-01-02 09:27:15 【问题描述】:

我有一个启用了可为空引用类型的 .NET 6 项目 (<Nullable>enable</Nullable>)。我有这个 EF 实体:

public class PostFile 
  public Int32 UserId  get; set; 
  public Int32 PostId  get; set; 

  public virtual User? User  get; set; 
  public virtual Post? Post  get; set; 

我在上面添加了? 以防止出现此可为空的警告:

Non-nullable property '...' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

现在,我有这个 Entity Framework 6 LINQ 查询:

var postFiles = context.postFiles.Where(x => x.User.Id == request.UserId);

...但我收到以下警告:

Dereference of a possibly null reference.

...关于我的查询的这一部分:

x.User.Id == ...

如何解决此警告?

【问题讨论】:

PostFile和User的表关系是什么? (用户属性在哪里?)是否保证每个 PostFile 都只有一个用户(并且永远不会“没有”用户)? 另外,您是否希望 PostFile 没有关联的 Post?当您期望该值可能为空时,您只会使用?。如果没有匹配的 Post,我不希望 PostFile 存在。 PostFile 将始终有一个 User 和 Post 但我相信 EF 在同样的情况下可能不会加载 Post 或 File? 可能是 context.postfiles.Include(x => x.User).Where(....entityframework.net/include 【参考方案1】:

我想你的意思是这样的:

public class PostFile 
    public Int32 UserId get; set; 
    public Int32 PostId  get; set; 

    public virtual User? User  get; set; 
    public virtual Post? Post  get; set; 

您最初的问题是 C#8 引入的警告,它更明确地使用可空引用类型。对于实体,上述实现无效,除非这些关系确实是可选的,这将要求它们的 FK 字段(UserId 和 PostId)也可以为 Null。它们可能不是可选的。

解决这个问题的主要方法:

A) 关闭该功能。 (在项目中禁用可空引用)

B) 请求“宽恕”这些不应该为空,但在构造时不会处于有效状态的事实。 (EF 会管理它们)

public class PostFile 
    public Int32 UserId get; set; 
    public Int32 PostId  get; set; 

    public virtual User User  get; set;  = null!;
    public virtual Post Post  get; set;  = null!;

更改模型以将导航属性标记为可空引用可能会导致各种问题,就像它可以迁移一样,并且将开始用可空的 FK 替换不可空的 FK。将这些引用标记为 Null-able 并让 EF 满意:

public class PostFile 
    public Int32? UserId get; set; 
    public Int32? PostId  get; set; 

    public virtual User? User  get; set; 
    public virtual Post? Post  get; set; 

这几乎肯定不是您在您的域中想要的,如果 UserId 和 PostId 是 PK 的一部分,甚至是合法的。

就我个人而言,我将 C# 中的这种变化称为最初默认启用的“地雷”MS,例如 EF 中的客户端评估。 :) 我预见到有关此警告或重大更改的许多 *** 问题,以及充斥着“!”的许多客户端代码库宽恕标记作为旧的不可空对象/引用被传递到代码中,并带有可空引用检查。

【讨论】:

【参考方案2】:

您应该将导航实体标记为可为空。您不应该启用延迟加载,因此导航属性可以从查询返回为null。即使数据库中需要它们,您的代码也不必加载它们。

在您的查询表达式中,您可以确定 Entity Framework 不会在客户端执行它们,而是从中解析出一个 SQL 查询。

因此:

.Where(x => x.User!.Id == request.UserId)

你可以用User! 告诉编译器你知道它在那里不会为空。除非你启用了客户端评估,但你不应该这样做,如果你这样做了,无论如何你都需要一个空检查。

至于PostFile.User的用法,如:

var postFile = dbContext.PostFiles.FirstOrDefault(p => p....) ?? throw ...;
var user = postFile.User;

如果您没有Include(p => p.User) 并且没有启用延迟加载,则它可以是null,因此user 在使用前需要进行空检查。

如果您确实使用延迟加载,则可以禁用警告:

#pragma warning disable CS8618 // EF initializes these properties through lazy loading
    public virtual User User  get; set; 
#pragma warning restore CS8618 

【讨论】:

【参考方案3】:

我认为你需要这个:

public class PostFile 
    public User User  get; set; 
    public Post Post  get; set; 

然后调用

var postFiles = context.postFiles.Where(x => x.User.Id == request.UserId).Include(x => x.Post);

【讨论】:

没有。不将这些属性标记为可为空会使编译器发出警告,指出它们可能未初始化,这是真的。【参考方案4】:

var postFiles = context.postFiles.Where(x => x.User != null && x.User.Id == request.UserId); 呢?

【讨论】:

以上是关于在 Entity Framework 6 查询中取消引用可能为空的引用的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 6 查询时上下文重复

如何将带有 EntityState 和值的查询从 Entity Framework 5 转换为 6?

为啥 Entity Framework 6 会为简单的查找生成复杂的 SQL 查询?

Entity Framework Core 6.0 预览4 性能改进

《Entity Framework 6 Recipes》中文翻译系列 (14) -----第三章 查询之查询中设置默认值和存储过程返回多结果集 (转)

《Entity Framework 6 Recipes》中文翻译系列 (12) -----第三章 查询之使用SQL语句 (转)