今天下午在排查一个EF问题时,遇到了个很隐蔽的坑,特此记录。

问题

使用ef执行Insert对象到某表时报错,此对象的Address为空:

 不能将值 NULL 插入列 'Address',表 'dbo.xxx';列不允许有 Null 值。INSERT 失败。

检查数据库和迁移文件时发现Address这个字段被意外设置成nullable: false,而其它的字段却正常,按理来说对于string类型的属性,EFCore在codefirst模式下应该映射为可空类型。

代码也确认了实体中不包含[Required]注释,在任何地方也没有出现.IsRequired()的调用。

于是开始排查:手动创建一个空程序集,引用EFCore,从原项目拷贝EF设计时库、DbContext和各实体类,一顿操作后竟然发现在新的程序集中生成的迁移文件是符合预期的。

令人费解,在多次比对代码之后,发现是.csproj文件中的这一行配置导致的

<Nullable>enable</Nullable>

原因分析

C# 8 引入了一项名为可为 null 引用类型 (NRT) 的新功能。官方文档

该功能允许对引用类型进行批注,指示引用类型能否包含 null。

通过查看EF文档了解到,可为空引用类型通过以下方式影响 EF Core 的行为:

  • 如果禁用可为空引用类型,则按约定将具有 .NET 引用类型的所有属性配置为可选 (例如 string ) 。
  • 如果启用了可为 null 的引用类型,则基于属性的 .NET 类型的 C# 为 Null 性来配置属性:string? 将配置为可选属性,但 string 将配置为必需属性。

换而言之,启用了该功能后,把原本《引用类型可为空》的这个传统约定,更改称为了《引用类型是否可为空,是通过?语法来表明的》,实体中string类型的属性在C#中作为引用类型,自然而然地受到了这个影响。

果然,在删除了这个功能后,string?的语法将不起作用

解决

关闭此功能,重新生成迁移,更新数据库,问题解决。

后记

语言特性会影响EF实体与表结构映射的约定,官方示例中对于string类型的处理方式也做了说明:

无NRT


public class CustomerWithoutNullableReferenceTypes
{
public int Id { get; set; } [Required] // Data annotations needed to configure as required
public string FirstName { get; set; } [Required]
public string LastName { get; set; } // Data annotations needed to configure as required public string MiddleName { get; set; } // Optional by convention
}

有NRT

public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; } // Required by convention
public string LastName { get; set; } // Required by convention
public string? MiddleName { get; set; } // Optional by convention // Note the following use of constructor binding, which avoids compiled warnings
// for uninitialized non-nullable properties.
public Customer(string firstName, string lastName, string? middleName = null)
{
FirstName = firstName;
LastName = lastName;
MiddleName = middleName;
}
}

这两种模型的数据库映射是等价的。

之后应留意项目的"NRT"功能是否开启,在解决方案.csproj文件中用如下方式关闭

<Nullable>disable</Nullable>

留意实体类中是否有代码段被标识"NRT"功能开启

#nullable disable

#nullable enable

从 .NET 6 开始,默认情况下会为新项目启用这些功能。原始项目是.NET 5.0升级而来的,所以项目文件中并不会包含Nullable相关的配置。

为了一行bug,好值得的一个下午呢

