DDD - 更新值对象的嵌套集合会抛出NHibernate异常

时间:2017-04-23 10:09:44

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

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的属性映射到Entity表,但ValueObjectA.ValueObjectB的属性映射到另一个表,因为它是一对多关系。删除ValueObjectB时,我希望在ValueObjectB表中删除该行。

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

  

cascade =&#34; all-delete-orphan&#34;不再被引用   由拥有实体实例。

请考虑以下代码:

        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编写了一个自定义setter,但实现开始变得复杂,设计应该很简单。

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原则的事情?

1 个答案:

答案 0 :(得分:0)

你拥有的是 anemic domain model

您应该使用Ubiquitous language中具有有意义名称的方法替换实体的公共setter ,检查不变量,并在值对象替换时执行所有必要的清理。

虽然看起来事情看起来更复杂,但实际上现在该实体完全控制了 internals 会发生的事情。你现在已经完全封装了。