将不可变对象持久保存到关系数据库

时间:2011-08-05 04:21:23

标签: c# domain-driven-design relational-database immutability

我见过某些面向对象的专家建议域对象(PO​​CO)应该是不可变的。

也就是说,他们的状态应该在构造时完全确定,并且对状态的更改应该需要创建一个全新的实例。

但是说我使用ORM坚持使用关系数据库 - 如何确定要保留哪些属性,以及如何根据数据库中的数据重建域对象?

例如,假设我想将此对象写入数据库:

class User
{
    private string _emailAddress;

    public User(string emailAddress, string password)
    {
       // ... generate hash & seed
       _emailAddress = emailAddress;
    }

    public string PasswordHash { get; private set; }

    public string PasswordSeed { get; private set; }

    public bool ValidatePassword(string password)
    {
       // validate it against the stored hash
    }
}

在上面的例子中,我想存储对象的状态并在以后检索它。这意味着存储PasswordHash,PasswordSeed和EmailAddress。

但是班级的结构使我无法这样做。

  • PasswordHash和PasswordSeed - 我可以存储它们,但是当我想从数据库中读取它们并重建对象时会发生什么?没有构造函数参数可以传递它们,我甚至无法构造没有它们的对象,因为必须提供“密码”,我不知道原始密码是什么。我无法设置'PasswordHash'或'PasswordSeed'属性,因为它们是'私有设置'。

  • EmailAddress - 我无法存储它,因为即使它是对象状态的一部分,它也被标记为私有,无法在内部访问。

是的,我可以将对象中的每个属性都公开并进行读/写。但那么,我的封装和不变性在哪里?

请参阅,我想将密码存储为哈希/种子并隐藏电子邮件地址。我想在我的域对象中强制执行某个工作流程。

但是当涉及到存储它们时,我必须撤消所有这些约束并将我的域对象转换为哑键/值存储,没有约束。


基本问题似乎是关系数据库没有封装概念。表中的每一列都是“公共”,每个值都是“可变的”。

这是否意味着如果我要拥有一个不可变的域模型,我必须完全抛弃关系映射?或者是否有一些众所周知的解决这个问题的策略,我还不知道呢?

4 个答案:

答案 0 :(得分:3)

首先 - 声明域对象应该是不可变的是错误的 只有对象应该是不可变的。用户最有可能是一个实体。

通常,ORM会使用一些技巧来解决这个问题。

E.g。 NHibernate利用像LinFu之类的代理库来静默子类化您使用动态创建的类映射的每个类。执行此操作时,它会为每个检查状态是否已被修改的方法附加处理程序,并触发脏实体的持续更改。

NHibernate强制映射对象具有受保护的,参数较少的构造函数,所有属性和方法都可以覆盖。通过此构造函数将对象创建为“空”,并通过覆盖属性设置状态。

答案 1 :(得分:1)

私人字段也可以序列化 - 请参阅http://msdn.microsoft.com/en-us/library/system.serializableattribute.aspx

在这种情况下,您希望将值序列化为数据库并将其读回...
有几种方法可以做到这一点,例如:

  • 实现2个静态方法作为类成员,可以访问所有内部/私有字段,从而序列化和重构类中的任何字段

  • 实现一些用于序列化/反序列化的接口,您可以将其用作静态方法的参数,以减少序列化/反序列化过程与类之间的依赖关系

编辑 - 根据评论:

在静态方法中,您可以自由选择要在DB中保留哪些字段以及如何(二进制,XML或每个字段等一列)。只需相应地实现序列化/反序列化接口。

答案 2 :(得分:0)

您可以阅读对象的序列化和反序列化,并将数据存储在数据库中(其中一些是特定于语言的,因此可能无法跨语言工作)。如果要存储所有字段。这将解决您的密码问题,但如果有人要获取访问权限并反序列化您的实例并获取明文密码(如果它存储在对象中),则会带来安全风险。

以下是如何执行此操作的基本示例:http://blog.kowalczyk.info/article/Serialization-in-C.html

答案 3 :(得分:0)

我建议您将域图层与数据层分离。换句话说:

  1. 创建一个数据图层类,它直接对应于您的User表的列,包含所有公共getter / setter,无参数构造函数,并且没有行为。
  2. 制作域图层类,其中包含行为(即方法),没有公共设置器(如果可能,为了避免Anemic Domain Model反模式),以及最小的公共getter 。
  3. 创建映射器类以在两者之间进行映射。
  4. (可选,但如果这是一个更大的应用程序,则很好。)确保外部世界只能访问域类(或理想情况下它的接口)。映射器和数据类不用于公共消费,因此不要暴露它们。
  5. 问题解决了!编写所有映射代码似乎很痛苦(并且您需要对其进行测试,因为映射非常容易出错)但最终,它将解决因使用一个类尝试服务器2目的而导致的头痛。 / p>