使用 Entity Framework 和 Code First 策略封装的最佳实践是什么?

What is the best practice for encapsulation with Entity Framework and Code First strategy?

关于封装的最佳实践是什么?

我在 .NET 6 中使用代码优先策略 Entity Framework。

public class Employee
{
    [Required]
    public string Name { get; private set; }

    public void SetName(string value)
    {
        this.Name = value;
    }
}

public class Employee
{
    private string _name;        

    [Required]
    public string Name {
        get { return _name; }
        set { _name = value; } 
    }
}

public class Employee
{
   [Required]
   public string Name {get;set;}
}

或者有更好的方法吗?

谢谢!

这完全是个人喜好,并根据您预计会遇到的情况以及您希望如何保护或简化它们。

作为一般规则,我个人提倡简单。易于理解的简单域对于其他开发人员和消费者来说很容易上手或以其他方式接受指导。通常做出这些决定是为了尝试限制开发人员 so-as 孤立域,这样 UI 开发人员要么不能直接修改数据,要么尝试严格控制访问。这在非常大的 projects/teams 中可能是必要的,并且可以工作,前提是您的“看门人”可以保持定期和一致的更新,以便每个人都可以做需要做的事情,但通常是由于时间限制或责任易手(看门人离开并被不理解或不同意的其他人回填)然后绕过不可避免地泄漏到模型中只会导致混乱和不必要的复杂混乱。

当涉及到域时,我通常会遵循与您的第一个示例类似的更 DDD-based 方法,除了我只使用我希望存在实体的验证或特定状态组合的方法可以强制执行自己。像这样的增变器方法的责任要么落在实体上,要么落在存储库上。 (因为我通常使用存储库模式)

对于可以更改并且可能具有简单验证或 none 的值,我将只使用 public setters。无验证:

 public string SomeValue { get; set; }

用于基本验证,即实体可以使用 setter 中的属性或验证逻辑来​​验证自身:

private string _someValue;
public string SomeValue
{
   get { return _someValue; }
   set 
   {
       if (string.IsNullOrEmpty(value)) throw new ArgumentException("SomeValue is not optional."); 
       _someValue = value;
   }
}

通常,状态更新涉及更改不止一件事,其中数据组合应该针对实体状态的当前其余部分一起进行验证。我们不想一次设置一个值,因为这意味着实体状态可能处于无效状态,或者不能保证调用者不会简单地设置一个值,而忽略其他值是技术上无效。作为该概念的一个非常粗略的示例,在不进行验证的情况下,它将更新地址。当然,我们可能希望对单个地址字段进行更正,但通常如果我们更改一个地址字段,我们很可能会使其余地址字段无效。例如,如果我的地址包含街道名称、号码、城市、邮政编码和国家/地区,仅更改城市或国家/地区通常会使地址完全无效。在这些情况下,我会使用 Setter 方法来封装更新地址:

public string Country { get; internal set; }
public string City { get; internal set; }
public string PostCode { get; internal set; }
public string StreetName { get; internal set; }
public string StreetNumber { get; set; }

public void UpdateAddress(string country, string city, string postCode, string streetName, string streetNumber)
{ // ...
}

允许他们自行更改街道号码,甚至可能不调用 UpdateAddress 更改街道名称可能会很好,因此这些可能有 public setters。城市和国家可能是 FK 值 (CityId/CountryId),因此更新这些独立性的需求就更少了。简单地使用此方法 gate-keep 值的设置应该向开发人员发送一个明确的消息,即他们应该确保立即发送完整和有效的地址详细信息,而不是依赖他们正确地链接零碎的更新。

在我可能想要针对现有数据状态验证更改的地方,我会使用内部 setter,并将更新方法作为存储库的一部分。例如,如果我想让他们更新名称,但要确保名称是唯一的。存储库可以访问该域,所以我发现它是履行此职责的好位置:

public void UpdateUserName(User user, string newName)
{
    if (user == null) throw new ArgumentNullException("user");
    if (string.IsNullOrEmpty(newName)) throw new ArgumentNullException("newName");
    if (user.Name == newName) return; // Nothing to do.

    var nameExists = _context.Users.Any(x => x.Name == newName && x.UserId != user.UserId);
    if (nameExists) throw new ArgumentException("The name is not unique.");
    user.Name = newName; // Allowed via the internal Setter.
}

如果这是在与 UI 对话,那么 UI 会在保存之前验证该名称是唯一的,但是持久性应该验证以防它可以被调用其他途径,如 API,其中诸如对数据库的唯一约束之类的东西充当最后的守卫。

类似地,当涉及到创建实体时,我将在存储库 类 中使用类似于上面的工厂方法来执行诸如 CreateAddress(...) 之类的操作,以确保不只是简单地更新地址实体起来并临时填充。这确保在创建实体时,提供并填写所有必需的字段和关系。这种方法的 objective 是为了帮助确保从创建实体的那一刻起,在通过其变异的每一点,它都处于有效和完整的状态。

希望这能让您对这个主题有所思考。最后,尽管您应该看看什么对您的特定场景很重要,以及您想要解决哪些真实和实际的问题。不要太执着于试图避开假设性的 worst-case 场景,并以死板的方式结束,以至于对您的编码响应能力产生负面影响。