[学习笔记]解决因C#8.0的语言特性导致EFCore实体类型映射的错误的更多相关文章

  1. C# 3.0新语言特性和改进(一)

    引言 关于C#3.0的特性,园子里已经有了一大把,可能大家都很熟悉了,虽然本人开发中使用过,但自己还是需要记录一下,总结一下.同时也是后面写Linq知识的基础.希望有兴趣的朋友,可以看看. C# 3. ...

  2. LINQ体验(2)——C# 3.0新语言特性和改进(上篇)

    整体来说.Visual Studio 2008和.NET 3.5是建立在.NET2.0核心的基础之上,.NET2.0核心本身将不再变化(假设不了解.NET2.0的朋友,请參看MSDN或者一些经典的书籍 ...

  3. go 学习笔记之10 分钟简要理解 go 语言闭包技术

    闭包是主流编程语言中的一种通用技术,常常和函数式编程进行强强联合,本文主要是介绍 Go 语言中什么是闭包以及怎么理解闭包. 如果读者对于 Go 语言的闭包还不是特别清楚的话,可以参考上一篇文章 go ...

  4. 【疯狂Java学习笔记】【第一章:Java语言概述】

    [学习笔记]1.Java与C#的异同:Java与C#的相同之处有很多,它们均摒弃了C++面向对象的多继承.宏定义.全局变量.全局函数.指针等等难以使用的机制,添加进了成熟的机制,如垃圾回收.接口等等. ...

  5. Dynamic CRM 2015学习笔记(2)更改系统显示语言

    默认装的是英文的系统,想换成中文的.下面列出操作步骤: 1. 下载并安装语言包 http://www.microsoft.com/en-US/download/details.aspx?id=4501 ...

  6. Android学习笔记----解决“com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536”问题

    同时在工程中引入了多个第三方jar包,导致调用的方法数超过了android设定的65536个(DEX 64K problem),进而导致dex无法生成,也就无法生成APK文件. 解决办法如下: 1.谷 ...

  7. MySQL学习笔记(一)Ubuntu16.04中MySQL安装配置(5.6优化、错误日志、DNS解决)

    目录 第一部分.5.6安装.配置.自动备份 第二部分.5.7源码安装.配置.自动备份 第一部分.5.6安装 1.安装mysql sudo apt-get install mysql-server su ...

  8. MySQL学习笔记(六)MySQL8.0 配置笔记

    今天把数据库配置文件修改了,结果重启不了了 需要使用 mysqld --initialize 或 mysqld --initialize-insecure 命令来初始化数据库 1.mysqld --i ...

  9. linux学习笔记-解决google-chrome打开后弹出输入密码以解锁您的登录密钥环的提示

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 一.理论知识 1.密钥的作用 google-chrome存储了网站登录时使用的账号和密码信息,这个密钥是用来保护这些信息的 2. ...

  10. 接口与协议学习笔记-USB协议_USB2.0_USB3.0不同版本(三)

    USB(Universal Serial Bus)全称通用串口总线,USB为解决即插即用需求而诞生,支持热插拔.USB协议版本有USB1.0.USB1.1.USB2.0.USB3.1等,USB2.0目 ...

随机推荐

  1. fatal: unable to access ' ' OpenSSL SSL_read: Connection was reset, errno 10054

    描述: git clone ...时报错 fatal: unable to access 'https://github.com/github-eliviate/papers.git/': OpenS ...

  2. podman 入门实战

    一入编程深似海,从此节操是路人. 最近使用podman,就想着写一篇总结性的笔记,以备后续参考.就如同写代码,不写注释,过了一段时间可能会想这是我写的吗?不会吧,还要理一下逻辑才能读懂,不利于后期维护 ...

  3. 在进行程序国际化时(Locale)遇到的问题

    Java程序国际化-Question 为了使程序能够个根据不同的国家/语言环境来输出不同的内容,通常需要把将需要输出的内容定义在资源文件中. 而在创建资源文件的过程中难免会出现问题,我遇到了在创建资源 ...

  4. 剑指 offer 第 28 天

    第 28 天 搜索与回溯算法(困难) 剑指 Offer 37. 序列化二叉树 请实现两个函数,分别用来序列化和反序列化二叉树. 你需要设计一个算法来实现二叉树的序列化与反序列化.这里不限定你的序列 / ...

  5. window的子对象

    在控制台输入 history history.length 历史记录个数history.back() 后退history.forward() 前进history.go(n) 跳到某一页,可正可负,当n ...

  6. 一款针对EF Core轻量级分表分库、读写分离的开源项目

    在项目开发中,如果数据量比较大,比如日志记录,我们往往会采用分表分库的方案:为了提升性能,把数据库查询与更新操作分开,这时候就要采用读写分离的方案. 分表分库通常包含垂直分库.垂直分表.水平分库和水平 ...

  7. 准确率、召回率及AUC概念分析

    准确率&&召回率 信息检索.分类.识别.翻译等领域两个最基本指标是准确率(precision rate)和召回率(recall rate),准确率也叫查准率,召回率也叫查全率.这些概念 ...

  8. Algorithm参数记录

    一.vector<Point2f> vector是一个存储二维点坐标的容器,其中每个元素都是一个Point2f类型的对象.在OpenCV中,Point2f表示一个由两个单精度浮点数构成的二 ...

  9. pandas之时间操作

    顾名思义,时间序列(time series),就是由时间构成的序列,它指的是在一定时间内按照时间顺序测量的某个变量的取值序列,比如一天内的温度会随时间而发生变化,或者股票的价格会随着时间不断的波动,这 ...

  10. [Maven]Maven聚合工程

    一直对此问题好奇,正好有这兴致和时间,有必要了解一下. 所谓聚合项目,实际上就是对项目分模块. 互联网项目一般来说按照业务分(订单模块.VIP模块.支付模块.CMS模块-): 传统的软件项目,大多采用 ...