MongoDB Asociación de muchos a muchos

¿Cómo harías una asociación de muchos a muchos con MongoDB?

Por ejemplo; digamos que tiene una tabla de Usuarios y una tabla de Roles. Los usuarios tienen muchos roles y los roles tienen muchos usuarios. En SQL land, crearía una tabla UserRoles.

Users: Id Name Roles: Id Name UserRoles: UserId RoleId 

¿Cómo se maneja el mismo tipo de relación en MongoDB?

Dependiendo de las necesidades de su consulta, puede poner todo en el documento de usuario:

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

Para obtener todos los ingenieros, use:

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

Si desea mantener los roles en documentos separados, puede incluir el _id del documento en la matriz de roles en lugar del nombre:

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

y configurar los roles como:

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

En lugar de intentar modelar de acuerdo con nuestros años de experiencia con RDBMS, me ha resultado mucho más fácil modelar soluciones de depósito de documentos usando MongoDB, Redis y otras tiendas de datos NoSQL optimizando para los casos de uso de lectura, a la vez que soy considerado con los atómicos operaciones de escritura que necesitan ser soportadas por los casos de uso de escritura.

Por ejemplo, los usos de un dominio de “Usuarios en Roles” siguen:

  1. Función: Crear, Leer, Actualizar, Eliminar, Listar usuarios, Agregar usuario, Eliminar usuario, Borrar todos los usuarios, Índice de usuario o similar para admitir “Es usuario en función” (operaciones como un contenedor + sus propios metadatos).
  2. Usuario – Crear, Leer, Actualizar, Eliminar (operaciones CRUD como una entidad autónoma)

Esto se puede modelar como las siguientes plantillas de documento:

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

Para admitir los usos de alta frecuencia, como funciones relacionadas con roles de la entidad de usuario, User.Roles se desnormaliza intencionalmente, se almacena en el usuario y en roles. Los usuarios tienen almacenamiento duplicado.

Si no es fácilmente aparente en el texto, este es el tipo de pensamiento que se fomenta al usar repositorys de documentos.

Espero que esto ayude a cerrar la brecha con respecto al lado de lectura de las operaciones.

Para el lado de la escritura, lo que se recomienda es modelar de acuerdo con las escrituras atómicas. Por ejemplo, si las estructuras de documentos requieren la adquisición de un locking, la actualización de un documento, luego de otro, y posiblemente más documentos, y luego la liberación del locking, es probable que el modelo haya fallado. El hecho de que podamos construir lockings distribuidos no significa que se supone que debemos usarlos.

Para el caso del modelo Usuario en Roles, las operaciones que extienden nuestra evitación de escritura atómica de lockings es agregar o quitar un Usuario de un Rol. En cualquier caso, una operación exitosa da como resultado la actualización de un solo usuario y un solo documento de rol. Si algo falla, es fácil realizar la limpieza. Esta es una de las razones por las que el patrón de unidad de trabajo aparece bastante cuando se usan repositorys de documentos.

La operación que realmente extiende nuestra evitación de escritura atómica de lockings es la eliminación de un Rol, lo que daría lugar a muchas actualizaciones de Usuario para eliminar el Nombre de rol de la matriz Usuario.rolas. Esta operación de clear entonces generalmente se desaconseja, pero si es necesario se puede implementar ordenando las operaciones:

  1. Obtenga la lista de nombres de usuario de Role.users.
  2. Iterar los nombres de usuario del paso 1, eliminar el nombre del rol de User.roles.
  3. Borre los usuarios de roles.

En el caso de un problema, que es más probable que ocurra en el paso 2, una reversión es fácil ya que el mismo conjunto de nombres de usuario del paso 1 se puede usar para recuperar o continuar.

Acabo de tropezar con esta pregunta y, aunque es una pregunta anterior, pensé que sería útil agregar un par de posibilidades que no se mencionan en las respuestas dadas. Además, las cosas se han movido un poco en los últimos años, por lo que vale la pena destacar que SQL y NoSQL se están acercando.

Uno de los comentaristas planteó la sabia actitud de advertencia de que “si los datos son relacionales, use relacionales”. Sin embargo, ese comentario solo tiene sentido en el mundo relacional, donde los esquemas siempre vienen antes de la aplicación.

MUNDO RELACIONAL: Datos de estructura> Solicitud de escritura para obtenerlo
NOSQL WORLD: Aplicación de diseño> Datos de estructura en consecuencia

Incluso si los datos son relacionales, NoSQL sigue siendo una opción. Por ejemplo, las relaciones uno a muchos no son ningún problema y están ampliamente cubiertas en los documentos de MongoDB

UNA SOLUCIÓN DE 2015 PARA UN PROBLEMA DE 2010

Desde que se publicó esta pregunta, ha habido serios bashs de acercar noSQL a SQL. El equipo dirigido por Yannis Papakonstantinou en la Universidad de California (San Diego) ha estado trabajando en FORWARD , una implementación de SQL ++ que pronto podría ser la solución a problemas persistentes como el que se publica aquí.

En un nivel más práctico, el lanzamiento de Couchbase 4.0 ha significado que, por primera vez, puede hacer JOINs nativos en NoSQL. Usan su propio N1QL. Este es un ejemplo de un JOIN de sus tutoriales :

 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 permite la mayoría si no todas las operaciones de SQL, incluidas la agregación, el filtrado, etc.

LA NO-NUEVA SOLUCIÓN HÍBRIDA

Si MongoDB sigue siendo la única opción, me gustaría volver a mi punto de que la aplicación debe tener prioridad sobre la estructura de los datos. Ninguna de las respuestas menciona la incorporación híbrida, por lo que la mayoría de los datos consultados se incluyen en el documento / objeto, y las referencias se mantienen para una minoría de casos.

Ejemplo: ¿puede esperar la información (que no sea el nombre del rol)? ¿Podría el arranque de la aplicación ser más rápido al no solicitar nada que el usuario aún no necesite?

Este podría ser el caso si el usuario inicia sesión y necesita ver todas las opciones para todos los roles a los que pertenece. Sin embargo, el usuario es un “Ingeniero” y las opciones para este rol rara vez se utilizan. Esto significa que la aplicación solo necesita mostrar las opciones para un ingeniero en caso de que desee hacer clic en ellas.

Esto se puede lograr con un documento que indique a la aplicación al comienzo (1) a qué roles pertenece el usuario y (2) dónde obtener información sobre un evento vinculado a un rol en particular.

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

O, mejor aún, indexe el campo role.name en la colección de roles, y puede que tampoco necesite incrustar ObjectID ().

Otro ejemplo: ¿hay información sobre TODOS los roles solicitados TODO el tiempo?

También podría darse el caso de que el usuario inicie sesión en el tablero y el 90% del tiempo realice tareas relacionadas con el rol “Ingeniero”. La incrustación híbrida se puede realizar para ese rol en particular completo y mantener referencias solo para el rest.

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

Ser sin esquema no es solo una característica de NoSQL, podría ser una ventaja en este caso. Es perfectamente válido anidar diferentes tipos de objetos en la propiedad “Roles” de un objeto de usuario.

en caso de que el empleado y la empresa sean entidad-objeto, intente utilizar el siguiente esquema:

 employee{ //put your contract to employee contracts:{ item1, item2, item3,...} } company{ //and duplicate it in company contracts:{ item1, item2, item3,...} }