今天下午在排查一個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 的行為:
換而言之,啟用了該功能後,把原本《參照型別可為空》的這個傳統約定,更改稱為了《參照型別是否可為空,是通過?
語法來表明的》,實體中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,好值得的一個下午呢
本文來自部落格園,作者:林曉lx,轉載請註明原文連結:https://www.cnblogs.com/jevonsflash/p/17413053.html