我有一个模型类,我希望 Id 是只读的,我希望字符串属性有一个支持字段(应用 Trim 来设置值)。这样做的正确方法是什么?
public class DisallowedEmail
{
private string _email;
public int Id { get; }
public required string Email
{
get => _email;
set => _email = value.Trim();
}
private DisallowedEmail(int id, string email)
{
Id = id;
_email = email;
}
public DisallowedEmail()
{
_email = string.Empty;
}
}
或...
public class DisallowedEmail
{
private string _email;
public int Id { get; private set; }
public required string Email
{
get => _email;
set => _email = value.Trim();
}
}
我怀疑第一个不会工作,因为 Id 需要一个 setter 来设置构造函数。然而,这就是说我决定测试它并且在它编译时,当我尝试下面的示例时它实际上不起作用,它确实与私有 setter 一起工作。 (它抱怨没有找到合适的构造函数,即使构造函数没有改变)
EF Core 支持使用构造函数参数,并将发现/使用私有构造函数,因此您可以施加更多控制以确保实体在有效状态下构造。
例如,EF 在构造实体时需要一个构造函数或公共/受保护的属性设置器来填充数据库中的状态。您可能需要一个公共或内部(如果使用工厂模式)构造函数来初始化新实例的代码,以确保有效和完整的状态。
假设您有一个客户实体,客户有一组必填字段,包括账单地址(实体)以及一些可选字段:
public class Customer
{
[Key]
public int CustomerId { get; private set; } = 0;
public string Name { get; private set; }
public string? PreferredName { get; set; } = null;
public virtual Address BillingAddress { get; protected set; }
// Constructor EF will use.
protected Customer(int customerId, string name)
{
CustomerId = customerId;
Name = name;
}
// Constructor consumers or factories will use. (could be internal for factory use)
public Customer(string name, Address billingAddress)
{
Name = name;
BillingAddress = billingAddress;
}
}
EF 构造函数可以是
private
,但前提是您不启用代理。否则它需要 protected
老实说它受到足够的 IMO 保护和更安全的选择。
这里的区别在于,虽然 EF 可以使用构造函数来初始化实体,但它不能使用引用导航属性的构造函数。对于我们新客户的公共设置,我们可能希望确保代码提供帐单地址,因为这是必填字段。我们还可以保护在验证它的方法后面更改帐单地址,或执行一些其他操作,如更新更改历史记录等,而不是简单的 setter。我们不希望消费者看到 EF 所做的版本,因为他们没有新客户的 ID,我们也不应该鼓励“hacky”行为,例如填充和附加假定代表实时数据的客户。
对于您的具体用例,最简单的方法是有一个用于创建实体的构造函数,在此期间您修剪电子邮件地址,以及 EntityFramework 在从数据库读取实体时将用于创建实体的第二个构造函数。
public class DisallowedEmail
{
// Properties
public int Id { get; private set; }
public string Address { get; private set; }
// Constructors
public DisallowedEmail(string address)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentNullException(nameof(address));
}
Address = address.Trim();
}
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
/// <summary>Constructor for EntityFrameworkCore.</summary>
/// <remarks>EntityFrameworkCore chooses the constructor with the least amount of parameters (including 0) where all of them correspond to a property.</remarks>
private DisallowedEmail() { }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
}
此外,如果您想要一些适当的 DotNet 电子邮件地址验证和清理,您可以使用我的扩展方法
string.ToEmailAddress()
通过复制它或使用包含它的my Nuget 包(它是纯 C# 而不是有任何依赖关系):
using AndrejKrizan.DotNet.Extensions;
namespace Domain.Entities
{
public class DisallowedEmail
{
// Properties
public int Id { get; private set; }
public string Address { get; private set; }
// Constructors
public DisallowedEmail(string address)
{
Address = address.ToEmailAddress();
}
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
/// <summary>Constructor for EntityFrameworkCore.</summary>
/// <remarks>EntityFrameworkCore chooses the constructor with the least amount of parameters (including 0) where all of them correspond to a property.</remarks>
private DisallowedEmail() { }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
}
}