Как смоделировать отношения «многие ко многим» в коде? - PullRequest
26 голосов
/ 09 июля 2009

Предположим, у меня есть 2 таблицы в базе данных. Например: собака и босс Это отношение многие ко многим, потому что у босса может быть более 1 собаки, а у собаки может быть более 1 владельца. Я владелец Бобби, но моя жена тоже.

Но многим многим это не разрешается, поэтому есть вспомогательная таблица: DogsPerBoss

Как смоделировать это в коде?

Класс Босс может иметь коллекцию собак. Класс Dog может иметь коллекцию Боссов. -> По крайней мере, я так думаю. Возможно, есть лучшие решения?

Как насчет дополнительных данных, которые есть в таблице помощников? Это должно быть в классе де Босса или в классе собаки? например: псевдоним (Я называю собаку "хороший мальчик", а моя жена называет его "песик")

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

ORM (как NHibernate) не вариант.

Ответы [ 12 ]

23 голосов
/ 09 июля 2009

Почему вы говорите о столах? Вы создаете объектную модель или модель базы данных?

Для объектной модели нет причины, по которой у собаки не может быть List<Owner>, а у владельца - List<Dog>. Только если у вас есть атрибуты в отношении, вам нужен промежуточный класс (который UML называет классом ассоциации). Тогда у вас будет класс DogOwnership с дополнительными свойствами, и у каждого Владельца будет List<DogOwnership>, как и у каждого Собака. У DogOwner будет Собака, Владелец и дополнительные свойства.

14 голосов
/ 09 июля 2009
public class Boss
{
   private string name;
   private List<Hashtable> dogs;
   private int limit;

   public Boss(string name, int dogLimit)
   {
      this.name = name;
      this.dogs = new List<Hashtable>();
      this.limit = dogLimit; 
   }

   public string Name { get { return this.name; } }

   public void AddDog(string nickname, Dog dog)
   {
      if (!this.dogs.Contains(nickname) && !this.dogs.Count == limit)
      {
         this.dogs.Add(nickname, dog);
         dog.AddBoss(this);
      } 
   }

   public void RemoveDog(string nickname)
   {
       this.dogs.Remove(nickname);
       dog.RemoveBoss(this);
   }

   public void Hashtable Dogs { get { return this.dogs; } }
}

public class Dog
{
   private string name;
   private List<Boss> bosses;

   public Dog(string name)
   {
      this.name = name;
      this.bosses = new List<Boss>();
   }

   public string Name { get { return this.name; } }

   public void AddBoss(Boss boss)
   {
      if (!this.bosses.Contains(boss))
      {
          this.bosses.Add(boss);
      }
   }

   public void RemoveBoss(Boss boss)
   {
      this.bosses.Remove(boss);
   }  

   public ReadOnlyCollection<Boss> Bosses { get { return new ReadOnlyCollection<Boss>(this.bosses); } }
}

Приведенное выше поддерживает отношения боссов могут иметь несколько собак (с наложенным ограничением) и собак, имеющих несколько боссов. Это также означает, что когда босс добавляет собаку, они могут указать псевдоним собаки, который является уникальным только для этого босса. Это означает, что другие боссы могут добавлять ту же собаку, но с разными никами.

Что касается лимита, я бы, вероятно, использовал его в качестве значения App.Config, которое вы только что прочитали перед созданием объекта (объектов) босса. Вот небольшой пример:

var james = new Boss("James", ConfigurationManager.AppSettings["DogsPerBoss"]);
var joe = new Boss("Joe", ConfigurationManager.AppSettings["DogsPerBoss"]);

var benji = new Dog("Benji");
var pooch = new Dog("Pooch");

james.AddDog("Good boy", benji);
joe.AddDog("Doggy", benji);

james.AddDog("Rover", pooch);
joe.AddDog("Buddy", pooch);  // won't add as the preset limit has been reached.

Вы можете явно настроить это, как считаете нужным, однако я думаю, что основы того, что вы ищете, есть.

  • Босс может иметь несколько собак с ограничением
  • У собак может быть несколько боссов
  • Боссы могут иметь разные псевдонимы для одной и той же собаки.
6 голосов
/ 09 июля 2009

