[學習筆記]解決因C#8.0的語言特性導致EFCore實體型別對映的錯誤

2023-05-18 21:00:50

今天下午在排查一個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,好值得的一個下午呢