【问题标题】:DDD - updating nested collection of value objects throws NHibernate exceptionDDD - 更新值对象的嵌套集合会引发 NHibernate 异常
【发布时间】:2017-09-20 01:17:35
【问题描述】:

TLDR 版本:我无法让我的 DDD 域模型与 NHibernate 一起使用。如果我的值对象本身包含一个值对象的集合,我无法分配一个新值而不得到 NHibernate 异常,并且想知道在这种情况下的最佳做法是什么。

加长版:

假设我有一个实体,它包含一个值对象作为属性,ValueObjectA,它本身包含一组不同的 ValueObjectB 类型的值对象。

ValueObjectB 仅作为 ValueObjectA 的属性有意义地存在,即如果 myEntity.ValueObjectA == null,则 ValueObjectB 的存在也没有意义。

为了简洁起见,我编写了一些示例代码来说明我的意思。

public class Entity
{
    public int Id { get; private set; }
    public ValueObjectA ValueObjectA { get; set; }

    // Constructor: public Entity(ValueObjectA valueObjectA)
}

public class ValueObjectA : IEquatable<ValueObjectA>
{
    public string X { get; private set; }
    public ISet<ValueObjectB> ValueObjectBs { get; private set; }

    // Constructor: public ValueObjectA(string x, ISet<ValueObjectB> valueObjectBs)
    // Implementation of Equals/GetHahcode
}

public class ValueObjectB : IEquatable<ValueObjectB>
{
    public int Y { get; private set; }
    public int Z { get; private set; }

    // Constructor: public ValueObjectB(int y, int z)
    // Implementation of Equals/GetHahcode
}

我有一个使用代码映射的对应映射类:

public class EntityMap : ClassMapping<Entity>
{
    public EntityMap()
    {
        Table("Entity");
        Id(x => x.Id, map => map.Generator(Generators.Identity));

        Component(x => x.ValueObjectA, c =>
        {
            c.Property(x => x.X);

            // Component relation is equilavent to <composite-element> in xml mappings
            c.Set(x => x.ValueObjectBs, map =>
            {
                map.Table("ValueObjectB");
                map.Inverse(true);
                map.Cascade(Cascade.All | Cascade.DeleteOrphans);
                map.Key(k => k.Column("Id"));
            }, r => r.Component(ce =>
            {
                ce.Property(x => x.Y);
                ce.Property(x => x.Z);
            }));
        });
    }
}

ValueObjectA 的属性映射到实体表,但 ValueObjectA.ValueObjectB 的属性映射到另一个表,因为它是一对多的关系。当 ValueObjectB 被删除时,我希望在 ValueObjectB 表中删除该行。

由于值对象是不可变的,所以当我更改 entity.ValueObjectA 的属性时,我应该创建一个新的 ValueObjectA 实例。问题是 ValueObjectBs 的集合是引用类型,所以当我尝试用不同的 ValueObjectA 保存实体时,NHibernate 会抛出异常,因为 NHibernate 正在跟踪的原始集合不再被引用:

不再引用具有 cascade="all-delete-orphan" 的集合 由拥有实体实例。

考虑以下代码:

        var valueObjectBs_1 = new HashSet<ValueObjectB>
        {
            new ValueObjectB(1, 2),
            new ValueObjectB(3, 4)
        };

        var valueObjectA_1 = new ValueObjectA("first", valueObjectBs_1);

        var entity = new Entity(valueObjectA_1);

        // Save entity, reload entity

        var valueObjectBs_2 = new HashSet<ValueObjectB>
        {
            new ValueObjectB(1, 2)
        };

        var valueObjectA_2 = new ValueObjectA("second", valueObjectBs_2);

        entity.ValueObjectA = valueObjectA_2;

        // Save entity again
        // NHIBERNATE EXCEPTION

我已经设法通过创建另一个 ValueObjectA 来解决这个问题,以便保留对集合的引用,例如

        valueObjectA_1.ValueObjectBs.Remove(new ValueObjectB(3, 4));
        entity.ValueObjectA = new ValueObjectA(valueObjectA_2.X, valueObjectA_1.ValueObjectBs);

但是……这感觉就像是代码异味——即使我为 Entity.ValueObjectA 编写了一个自定义设置器,在设计本应简单的地方,实现也开始变得复杂。

public class Entity
{
    // ...
    private ValueObjectA valueObjectA;
    public ValueObjectA ValueObjectA
    {
        // get
        set
        {
            // Add/Remove relevant values from ValueObjectA.ValueObjectBs
            valueObjectA = new ValueObjectA(value.X, ValueObjectA.ValueObjectBs);
        }
    }
}

在这种情况下,最佳做法是什么?或者这是否表明我正在尝试做一些违反 DDD 原则的事情?

【问题讨论】:

  • 你很幸运 NHibernate 对你大喊大叫,否则你可能已经摆脱了那个设计一段时间,然后它就会再次闻起来

标签: nhibernate collections domain-driven-design value-objects


【解决方案1】:

您拥有的是 anemic domain model

您应该将实体的公共设置器替换为具有来自Ubiquitous language 的有意义名称的方法,以检查不变量并在值对象替换的情况下进行所有必要的清理。 p>

虽然看起来事情可能更复杂,但现在实体完全控制其内部发生的事情这一事实得到了回报。你现在已经完全封装了。

【讨论】:

  • 谢谢。我知道贫血的域模型。我的实际领域模型使用通用语言,并在实际对象中维护业务逻辑。 (我很欣赏提供的代码是贫乏的,但我想简化它以简化它以说明 NHibernate 异常 - 否则,它将是 TLDR)。
  • 我担心的是,当值对象中有引用类型时,很难保持简单。假设我有一个值对象、地址和实体 Person 和 Order,它们分别有一个帐单地址和一个送货地址。如果我有定制的逻辑来更新地址,例如因为它包含值对象的集合,就像在原始示例中一样,我必须在两个实体中放置相同的逻辑,或者引入一个额外的映射/构建器类来保持它的 DRY。尽管概念设计可能很简单,但感觉实现变得越来越复杂。那是代码味道吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-28
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
相关资源
最近更新 更多