12

Las bases de datos orientadas a documentos (particularmente RavenDB) me intrigan realmente, y estoy deseando jugar con ellas un poco. Sin embargo, como alguien que está muy acostumbrado al mapeo relacional, estaba tratando de pensar cómo modelar los datos correctamente en una base de datos documental.¿Cómo modelizaré los datos que son heirarchal y relacionales en un sistema de base de datos orientado a documentos como RavenDB?

Decir que tengo un CRM con las siguientes entidades en mi aplicación de C# (dejando de lado las propiedades que no sean necesarios):

public class Company 
{ 
    public int Id { get; set; } 
    public IList<Contact> Contacts { get; set; } 
    public IList<Task> Tasks { get; set; } 
} 

public class Contact 
{ 
    public int Id { get; set; } 
    public Company Company { get; set; } 
    public IList<Task> Tasks { get; set; } 
} 

public class Task 
{ 
    public int Id { get; set; } 
    public Company Company { get; set; } 
    public Contact Contact { get; set; } 
} 

Estaba pensando en poner todo esto en un documento Company, los contactos o las tareas no tienen un propósito fuera de las empresas, y la mayoría de las veces la consulta de una tarea o contactos también mostrará información sobre la compañía asociada.

El problema viene con las entidades Task. Supongamos que el negocio requiere que una tarea esté SIEMPRE asociada con una empresa pero opcionalmente asociada a una tarea.

En un modelo relacional esto es fácil, ya que sólo hay una mesa Tasks y tienen la Company.Tasks se refieren a todas las tareas de la empresa, mientras que Contact.Tasks sólo muestra las tareas para la tarea específica.

Para modelar esto en una base de datos documental, pensé en los siguientes tres ideas:

  1. Tareas modelo como un documento separado. Esto parece una especie de documento anti-documentos ya que la mayoría de las veces que miras a una empresa o contacto querrás ver la lista de tareas, por lo que debes realizar muchas más entregas de documentos.

  2. Mantenga las tareas que no están asociadas con un contacto en la lista Company.Tasks y coloque las tareas asociadas con un contacto en la lista para cada contacto individual. Desafortunadamente, esto significa que si desea ver todas las tareas para una empresa (que probablemente será mucho), debe combinar todas las tareas para la empresa con todas las tareas para cada contacto individual. También veo que esto es complicado cuando desea desasociar una tarea de un contacto, ya que debe moverlo del contacto a la compañía

  3. Mantenga todas las tareas en la lista Company.Tasks, y cada contacto tiene una lista de identificación valores para las tareas a las que está asociado. Esto parece un buen enfoque, excepto para tener que tomar manualmente los valores de identificación y tener que hacer una sub-lista de entidades Task para un contacto.

¿Cuál es la forma recomendada de modelar estos datos en una base de datos orientada a documentos?

Respuesta

10

Uso desnormalizará referencias:

http://ravendb.net/faq/denormalized-references

, en esencia, que tiene una clase DenormalizedReference:

public class DenormalizedReference<T> where T : INamedDocument 
{ 
    public string Id { get; set; } 
    public string Name { get; set; } 

    public static implicit operator DenormalizedReference<T> (T doc) 
    { 
     return new DenormalizedReference<T> 
     { 
      Id = doc.Id, 
      Name = doc.Name 
     } 
    } 
} 

sus documentos parecen - He implementado la interfaz INamedDocument - esto puede ser lo que necesita que sea así:

public class Company : INamedDocument 
{ 
    public string Name{get;set;} 
    public int Id { get; set; } 
    public IList<DenormalizedReference<Contact>> Contacts { get; set; } 
    public IList<DenormalizedReference<Task>> Tasks { get; set; } 
} 

public class Contact : INamedDocument 
{ 
    public string Name{get;set;} 
    public int Id { get; set; } 
    public DenormalizedReference<Company> Company { get; set; } 
    public IList<DenormalizedReference<Task>> Tasks { get; set; } 
} 

public class Task : INamedDocument 
{ 
    public string Name{get;set;} 
    public int Id { get; set; } 
    public DenormalizedReference<Company> Company { get; set; } 
    public DenormalizedReference<Contact> Contact { get; set; } 
} 

Ahora guardar una tarea funciona exactamente como lo hacía antes:

var task = new Task{ 
    Company = myCompany, 
    Contact = myContact 
}; 

Sin embargo tirando hacia atrás de todo esto significará que sólo va a obtener la referencia sin normalizar para los objetos secundarios. Para hidratar estos utilizo un índice:

public class Tasks_Hydrated : AbstractIndexCreationTask<Task> 
{ 
    public Tasks_Hydrated() 
    { 
     Map = docs => from doc in docs 
         select new 
           { 
            doc.Name 
           }; 

     TransformResults = (db, docs) => from doc in docs 
             let Company = db.Load<Company>(doc.Company.Id) 
             let Contact = db.Load<Contact>(doc.Contact.Id) 
             select new 
                { 
                 Contact, 
                 Company, 
                 doc.Id, 
                 doc.Name 
                }; 
    } 
} 

y utilizar el índice para recuperar las tareas hidratados es:

var tasks = from c in _session.Query<Projections.Task, Tasks_Hydrated>() 
        where c.Name == "taskmaster" 
        select c; 

que creo que es bastante limpio :)

como una conversación de diseño - la regla general es que si alguna vez necesita cargar los documentos secundarios solo como en - no es parte del documento principal. Ya sea para editar o ver, debe modelarlo con su propio Id. Como documento propio. Usar el método anterior hace que esto sea bastante simple.

+0

Ok, así que supongo que estaba tomando la desnormalización demasiado lejos, pero ¿dividirlas renunciar a las ventajas de un documento basado en db, ya que tendría que tener que hacer uniones entre documentos constantemente? – KallDrexx

+0

no lo hará porque estos índices son muy rápidos y la carga db.Load ocurre en el servidor, por lo que el costo es mínimo. Debería considerar dónde están sus límites transaccionales y utilizar este método solo cuando realmente lo necesita, pero significa que puede obtener realmente los beneficios de ambos mundos. Olvidé mencionar que actualizar las referencias desnormalizadas (si el nombre cambia, por ejemplo) necesita ejecutar un parche para actualizar las referencias. De nuevo, esto es muy simple, pero es un proceso que debe administrar. Encuentro que este es un pequeño costo que es superado masivamente por los beneficios de un DB sin esquema :) – iwayneo

+0

Eso tiene sentido :). Me gusta mucho la idea de las bases de datos de documentos (y, lo que es más importante, sin esquemas). ¡Gracias! – KallDrexx

1

Soy nuevo para documentar DBS, así ... así que con un grano de sal ...

Como ejemplo de contraste ... si está en Twitter y usted tiene una lista de las personas que follow, que contiene una lista de sus tweets ... no movería sus tweets a su cuenta de Twitter para leerlos, y si vuelve a twittear, solo tendría una copia, no el original.

Entonces, de la misma manera, mi opinión es que si las tareas pertenecen a una empresa, entonces permanecen dentro de la empresa. La compañía es la raíz agregada para las tareas. Los Contactos solo pueden contener referencias (ids) o copias de las Tareas y no pueden modificarlas directamente. Si su contacto tiene una "copia" de la tarea, está bien, pero para modificar la tarea (por ejemplo, marcarla como completada) la modificaría mediante su raíz agregada (empresa).Como una copia puede quedar obsoleta rápidamente, parece que solo desea que exista una copia mientras está en la memoria y al guardar el contacto, solo guardará las referencias a las tareas.

Cuestiones relacionadas