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

C# 将不可变对象持久化到关系数据库,c#,domain-driven-design,relational-database,immutability,C#,Domain Driven Design,Relational Database,Immutability,我看到一些面向对象专家建议域对象(POCO)应该是不可变的 也就是说,它们的状态应该在构造时完全确定,对状态的更改应该需要创建一个全新的实例 但是,假设我使用ORM持久化到关系数据库——我如何确定要持久化的属性,以及如何基于数据库中的数据重建域对象 例如,假设我想将此对象写入数据库: class User { private string _emailAddress; public User(string emailAddress, string password) {

我看到一些面向对象专家建议域对象(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
    }
}
在上面的示例中,我希望存储对象的状态,并在以后检索它。这意味着存储PasswordHash、PasswordSeed和EmailAddress

但是班级的结构阻止了我这么做

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

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

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

看,我想将密码存储为散列/种子,并出于某种原因隐藏电子邮件地址。我想在我的域对象中强制执行某个工作流

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


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


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

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


下面是一个基本的示例:私有字段也可以序列化-请参阅

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

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

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

编辑-根据评论:


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

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

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

例如,NHibernate利用代理库,像是用动态创建的类对映射的每个类进行静默子类化。为此,它为每个方法附加处理程序,这些方法检查状态是否已被修改,并触发脏实体的持久更改


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

我建议您将域层与数据层分离。换言之:

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

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

    我不确定我是否理解这个问题。。。哪些属性已计算,哪些需要存储?对不起,我不擅长解释这些东西:P我更新了问题。现在清楚一点了吗?是的-看看我的答案。。。如果您需要进一步的信息/想法,只需评论:-)NHibernate。可以分配私有/受保护的属性/字段。编写另一个构造函数有什么问题?有趣的解决方案。安全性不会成为问题,因为密码不会被存储,但会在构建对象时立即散列。但是,此解决方案适用于二进制流,而不是关系存储。我正在寻找一种持久化到关系数据库(例如SQL Server)的方法,如果