升級 .NET 6 踩到的小問題。

依之前學到的 EF Core Model 設計,string 屬性預設對映的欄位預設為 Nullable,標註 [Required] 才會宣告為 NOT NULL。 不過,這條規則到 .NET 6 已有所改變。某段 EF Core 寫入資料庫時冒出欄位不允許 NULL,但 Model 中該屬性並未宣告為 [Required]。

研究發現這與 .NET 6 啟用 Nullable Context 有關,csproj 多了 <Nullable>enable</Nullable> 設定以支援 C# 8 推出的 Nullable Reference Type 概念。 設為 enable 時,Compiler 啟用 Null Reference Analysis 及相關語言特性,以字串為例,若 string 沒宣告成 string? 卻可能為 null 時會得到警告;若要明確標示此處就是要設成 null,可在後方加上 Null-Forgiving Operator, 例如 string x = null!;

若不想啟用此特性,設成 disable,Compiler 即會恢復 C# 7.3 以前的行為。

EF Core 產生資料庫對映 SQL Schema 時,也會受 Nullable Context 影響,當 <Nullable>enable</Nullable>,即使未加 [Required],Model 的字串屬性仍會被視為不可為 null,在 CREATE TABLE 時會加上 NOT NULL。

用以下程式重現問題。簡單宣告了 Entity 型別、DbContext,其中 RequiredText 有加註 [Required],另一個 OptionalText 則沒有,呼叫 DbContext.DataBase.GenerateCreateScript() 檢視其對映的 SQL Schema:

using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;

var options = 
    new DbContextOptionsBuilder<MyContext>()
    .UseSqlServer("data source=(localdb)\\mssqllocaldb")
    .Options;
var dbCtx = new MyContext(options);
Console.WriteLine(dbCtx.Database.GenerateCreateScript());

class MyContext : DbContext
{
    public DbSet<Item> Items { get; set; } = null!;
    public MyContext(DbContextOptions<MyContext> options)
        : base(options) { }
}
public class Item //Entity 型別
{
    //慣例,屬性名稱為 Id 或 <type name>Id 會自動成為 Entity 的 Key
    public int ItemId { get; set; }
    [Required] 
    public string RequiredText { get; set; } = null!;
    public string OptionalText { get; set; } = null!;
}

如下圖所示,當 <Nullable>enable</Nullable> 時,OptionalText 也會被加上 NOT NULL,換成 disable 才會恢復之前的規則。

所以,.NET 6 啟用 Nullable Context 時,Model 字串屬性要允許 null,型別也需改成 string?,這樣才會對映成 Nullable 資料庫欄位。(註:RequiredText 故意拿掉 = null! 觸發 CS8618 警告,證明有設 <Nullable>enable</Nullable>)

.NET 6 comes with Nullable Context, so string property without [Required] will be now NOT NULL in SQL. You need change it as string? to be nullable.


Comments

# by 小黑

謝黑哥

Post a comment