DocumentDB - Modelado de datos

Si bien las bases de datos sin esquema, como DocumentDB, facilitan la adopción de cambios en su modelo de datos, aún debe dedicar un tiempo a pensar en sus datos.

  • Tienes muchas opciones. Naturalmente, puede trabajar con gráficos de objetos JSON o incluso cadenas sin formato de texto JSON, pero también puede usar objetos dinámicos que le permitan enlazar propiedades en tiempo de ejecución sin definir una clase en tiempo de compilación.

  • También puede trabajar con objetos reales de C #, o Entidades como se les llama, que podrían ser sus clases de dominio empresarial.

Relaciones

Echemos un vistazo a la estructura jerárquica del documento. Tiene algunas propiedades de nivel superior como la identificación requerida, así como lastName e isRegistered, pero también tiene propiedades anidadas.

{ 
   "id": "AndersenFamily", 
   "lastName": "Andersen", 
	
   "parents": [ 
      { "firstName": "Thomas", "relationship": "father" }, 
      { "firstName": "Mary Kay", "relationship": "mother" } 
   ],
	
   "children": [ 
      { 
         "firstName": "Henriette Thaulow", 
         "gender": "female", 
         "grade": 5, 
         "pets": [ { "givenName": "Fluffy", "type": "Rabbit" } ] 
      } 
   ], 
	
   "location": { "state": "WA", "county": "King", "city": "Seattle"}, 
   "isRegistered": true 
}
  • Por ejemplo, la propiedad principal se proporciona como una matriz JSON, como se indica con los corchetes.

  • También tenemos otra matriz para niños, aunque solo hay un niño en la matriz en este ejemplo. Así es como modela el equivalente de relaciones uno a muchos dentro de un documento.

  • Simplemente use matrices donde cada elemento de la matriz podría ser un valor simple u otro objeto complejo, incluso otra matriz.

  • Por lo tanto, una familia puede tener varios padres y varios hijos, y si observa los objetos secundarios, tienen la propiedad de una mascota que es en sí misma una matriz anidada para una relación uno a muchos entre los niños y las mascotas.

  • Para la propiedad de ubicación, estamos combinando tres propiedades relacionadas, el estado, el condado y la ciudad en un objeto.

  • Incrustar un objeto de esta manera en lugar de incrustar una matriz de objetos es similar a tener una relación uno a uno entre dos filas en tablas separadas en una base de datos relacional.

Incrustar datos

Cuando comience a modelar datos en un almacén de documentos, como DocumentDB, intente tratar sus entidades como documentos autónomos representados en JSON. Cuando trabajamos con bases de datos relacionales, siempre normalizamos los datos.

  • Normalmente, la normalización de sus datos implica tomar una entidad, como un cliente, y dividirla en datos discretos, como detalles de contacto y direcciones.

  • Para leer a un cliente, con todos sus datos de contacto y direcciones, debe utilizar JOINS para agregar eficazmente sus datos en tiempo de ejecución.

Ahora echemos un vistazo a cómo modelaríamos los mismos datos como una entidad autónoma en una base de datos de documentos.

{
   "id": "1", 
   "firstName": "Mark", 
   "lastName": "Upston", 
	
   "addresses": [ 
      {             
         "line1": "232 Main Street", 
         "line2": "Unit 1", 
         "city": "Brooklyn", 
         "state": "NY", 
         "zip": 11229
      }
   ],
	
   "contactDetails": [ 
      {"email": "[email protected]"}, 
      {"phone": "+1 356 545-86455", "extension": 5555} 
   ]
}

Como puede ver, hemos desnormalizado el registro del cliente donde toda la información del cliente está incrustada en un solo documento JSON.

En NoSQL tenemos un esquema gratuito, por lo que también puede agregar detalles de contacto y direcciones en diferentes formatos. En NoSQL, puede recuperar un registro de cliente de la base de datos en una sola operación de lectura. De manera similar, la actualización de un registro también es una operación de escritura única.

Los siguientes son los pasos para crear documentos usando .Net SDK.

Step 1- Crear una instancia de DocumentClient. Luego consultaremos la base de datos myfirstdb y también consultaremos la colección MyCollection, que almacenamos en esta colección de variables privadas para que sea accesible en toda la clase.

private static async Task CreateDocumentClient() { 
   // Create a new instance of the DocumentClient
	
   using (var client = new DocumentClient(new Uri(EndpointUrl), AuthorizationKey)) { 
      database = client.CreateDatabaseQuery("SELECT * FROM c WHERE c.id =
         'myfirstdb'").AsEnumerable().First(); 
			
      collection = client.CreateDocumentCollectionQuery(database.CollectionsLink,
         "SELECT * FROM c WHERE c.id = 'MyCollection'").AsEnumerable().First();  
			
      await CreateDocuments(client); 
   }

}

Step 2 - Crear algunos documentos en la tarea CreateDocuments.

private async static Task CreateDocuments(DocumentClient client) {
   Console.WriteLine(); 
   Console.WriteLine("**** Create Documents ****"); 
   Console.WriteLine();
	
   dynamic document1Definition = new {
      name = "New Customer 1", address = new { 
         addressType = "Main Office", 
         addressLine1 = "123 Main Street", 
         location = new { 
            city = "Brooklyn", stateProvinceName = "New York"
         }, 
         postalCode = "11229", countryRegionName = "United States" 
      }, 
   };
	
   Document document1 = await CreateDocument(client, document1Definition); 
   Console.WriteLine("Created document {0} from dynamic object", document1.Id); 
   Console.WriteLine(); 
}

El primer documento se generará a partir de este objeto dinámico. Esto puede parecer JSON, pero por supuesto que no lo es. Este es código C # y estamos creando un objeto .NET real, pero no hay una definición de clase. En cambio, las propiedades se infieren de la forma en que se inicializa el objeto. También puede observar que no hemos proporcionado una propiedad Id para este documento.

Step 3 - Ahora echemos un vistazo a CreateDocument y parece el mismo patrón que vimos para crear bases de datos y colecciones.

private async static Task<Document> CreateDocument(DocumentClient client,
   object documentObject) {
   var result = await client.CreateDocumentAsync(collection.SelfLink, documentObject); 
	
   var document = result.Resource; 
   Console.WriteLine("Created new document: {0}\r\n{1}", document.Id, document); 
	
   return result; 
}

Step 4- Esta vez llamamos CreateDocumentAsync especificando el SelfLink de la colección a la que queremos agregar el documento. Obtenemos una respuesta con una propiedad de recurso que, en este caso, representa el nuevo documento con sus propiedades generadas por el sistema.

En la siguiente tarea CreateDocuments, hemos creado tres documentos.

  • En el primer documento, el objeto Documento es una clase definida en el SDK que hereda del recurso y, por lo tanto, tiene todas las propiedades comunes del recurso, pero también incluye las propiedades dinámicas que definen el documento sin esquema en sí.

private async static Task CreateDocuments(DocumentClient client) {
   Console.WriteLine(); 
   Console.WriteLine("**** Create Documents ****"); 
   Console.WriteLine();
	
   dynamic document1Definition = new {
      name = "New Customer 1", address = new {
         addressType = "Main Office", 
         addressLine1 = "123 Main Street", 
         location = new {
            city = "Brooklyn", stateProvinceName = "New York" 
         }, 
         postalCode = "11229", 
         countryRegionName = "United States" 
      }, 
   };
	
   Document document1 = await CreateDocument(client, document1Definition); 
   Console.WriteLine("Created document {0} from dynamic object", document1.Id); 
   Console.WriteLine();
	
   var document2Definition = @" {
      ""name"": ""New Customer 2"", 
		
      ""address"": { 
         ""addressType"": ""Main Office"", 
         ""addressLine1"": ""123 Main Street"", 
         ""location"": { 
            ""city"": ""Brooklyn"", ""stateProvinceName"": ""New York"" 
         }, 
         ""postalCode"": ""11229"", 
         ""countryRegionName"": ""United States"" 
      } 
   }"; 
	
   Document document2 = await CreateDocument(client, document2Definition); 
   Console.WriteLine("Created document {0} from JSON string", document2.Id);
   Console.WriteLine();
	
   var document3Definition = new Customer {
      Name = "New Customer 3", 
		
      Address = new Address {
         AddressType = "Main Office", 
         AddressLine1 = "123 Main Street", 
         Location = new Location {
            City = "Brooklyn", StateProvinceName = "New York" 
         }, 
         PostalCode = "11229", 
         CountryRegionName = "United States" 
      }, 
   };
	
   Document document3 = await CreateDocument(client, document3Definition); 
   Console.WriteLine("Created document {0} from typed object", document3.Id); 
   Console.WriteLine(); 
}
  • Este segundo documento solo funciona con una cadena JSON sin procesar. Ahora entramos en una sobrecarga para CreateDocument que usa JavaScriptSerializer para deserializar la cadena en un objeto, que luego pasa al mismo método CreateDocument que usamos para crear el primer documento.

  • En el tercer documento, hemos utilizado el objeto C # Customer que se define en nuestra aplicación.

Echemos un vistazo a este cliente, tiene una propiedad de identificación y dirección donde la dirección es un objeto anidado con sus propias propiedades, incluida la ubicación, que es otro objeto anidado.

using Newtonsoft.Json; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace DocumentDBDemo {
 
   public class Customer { 
      [JsonProperty(PropertyName = "id")] 
      public string Id { get; set; }
      // Must be nullable, unless generating unique values for new customers on client  
      [JsonProperty(PropertyName = "name")] 
      public string Name { get; set; }  
      [JsonProperty(PropertyName = "address")] 
      public Address Address { get; set; } 
   }
	
   public class Address {
      [JsonProperty(PropertyName = "addressType")] 
      public string AddressType { get; set; }  
		
      [JsonProperty(PropertyName = "addressLine1")] 
      public string AddressLine1 { get; set; }  
		
      [JsonProperty(PropertyName = "location")] 
      public Location Location { get; set; }  
		
      [JsonProperty(PropertyName = "postalCode")] 
      public string PostalCode { get; set; }  
		
      [JsonProperty(PropertyName = "countryRegionName")] 
      public string CountryRegionName { get; set; } 
   }
	
   public class Location { 
      [JsonProperty(PropertyName = "city")] 
      public string City { get; set; }  
		
      [JsonProperty(PropertyName = "stateProvinceName")]
      public string StateProvinceName { get; set; } 
   } 
}

También tenemos atributos de propiedad JSON en su lugar porque queremos mantener las convenciones adecuadas en ambos lados de la cerca.

Así que simplemente creo mi objeto New Customer junto con sus objetos secundarios anidados y llamo a CreateDocument una vez más. Aunque nuestro objeto de cliente tiene una propiedad Id, no le proporcionamos un valor, por lo que DocumentDB generó uno basado en el GUID, tal como lo hizo para los dos documentos anteriores.

Cuando se compile y ejecute el código anterior, recibirá el siguiente resultado.

**** Create Documents ****  
Created new document: 575882f0-236c-4c3d-81b9-d27780206b2c 
{ 
  "name": "New Customer 1", 
  "address": { 
    "addressType": "Main Office", 
    "addressLine1": "123 Main Street", 
    "location": { 
      "city": "Brooklyn", 
      "stateProvinceName": "New York" 
    }, 
    "postalCode": "11229", 
    "countryRegionName": "United States" 
  }, 
  "id": "575882f0-236c-4c3d-81b9-d27780206b2c", 
  "_rid": "kV5oANVXnwDGPgAAAAAAAA==", 
  "_ts": 1450037545, 
  "_self": "dbs/kV5oAA==/colls/kV5oANVXnwA=/docs/kV5oANVXnwDGPgAAAAAAAA==/", 
  "_etag": "\"00006fce-0000-0000-0000-566dd1290000\"", 
  "_attachments": "attachments/" 
} 
Created document 575882f0-236c-4c3d-81b9-d27780206b2c from dynamic object  
Created new document: 8d7ad239-2148-4fab-901b-17a85d331056 
{ 
  "name": "New Customer 2", 
  "address": {
    "addressType": "Main Office", 
    "addressLine1": "123 Main Street", 
    "location": { 
      "city": "Brooklyn", 
      "stateProvinceName": "New York" 
    }, 
    "postalCode": "11229", 
    "countryRegionName": "United States" 
  }, 
  "id": "8d7ad239-2148-4fab-901b-17a85d331056", 
  "_rid": "kV5oANVXnwDHPgAAAAAAAA==", 
  "_ts": 1450037545, 
  "_self": "dbs/kV5oAA==/colls/kV5oANVXnwA=/docs/kV5oANVXnwDHPgAAAAAAAA==/", 
  "_etag": "\"000070ce-0000-0000-0000-566dd1290000\"", 
  "_attachments": "attachments/" 
} 
Created document 8d7ad239-2148-4fab-901b-17a85d331056 from JSON string  
Created new document: 49f399a8-80c9-4844-ac28-cd1dee689968 
{ 
  "id": "49f399a8-80c9-4844-ac28-cd1dee689968", 
  "name": "New Customer 3", 
  "address": { 
    "addressType": "Main Office", 
    "addressLine1": "123 Main Street", 
    "location": { 
      "city": "Brooklyn", 
      "stateProvinceName": "New York" 
    }, 
    "postalCode": "11229", 
    "countryRegionName": "United States" 
  }, 
  "_rid": "kV5oANVXnwDIPgAAAAAAAA==", 
  "_ts": 1450037546, 
  "_self": "dbs/kV5oAA==/colls/kV5oANVXnwA=/docs/kV5oANVXnwDIPgAAAAAAAA==/", 
  "_etag": "\"000071ce-0000-0000-0000-566dd12a0000\"", 
  "_attachments": "attachments/" 
}
Created document 49f399a8-80c9-4844-ac28-cd1dee689968 from typed object