Моделирование отношений от одного до нуля или одного (Z-кардинальность) - PullRequest
8 голосов
/ 05 февраля 2010

Я изо всех сил пытаюсь найти лучший способ смоделировать отношения 1: 0,1 («может иметь один» или «имеет максимум один»). Я считаю, что это называется Z кардинальности.

Например, предположим, у меня есть два класса Widget и WidgetTest. Не все виджеты проверены, и этот тест разрушительный, поэтому на каждый виджет может быть не более одного виджета. Также предположим, что неуместно добавлять поля WidgetTest в Widget.

Мне бы хотелось, чтобы мой публичный интерфейс был:

Widget
    WidgetTest { get; set; }

WidgetTest
    Widget { get; }

Модель 1: Widget имеет свойство WidgetTest, а в базе данных таблица Widget имеет уникально ограниченный внешний ключ WidgetTest. Мой администратор БД утверждает, что это позволило бы существовать записи WidgetTest без Widget.

WidgetTable
    WidgetTestId (FK, UQ)

Модель 2: Widget имеет частную коллекцию WidgetTest и применяет отношение 0,1, добавляя или удаляя отдельный объект из коллекции, контролируемой общедоступным свойством WidgetTest. База данных моделирует это как 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 ]

4 голосов
/ 06 февраля 2010

Мой подход состоял в том, чтобы смоделировать отношения один-ко-многим в отображениях, но ограничить «многие» одним элементом. Это позволяет использовать один-к-одному, а также гарантирует, что ваш экземпляр 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 голос
/ 05 февраля 2010

Когда вы говорите «предположим, что неуместно добавлять поля 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 станет объектом значения, который вы можете или не можете хотеть.

0 голосов
/ 13 сентября 2016

Ответ , данный 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/

0 голосов
/ 02 февраля 2012

У меня есть еще 2 идеи здесь

  • Присоединиться к таблице и отобразить как Компонент
  • игнорировать идентификатор зависимого класса
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...