看看这个实体:
class EntityA
{
public int Id { get;set; }
public string Name { get;set; }
public int? ClientId { get; set; }
// Navigation properties:
public ClientEntity? Client { get; set; }
}
如您所见,该实体包含一个可选属性:ClientId。这意味着客户端是可选的。在这种情况下,sql server 数据库中的 ClientId 字段将包含 NULL。
我正在使用外键的导航属性:这是“客户端”属性。当 ClientId 为 null 时,Client 也应该为 null。
这就是为什么我声明:“ClientEntity?”键入 Client 属性。
但我看到有人在相同的情况下声明“ClientEntity”(不可为空)。 但我不明白他们如何在这种情况下操纵空客户端......
有什么想法吗?
谢谢
将这个留给在为项目启用可空性时也遇到此问题的任何人,导航属性不应该为空。
根据微软,
集合导航包含对多个相关实体的引用,应始终不可为空。空集合意味着不存在相关实体,但列表本身不应该为空。
https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types
所以编码的解决方案是:
class EntityA
{
public int Id { get;set; }
public string Name { get;set; }
public int ClientId { get; set; }
// Navigation properties:
public ClientEntity Client { get; set; } = null!;
}
这个很好的问题缺少一个很好的答案,所以对于任何来到这里寻求指导的人......
让我们想象一个具有一些基本属性
Customer
和 FullName
的实体 Age
。 FullName
必须提供,Age
是可选的。
每个客户**必须有一个地址并且可以有一个折扣代码,这两个重要实体存储在同一个数据库中,但存储在不同的表中
此外,
Customer
实体在数据库中对于Order
s具有1:N关系。
所以...我们基本上拥有所有可能遇到的选择。
public class Customer
{
// In DB, set as mandatory, but EF take care about this.
// By default, initiated to 0
public int Id {get; set;}
// In DB, set as mandatory, we could take care about this,
// or set it up in DB as auto value.
// By default initiated on Guid.Empty.
public Guid UniqueId {get; set;}
// In DB, set as mandatory.
public string FullName {get; set;}
// In Db, set as optional (can have null).
public int Age {get; set;}
// In Db, set as mandatory.
public int AddressId {get; set;}
public Address Address {get; set;}
// In Db, set as optional relationship (it might not exists).
public int DiscountCodeId {get; set;}
public DiscountCode DiscountCode {get; set;}
public ICollection<Order> Orders {get; set;}
}
首先,您必须区分可以由某些基本类型(
int
、double
、datetime
、string
等)表示的基本值和表示导航的任何非基本对象财产。
其次,你必须设定你想要遵循的标准,要么是防御性的,要么是乐观的。
以下是 .NET 8 和 C# 12 两种案例的展示。
public class Customer
{
// In DB, set as mandatory.
public int Id {get; set;}
// In DB, set as mandatory.
public Guid UniqueId {get; set;}
// In DB, set as mandatory.
public required string FullName {get; set;}
// In Db, set as optional (can have null).
public int? Age {get; set;}
// In Db, set as mandatory.
public required int AddressId {get; set;}
public Address? Address {get; set;}
// In Db, set as optional relationship.
public int? DiscountCodeId {get; set;}
public DiscountCode? DiscountCode {get; set;}
public ICollection<Order> Orders {get; set;} = [];
}
FullName
被标记为 required
,因为它是 DB 所要求的。
AddressId
可以标记为必填,但这取决于情况。每次您想要创建 Customer
实例时都需要它,这在编写测试时可能会很乏味。一般来说,我会根据需要进行设置,并使用构建器模式在测试中创建Customer
。”
Address
设置为可为空,因为必须加载 Address
才能成为 null
。在防御方法中,我们希望在使用时强制进行 null
检查,因为它不需要加载。
Age
设置为可为空,因为它不是必需的,并且可能是 null
。
DiscountCodeId
设置为可为空,因为它不是必需的,并且可能是 null
。
DiscountCode
被设置为可为空,因为整个关系可为空,因此当 DiscountCodeId
为 null
时,DiscountCode
必须相应地为 null
。
Orders
未标记为 null
,因为如果给定客户没有订单,则应仅通过空集合来指示。因此,我们只需使用空列表来初始化该属性。如果有任何订单,EF Core 将使用订单集合覆盖此设置。
此设置将生成不可为空引用类型警告。
public class Customer
{
// In DB, set as mandatory.
public int Id {get; set;}
// In DB, set as mandatory.
public Guid UniqueId {get; set;}
// In DB, set as mandatory.
public required string FullName {get; set;}
// In Db, set as optional (can have null).
public int? Age {get; set;}
// In Db, set as mandatory.
public int AddressId {get; set;}
public Address Address {get; set;} = null!
// In Db, set as optional relationship.
public int? DiscountCodeId {get; set;}
public DiscountCode? DiscountCode {get; set;}
public ICollection<Order> Orders {get; set;} = [];
}
唯一的区别在于导航属性。所有其他情况保持不变。在这种方法中,我们期望自动加载
Address
。
AddressId
未按要求标记,开发者负责在创建该类的新对象时正确设置该值。
Address
设置为不可空,这会在实例初始化后触发有关可能为空值的编译器警告。这是对的。该警告被空宽恕运算符抑制 = null!
。