DDD - 更新值对象的嵌套集合抛出 NHibernate 异常
DDD - updating nested collection of value objects throws NHibernate exception
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
}
我有一个相应的映射 class 使用代码映射:
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 的属性映射到实体 table,但 ValueObjectA.ValueObjectB 的属性映射到另一个 table,因为它是一对多关系。删除 ValueObjectB 后,我希望在 ValueObjectB table.
中删除该行
由于值对象是 immutable,当我更改 entity.ValueObjectA 的属性时,我应该创建一个新的 ValueObjectA 实例。问题是 ValueObjectBs 的集合是引用类型,所以当我尝试用不同的 ValueObjectA 保存实体时,NHibernate 会抛出异常,因为 NHibernate 跟踪的原始集合不再被引用:
A collection with cascade="all-delete-orphan" was no longer referenced
by the owning entity instance.
考虑以下代码:
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 原则的事情?
你有一个 anemic domain model。
您应该用Ubiquitous language
中具有有意义名称的方法替换实体的public设置器,检查不变量并执行所有在值对象替换的情况下进行必要的清理。
尽管事情看起来更复杂,但事实是现在该实体完全控制了其 内部发生的事情。你现在已经完全封装了。
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
}
我有一个相应的映射 class 使用代码映射:
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 的属性映射到实体 table,但 ValueObjectA.ValueObjectB 的属性映射到另一个 table,因为它是一对多关系。删除 ValueObjectB 后,我希望在 ValueObjectB table.
中删除该行由于值对象是 immutable,当我更改 entity.ValueObjectA 的属性时,我应该创建一个新的 ValueObjectA 实例。问题是 ValueObjectBs 的集合是引用类型,所以当我尝试用不同的 ValueObjectA 保存实体时,NHibernate 会抛出异常,因为 NHibernate 跟踪的原始集合不再被引用:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance.
考虑以下代码:
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 原则的事情?
你有一个 anemic domain model。
您应该用Ubiquitous language
中具有有意义名称的方法替换实体的public设置器,检查不变量并执行所有在值对象替换的情况下进行必要的清理。
尽管事情看起来更复杂,但事实是现在该实体完全控制了其 内部发生的事情。你现在已经完全封装了。