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

本文关键字:关系数据库 持久化 对象 不可变 | 更新日期: 2023-09-27 18:04:46

我看到一些面向对象的专家建议域对象(poco)应该是不可变的。

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

但是,假设我使用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
    }
}
在上面的示例中,我希望存储对象的状态并稍后检索它。这意味着存储passworddhash, PasswordSeed和EmailAddress。

但是类的结构阻止我这样做。

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

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

是的,我可以让对象中的每个属性都是公共的并且可以读/写。但是,我的封装和不变性在哪里呢?

看,我想将密码存储为哈希/种子并隐藏电子邮件地址是有原因的。我想在我的领域对象中执行一个特定的工作流。

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


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

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

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

首先,域对象不可变的说法是错误的。
只有对象应该是不可变的。用户很可能是一个实体。

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

。NHibernate利用像LinFu这样的代理库,静默地将你映射的每个类子类化为动态创建的类。这样做,它为每个方法附加处理程序,检查状态是否已被修改,并触发脏实体的持久化更改。

NHibernate强制映射对象具有受保护的、参数较少的构造函数,所有的属性和方法都是可重写的。对象通过此构造函数创建为"空",并通过重写属性设置状态。

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

如果您想将值序列化到DB,并将它们读回来…
有几种方法可以做到这一点,例如:

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

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

编辑-按注释:

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

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

下面是一个基本示例:http://blog.kowalczyk.info/article/Serialization-in-C.html

我建议您将域层与数据层解耦。换句话说:

  1. 创建一个数据层类,它直接对应于你的User表的列,所有的公共getter/setter,一个无参数的构造函数,没有行为
  2. 创建一个域层类,它具有行为(即方法),没有公共setter(如果可能的话,为了避免贫血的域模型反模式)和最小的公共getter。
  3. 创建映射类在两者之间进行映射。
  4. (可选,但如果这是一个更大的应用程序。)确保只有域类(或理想情况下它的接口)是可访问的外部世界。映射器和数据类不是供公众使用的,所以不要公开它们。

问题解决了!编写所有的映射代码似乎是一件痛苦的事情(并且您需要对其进行测试,因为映射非常容易出错),但最终,它将解决试图用一个类实现server 2目的所带来的头痛。