Подтвердить что ты не робот

Поддержание двухсторонних отношений между классами

Очень часто, особенно в приложениях с ORM, есть двухстороннее сопоставление между классами. Вот так:

public class Product
{
    private List<Price> HistoricPrices { get; private set;}
}

public class Price
{
    private Product Product { get; set; }
}

Существует ли приемлемый способ поддержания этого отношения в коде? Итак, когда я добавляю цену к продукту, свойство Product автоматически устанавливается?

В идеале я ищу удобное многоразовое решение. Кажется неправильным добавить что-то в коллекцию, а затем установить противоположные отношения вручную.

Обратите внимание, что это не вопрос о том, как моделировать продукты и цены. Речь идет о том, как моделировать двусторонние отношения. Есть много ситуаций, когда это совершенно разумно.

4b9b3361

Ответ 1

Во-первых, я думаю, что пример, который ваш подарок сбивает с толку, - это редкость, когда что-то вроде цены моделируется как объект или имеет ссылку на сущности, которые будут иметь цену. Но я думаю, что вопрос правомерен - в мире ORM это иногда называют согласованностью графиков. Насколько мне известно, не существует однозначного способа решения этой проблемы, есть несколько способов.

Давайте начнем с небольшого изменения примера:

public class Product
{
    private Manufacturer Manufacturer { get; private set; }
}

public class Manufacturer
{
    private List<Product> Products { get; set; }
}

Таким образом, у каждого товара есть один производитель, и у каждого производителя может быть список товаров. Проблема с моделью заключается в том, что если класс Product и класс Manufacturer сохраняют несвязанные ссылки друг на друга, обновление одного может сделать недействительным другой.

Есть несколько способов решения этой проблемы:

  1. Устранить круговую ссылку. Это решает проблему, но делает объектную модель менее выразительной и более сложной для использования.

  2. Измените код таким образом, чтобы ссылка на производителя в списке "Продукция и продукты" в разделе "Производитель" была рефлексивной. Другими словами, изменение одного влияет на другое. Обычно для этого требуется некоторый код, который устанавливает и собирает коллекция, чтобы перехватывать изменения и отражать их друг в друге.

  3. Управляйте одним свойством с точки зрения другого. Таким образом, вместо того, чтобы хранить ссылку на производителя в Product, вы вычисляете ее путем поиска по всем производителям, пока не найдете того, кто владеет вами. И наоборот, вы можете сохранить ссылку на производителя в классе Product и динамически создавать список продуктов. При таком подходе вы обычно делаете одну сторону отношений доступной только для чтения. Между прочим, это стандартный подход к реляционной базе данных - сущности ссылаются друг на друга через внешний ключ, который управляется в одном месте.

  4. Выделите отношения из обоих классов и управляйте ими в отдельном объекте (часто называемом контекстом данных в ORM). Когда Product хочет вернуть своего производителя, он запрашивает DataContext. Когда производитель хочет вернуть список продуктов, он делает то же самое. Внутренне, есть много способов реализовать контекст данных, набор двунаправленных словарей не является редкостью.

Наконец, я упомяну, что вам следует рассмотреть возможность использования инструмента ORM (например, NHibernate или CSLA), который может помочь вам управлять согласованностью графиков. Как правило, эту проблему решить непросто, и она может стать очень сложной, если вы начнете изучать такие случаи, как отношения "многие ко многим", отношения "один к одному" и ленивая загрузка объектов. Вам лучше использовать существующую библиотеку или продукт, а не изобретать собственный механизм.

Вот некоторые ссылки, которые говорят о двунаправленных ассоциациях в NHibernate, которые вы можете найти полезными.

Вот пример кода для непосредственного управления отношениями с помощью метода № 2, который обычно является самым простым. Обратите внимание, что редактируется только одна сторона отношения (в данном случае - Производитель) - внешние потребители не могут напрямую устанавливать производителя продукта.

public class Product
{
    private Manufacturer m_manufacturer;

    internal Manufacturer Manufacturer
    {
        get { return m_manufacturer; }
        set { m_manufacturer = value; }
    }
}

public class Manufacturer
{
    private List<Product> m_Products = new List<Product>();

    public IEnumerable<Product> Products { get { return m_Products.AsReadOnly(); } }

    public void AddProduct( Product p )
    {
        if( !m_Products.Contains( p ) )
        {
            m_Products.Add( p );
            p.Manufacturer = this;
        }
    }

    public void RemoveProduct( Product p )
    {
        m_Products.Remove( p );
        p.Manufacturer = null;
    }
}

Ответ 2

Это не правильный способ моделирования этой проблемы.

A Product должен иметь Price, a Price не должен иметь Product:

public class Product
{
    public Price CurrentPrice {get; private set; }
    public IList<Price> HistoricPrices { get; private set;}
}

public class Price { }

В вашей конкретной настройке, что значит для Price иметь Product? В классе, который я создал выше, вы сможете обрабатывать все цены внутри самого класса Product.

Ответ 3

В прошлом я добавил методы "ссылки", т.е. вместо "установки" цены на продукт, вы связываете продукт и цену.

public void linkPrice(Price toLink) {
    this.price = toLink;
    toLink.setProduct(this);
}

(если вы используете setPrice для этого, тогда setProduct также сделает это, и они навсегда назовут друг друга во второй строке, поэтому я создаю явный метод ссылок вместо использования сеттеров и геттеров. быть защищенным пакетом.

YMMV, ваша ситуация может быть другой.

Ответ 4

Моя первая мысль заключается в том, что в вашей функции/свойства, используемой для добавления цен, добавьте строку кода следующим образом:

public void addPrice(Price p) {
     //code to add price goes here
     p.Product = this;
}

Ответ 5

В прошлом я сделал что-то подобное, когда мне нужно было поддерживать этот тип отношений...

public class Owner
{
    public List<Owned> OwnedList { get; set; }
}

public class Owned
{
    private Owner _owner;

    public Owner ParentOwner
    {
        get
        {
            if ( _owner == null )
                _owner = GetParentOwner(); // GetParentOwner() can be any method for getting the parent object
            return _owner;
        }
    }
}

Ответ 6

Я сейчас работаю над проблемой, подобной этой, и я использую # 3 в ответе Л.Бушкина.

У меня есть объект хранения данных со списками для всех моих классов данных. У классов данных есть идентификаторы в них для ссылок на другие классы, и я возвращаюсь к классу хранения данных для получения ссылочного элемента.

Я нашел это действительно полезным, потому что мне нужно иметь возможность фильтровать данные, и это делается при запросе данных из хранилища данных.