使用实体框架和代码优先策略进行封装的最佳实践是什么



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

我在.NET6中使用实体框架的代码优先策略。

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;}
}

或者有更好的方法吗?

谢谢!

这完全是个人偏好,并处理您期望遇到的场景以及您希望如何保护或简化它们。

一般来说,我个人主张简单。一个易于理解的简单领域对其他开发人员和消费者来说很容易上手或接受指导。通常,做出这些决定是为了试图限制开发人员,从而孤立域,例如,UI开发人员要么无法直接修改数据,要么试图严格控制访问。这在非常大的项目/团队中可能是必要的;"守门员";可以保持定期和一致的更新,这样每个人都可以做需要做的事情,但通常由于时间限制或责任易手(看门人离开并被其他不理解或不同意的人回填),绕过不可避免地泄漏到模型中,只会导致混乱和不必要的复杂混乱。

当涉及到域时,我通常遵循一种更基于DDD的方法,类似于您的第一个示例,只是我只使用那些我认为存在实体可以强制执行的验证或特定状态组合的方法。像这样的mutator方法的责任要么落在实体上,要么落在存储库上。(我通常使用存储库模式)

对于一个可以更改并且可能有简单验证或根本没有验证的值,我将只使用公共setter。无验证:

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的情况下更改街道名称,这样可能会有公共设置器。城市和国家可能是FK值(CityId/CountryId),因此更新这些独立性的需要更少。简单地让这个方法保持值的设置应该会向开发人员发出一个明确的信息,即他们应该确保立即发送完整有效的地址详细信息,而不是依赖于他们正确地链接零碎的更新。

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

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)可以调用,在API中,DB上的唯一约束等作为最终保护。

类似地,当涉及到创建实体时,我将在Repository类中使用与上面类似的工厂方法来执行CreateAddress(…)之类的操作,以确保地址实体不会简单地新建和临时填充。这确保了在创建实体时,所有必需的字段&提供并填充关系。这种方法的目的是帮助确保从一个实体被创建的那一刻起,在其突变的每一点上,它都处于有效和完整的状态。

希望这能给你一些思考的食物。不过,最终你应该看看什么对你的特定场景很重要,以及你想解决什么真实和实际的问题。不要过于沉迷于试图避开假设的最坏情况,最终会遇到一些过于僵化的事情,从而对您的编码响应产生负面影响。

最新更新