Ассоциация "Много ко многим" MongoDB

Как бы вы сделали связь "многие-ко-многим" с MongoDB?

Например; скажем, у вас есть таблица "Пользователи" и таблица "Роли". У пользователей много ролей, и у ролей много пользователей. В области SQL вы создадите таблицу UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Как отношения такого же отношения обрабатываются в MongoDB?

Ответ 1

В зависимости от ваших запросов вы можете поместить все в пользовательский документ:

{name:"Joe"
,roles:["Admin","User","Engineer"]
}

Чтобы получить всех Инженеров, используйте:

db.things.find( { roles : "Engineer" } );

Если вы хотите сохранить роли в отдельных документах, вы можете включить документ _id в массив ролей вместо имени:

{name:"Joe"
,roles:["4b5783300334000000000aa9","5783300334000000000aa943","6c6793300334001000000006"]
}

и установите такие роли, как:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}

Ответ 2

Вместо того, чтобы пытаться моделировать в соответствии с нашим многолетним опытом работы с RDBMS, мне было намного проще моделировать решения для репозитория документов с использованием MongoDB, Redis и других хранилищ данных NoSQL, оптимизируя для чтения варианты использования, будучи внимательным операций атомной записи, которые должны поддерживаться случаями использования записи.

Например, использование домена "Пользователи в ролях":

  • Роль - создание, чтение, обновление, удаление, список пользователей, добавление пользователя, удаление пользователя, очистка всех пользователей, указатель пользователя или аналогичная поддержка "Является ли пользовательская роль" (операции, такие как контейнер + его собственные метаданные).
  • Пользователь - создание, чтение, обновление, удаление (операции CRUD, такие как автономный объект).

Это может быть смоделировано как следующие шаблоны документов:

User: { _id: UniqueId, name: string, roles: string[] }
    Indexes: unique: [ name ]
Role: { _id: UniqueId, name: string, users: string[] }
    Indexes: unique: [ name ]

Для поддержки высокочастотных применений, таких как функции, связанные с ролями, из пользовательского объекта User.Roles намеренно денормализуется, сохраняется на пользователе, а также на Role.Users, имеющих дублирующее хранилище.

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

Я надеюсь, что это поможет преодолеть разрыв в отношении прочитанной стороны операций.

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

В случае модели "Пользователь в ролях" операции, которые растягивают наше запрет на блокировку записи в ящики, - это добавление или удаление пользователя из роли. В любом случае успешная операция приводит к обновлению как одного пользователя, так и одного документа роли. Если что-то не удается, легко выполнить очистку. Это единственная причина, по которой шаблон Unit of Work появляется довольно часто, когда используются репозитории документов.

Операция, которая действительно расширяет наше запрет на блокировку записи атома, - это очистка роли, что приведет к тому, что многие пользовательские обновления удалит Role.name из массива User.roles. Эта операция очистки тогда обычно обескуражена, но при необходимости может быть реализована путем упорядочения операций:

  • Получить список имен пользователей от Role.users.
  • Итерируйте имена пользователей с шага 1, удалите имя роли из User.roles.
  • Очистить роль. Пользователи.

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

Ответ 3

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

Один из комментаторов поднял мудрый предостерегающий подход: "если данные реляционные, используйте реляционные". Однако этот комментарий имеет смысл только в реляционном мире, где схемы всегда появляются перед приложением.

ОТЛИЧИТЕЛЬНЫЙ МИР: Структурные данные > Напишите приложение, чтобы получить его
NOSQL WORLD: приложение для проектирования > Соответствующие структурные данные

Даже если данные являются реляционными, NoSQL по-прежнему является опцией. Например, отношения "один ко многим" не являются проблемой вообще и широко освещаются в Документах MongoDB

РЕШЕНИЕ 2015 ГОДА К ПРОБЛЕМЕ 2010 ГОДА

Поскольку этот вопрос был опубликован, были предприняты серьезные попытки приблизить noSQL к SQL. Команда, возглавляемая Яннисом Папаконстантину в Калифорнийском университете (Сан-Диего), работает над FORWARD, реализацией SQL ++, которая вскоре может стать решением постоянных проблем как тот, который вы отправили здесь.

На более практическом уровне выпуск Couchbase 4.0 означает, что в первый раз вы можете создавать собственные JOINs в NoSQL. Они используют свой собственный N1QL. Это пример JOIN из их руководств:

SELECT usr.personal_details, orders 
        FROM users_with_orders usr 
            USE KEYS "Elinor_33313792" 
                JOIN orders_with_users orders 
                    ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END

N1QL допускает большинство, если не все операции SQL, включая агрегирование, фильтрацию и т.д.

НЕ-НОВОЕ ГИБРИДНОЕ РЕШЕНИЕ

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

Пример: может ли информация (кроме имени роли) ждать? может ускорить загрузку приложения, не запрашивая ничего, что пользователю еще не нужно?

Это может быть так, если пользователь входит в систему и ему/ей необходимо просмотреть все параметры для всех ролей, к которым он принадлежит. Тем не менее, пользователь является "инженером", и опции для этой роли редко используются. Это означает, что приложение должно показывать параметры для инженера в случае, если он захочет нажать на них.

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

   {_id: ObjectID(),
    roles: [["Engineer", "ObjectId()"],
            ["Administrator", "ObjectId()"]]
   }

Или, что еще лучше, проиндексируйте поле role.name в коллекции ролей, и вам может не понадобиться вставлять ObjectID().

Другой пример: это информация обо всех запрошенных роликах ВСЕ время?

Также может быть случай, когда пользователь входит в панель управления, а 90% времени выполняет задачи, связанные с ролью "Инженер". Гибридное вложение может быть выполнено для этой конкретной роли в полном объеме и содержать ссылки только для остальных.

{_id: ObjectID(),
  roles: [{name: "Engineer", 
           property1: value1,
           property2: value2
          },   
          ["Administrator", "ObjectId()"]
         ]
}

Будучи схематичным, это не просто характеристика NoSQL, но и может быть преимуществом в этом случае. Это совершенно верно для размещения разных типов объектов в свойстве "Роли" объекта пользователя.

Ответ 4

если сотрудник и компания объект-объект попробуйте использовать следующую схему:

employee{
   //put your contract to employee
   contracts:{ item1, item2, item3,...}
}

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}