将一个建模为零或一个关系(Z基数)

时间:2010-02-05 16:52:09

标签: nhibernate oop nhibernate-mapping

我正在努力寻找建立1:0,1关系(“可能有一个”或“最多只有一个”)的最佳方式。我相信这被称为Z基数。

例如,假设我有两个类WidgetWidgetTest。并非所有小部件都经过测试且测试具有破坏性,因此每个小部件最多只能有一个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);
            }
         }
     }
}

4 个答案:

答案 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

  • 将表格和地图连接为组件
  • 忽略依赖类的ID

答案 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>

增强的灵感:

http://www.onkarjoshi.com/blog/188/hibernateexception-a-collection-with-cascade-all-delete-orphan-was-no-longer-referenced-by-the-owning-entity-instance/comment-page-1/