как то так; Тем не менее, он все же нуждается в некоторой тонкой настройке (сделайте коллекцию частной и добавьте для нее общедоступный метод доступа только для чтения, который, например, возвращает коллекцию только для чтения, но вы поймете дрейф.

public class Dog
{
    public List<Boss> Bosses;

    public void AddBoss( Boss b )  
    {
        if( b != null && Bosses.Contains (b) == false )
        {
            Bosses.Add (b);
            b.AddDog (this);
        }
    }

    public void RemoveBoss( Boss b )
    {
         if( b !=null && Bosses.Contains (b) )
         {
             Bosses.Remove (b);
             b.RemoveDog (this);
         }
    }
}

public class Boss
{
    public List<Dog> Dogs;

    public void AddDog( Dog d )
    {
         if( d != null && Dogs.Contains (d) == false )
         {
              Dogs.Add(d);
              d.AddBoss(this);
         }
    }

    public void RemoveDog( Dog d )
    {
        if( d != null && Dogs.Contains(d) )
        {
            Dogs.Remove (d);
            d.RemoveBoss(this);
        }
    }
}

Таким образом, вы можете смоделировать многие-многие в своем коде, где каждый Собака знает своих Боссов, а каждый Босс знает своих Собак. Когда вам понадобятся дополнительные данные в вспомогательной таблице, вам также потребуется создать еще один класс.

2 голосов
/ 09 июля 2009

традиционное отношение многие ко многим не будет иметь дополнительных полей в соответствующей таблице.

Поскольку у вас есть поля с уникальной информацией, я склонен перестать думать об этих отношениях как о многих со многими.

Как только вы добавляете информацию в соответствующую таблицу, я думаю, что вы превратили эту таблицу в отдельную сущность и поэтому нуждаетесь в собственном объекте для ее представления.

С этого момента у вас может появиться класс DogsName для соединения человека и собаки - оба из них будут содержать ссылки на этот объект как часть коллекции.

Однако, независимо от того, даете ли вы собаке имя, которым ее будут называть или владеете собакой,

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

1 голос
/ 09 июля 2009

Если у вас есть простая таблица связи «многие ко многим» с внешними ключами от каждой таблицы в отношениях, то вы должны смоделировать ее так, как вы предлагаете: у босса есть коллекция собак, а у собаки - коллекция боссов.

Если у вас есть отношение многие ко многим с дополнительными данными, такими как псевдоним, вы бы смоделировали это как два отношения один ко многим. Создайте сущность, такую ​​как DogBoss, чтобы у босса была коллекция DogBoss, а у собаки - DogBoss.

1 голос
/ 09 июля 2009

Это классическая проблема между базами данных, где многие ко многим не работают, отсюда ваша таблица помощников, и объектным миром, где многие ко многим отлично работают. Как только отношения имеют атрибуты, вы должны создать новый класс для хранения этой информации. Тем не менее, вы сэкономите много времени, если посмотрите на Object Relation Mapping - ORM - это целое поле выросло для решения этой (и многих других) проблем между DB и Object.

1 голос
/ 09 июля 2009

Если вам не нужно было записывать псевдоним, у собаки должен быть список боссов, а у босса - список собак.

Если отношения между Собакой и Боссом имеют атрибуты, в данном случае псевдоним, то вы должны создать класс, представляющий эту связь, и в обоих собаках и Боссах хранятся списки этого типа.

Я уже некоторое время использую NHibernate и считаю его очень полезным для ослабления такого рода несоответствия реляционного сопротивления объекта .

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

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

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

  • Например, если вы разрабатываете ветеринарное программное обеспечение для управления пациентами, для ветеринара, который лечит бездомных собак, отношения между пациентом (собакой) и гардианом (боссом) должны быть такими: У боссов должна быть хотя бы одна собака, и у собаки может не быть какого-либо босса (тогда идентификатор босса является внешним ключом этих отношений), что означает, что в вашем дизайне класс собаки должен содержать коллекцию боссов. Почему, потому что ни один экземпляр босса не может быть создан без каких-либо собак. Мы можем принять это решение и с помощью логики данных. Давайте подумаем, когда вы пытаетесь сохранить классы вашей собаки и босса в базе данных. Если условие отношения, как указано выше, при сохранении босса вы должны вставить запись соединения в таблицу соединений.

  • Если вы разрабатываете это программное обеспечение для ветеринара, который не лечит бездомных собак, тогда отношения между пациентом и родителем должны быть такими: У собаки должен быть хотя бы один босс, а у босса должна быть хотя бы собака, и мы должны рассмотреть этот особый случай отношений. Это означает, что ни один из этих экземпляров классов не может быть создан без другого. Таким образом, мы должны определить эту специальность в нашем ОО-дизайне. Это означает, что нам нужен класс, который представляет эту зависимость. Конечно, эта зависимость будет храниться в соединительной таблице.

-Если ваше программное обеспечение разработано для ветеринара, который лечит бездомных собак, и эти собаки, принятые боссами, должны выглядеть так: У любой собаки не может быть босса (ов), и у любого босса не может быть собаки (ов) до усыновления. В этом случае наш OO дизайн должен заботиться об этом особом случае. Этот случай немного похож на первый. Таким образом, мы можем добавить коллекцию любого класса в другой. Но любое подобное программное обеспечение будет влиять на другие потребности. Нравится отчетность. Если ветеринар беспокоится о собаках, которых усыновил начальник (и), рано или поздно он спрашивает, какая собака усыновлена ​​кем. Как и в предложении (собаки, принятые боссом (-ами)), лучше, если класс собаки содержит коллекцию классов босса.

Надеюсь, я смогу дать правильные ответы на ваш вопрос.

0 голосов
/ 09 июля 2009

Я что-то упустил или это единственный код, который вам нужен для этого:

List<Bosses> BossList;

class Dog {}
class Boss { Dog[] Dogs; }

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

0 голосов
/ 09 июля 2009

В реляционной модели лучший способ смоделировать отношения «многие ко многим» (используя ваш пример «Собаки / Боссы») - это иметь три отдельные таблицы.

Одна таблица для DOGS, одна таблица для BOSSES (и каждая из этих таблиц имеет уникальный ключ), а третья таблица обычно является " таблицей соединений ".

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

Теперь, когда дело доходит до моделирования этого в коде более объектно-ориентированным способом, это обычно достигается с помощью класса Dog и класса Boss. Помимо наличия обычных атомных свойств для каждого из этих объектов, каждый из них также выставить свойство, которое является коллекцией другого.

Так, например, объект Dog будет иметь свойство с именем «Боссы». Это свойство будет предоставлять коллекцию объектов Boss, которые выделены для конкретного объекта Dog (как определено в соединительной таблице), а с другой стороны, каждый объект Boss будет представлять свойство с именем Dogs, которое будет представлять собой коллекцию объектов Dog, выделенных к этому конкретному объекту Босса (как определено таблицей соединений).

Обратите внимание, что в этих объектах может быть некоторое «наложение» (т. Е. Один объект «собака» может иметь объекты «босс», которые есть у другого объекта «собака»), однако, это традиционный механизм перевода трех преобразование реляционной модели «многие ко многим» в объектно-ориентированную модель.

...