我正在努力寻找建立1:0,1关系(“可能有一个”或“最多只有一个”)的最佳方式。我相信这被称为Z基数。
例如,假设我有两个类Widget
和WidgetTest
。并非所有小部件都经过测试且测试具有破坏性,因此每个小部件最多只能有一个WidgetTest。还假设将WidgetTest字段添加到Widget是不合适的。
我希望我的公共界面是:
Widget
WidgetTest { get; set; }
WidgetTest
Widget { get; }
模型1:Widget具有WidgetTest属性,并且在数据库中Widget表具有WidgetTest的唯一约束外键。我的DBA辩称,这将允许WidgetTest记录在没有Widget的情况下存在。
WidgetTable
WidgetTestId (FK, UQ)
模型2:Widget具有WidgetTest的私有集合,并通过在公共WidgetTest属性控制的集合中添加或删除单个对象来强制执行0,1关系。数据库将此模型设置为1:m,WidgetTest具有对Widget唯一约束的外键。我认为这意味着采用该模型来适应数据库模式(即对我来说更多的工作)。
WidgetTestTable
WidgetId (FK, UQ)
哪种型号更好?使用NHibernate更容易实现哪些?还是有第三种方式?
编辑......以下是我最终的结果:
public class Widget
{
// This is mapped in NH using a access strategy
private IList<WidgetTest> _widgetTests = new List<WidgetTest>(1);
public WidgetTest
{
get { return _widgetTests.FirstOrDefault(); }
set
{
_widgetTests.Clear();
if (value != null)
{
_widgetTests.Add(value);
}
}
}
}
答案 0 :(得分:4)
我的方法是在映射中建模一对多关系,但是将“许多”约束到单个项目。这允许可选的一对一,并且还保证在保存Widget时保持WidgetTest实例的持久性。例如:
public class Widget
{
/// <summary>
/// This property is ignored by the NHibernate mappings.
/// </summary>
public virtual WidgetTest WidgetTest { get; set; }
/// <summary>
/// For easier persistence with NHibernate, this property repackages the
/// WidgetTest property as a list containing a single item. If an
/// attempt is made to set this property to a list containing more than
/// one item, an exception will be thrown. But why bother? Just use the
/// WidgetTest property.
/// </summary>
public virtual IList<WidgetTest> WidgetTests
{
get
{
IList<WidgetTest> widgetTests = new List<WidgetTest>();
if (this.WidgetTest != null)
{
widgetTests.Add(this.WidgetTest);
}
return widgetTests;
}
set
{
if (value != null && value.Count > 1)
{
throw new Exception("The WidgetTests collection may not contain more than one item.");
}
else if (value != null && value.Count == 1)
{
this.WidgetTest = value[0];
}
else
{
this.WidgetTest = null;
}
}
}
}
答案 1 :(得分:1)
当您说“假设将WidgetTest字段添加到Widget”不合适时,您的意思是在您的域对象或数据库中。如果您对数据库中同一个表中的字段感到满意,那么如何将WidgetTest映射为Widget的一个组件?让NHibernate映射文件看起来像:
<class name="Widget" table="Widget">
...
<property name="WidgetProperty"/>
...
<component name="WidgetTest" class="WidgetTest">
<property name="WidgetTestProperty"/>
</component>
</class>
给表结构:
WidgetTable
WidgetProperty
WidgetTestProperty
这仍然会让你拥有你指定的公共接口,但是,WidgetTest将成为你可能想要或不想要的值对象。
答案 2 :(得分:0)
我还有其他2个想法here
答案 3 :(得分:0)
answer given by nw.可能会导致异常“A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance
”。
如果您在映射文件中使用inverse="true"
和cascade="all-delete-orphan"
,则会发现这种情况。
这是因为nw。的答案每次调用get
访问器时都会创建一个新列表,并且不会对通过set
访问器传入的列表执行任何操作。因此,NHibernate在创建对象时没有最初传入的IList<WidgetTest>
引用,并且无法继续进行级联。
因此,为了解决这个问题,我们需要对IList<WidgetTest>
引用做一些事情,并注意不要去引用它。
public class Widget
{
public Widget()
{
_widgetTests = new List<WidgetTest>();
}
/// <summary>
/// This property is ignored by the NHibernate mappings.
/// </summary>
public WidgetTest WidgetTest { get; set; }
/// <summary>
/// For easier persistence with NHibernate, this property repackages the
/// WidgetTest property as a list containing a single item. If an
/// attempt is made to set this property to a list containing more than
/// one item, an exception will be thrown. But why bother? Just use the
/// WidgetTest property.
/// </summary>
private IList<WidgetTest> _widgetTests;
protected virtual IList<WidgetTest> WidgetTests
{
get
{
if (_widgetTests.Count == 0 && WidgetTest != null)
{
_widgetTests.Add(WidgetTest);
}
else if (_widgetTests.Count > 0 && WidgetTest == null)
{
_widgetTests.Clear();
}
else if (_widgetTests.Count > 0 && WidgetTest != _widgetTests[0])
{
_widgetTests.Clear();
_widgetTests.Add(WidgetTest);
}
return _widgetTests;
}
set
{
if (value != null && value.Count > 1)
{
throw new Exception("The WidgetTest collection may not contain more than one item.");
}
if (value != null && value.Count == 1)
{
WidgetTest = value[0];
}
else
{
WidgetTest = null;
}
//Store the reference
_widgetTests = value;
}
}
}
映射:
<class name="Widget" table="widgets">
...
<id name="Id" type="Guid" column="widgetId">
...
</id>
...
<bag name="WidgetTests" inverse="true" cascade="all-delete-orphan" access="property">
...
<key column="widgetId" />
<one-to-many class="WidgetTest" />
</bag>
</class>
增强的灵感: