📜  DocumentDB-数据建模

📅  最后修改于: 2020-11-28 13:48:15             🧑  作者: Mango


尽管无模式数据库(如DocumentDB)使对数据模型的更改变得非常容易,但是您仍然应该花一些时间考虑数据。

  • 您有很多选择。自然,您可以只处理JSON对象图,甚至可以处理JSON文本的原始字符串,但是您也可以使用动态对象,这些对象使您可以在运行时绑定到属性,而无需在编译时定义类。

  • 您还可以使用实际的C#对象或称为实体的实体,它们可能是您的业务域类。

人际关系

让我们看一下文档的层次结构。它具有一些顶级属性,例如所需的id,lastName和isRegistered,但它也具有嵌套属性。

{ 
   "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 
}
  • 例如,parents属性作为JSON数组提供,如方括号所示。

  • 即使在此示例中,数组中只有一个孩子,我们也有另一个孩子数组。因此,这就是在文档中建模等效关系的方式。

  • 您只需使用数组,其中数组中的每个元素可以是一个简单值或另一个复杂对象,甚至另一个数组。

  • 因此,一个家庭可以有多个父母和多个孩子,并且如果您查看这些子对象,则它们具有宠物的属性,该属性本身是嵌套数组,用于孩子和宠物之间的一对多关系。

  • 对于location属性,我们将三个相关属性(州,县和城市)组合到一个对象中。

  • 以这种方式嵌入对象而不是嵌入对象数组类似于在关系数据库的不同表中的两行之间具有一对一的关系。

嵌入数据

当您在文档存储(例如DocumentDB)中开始对数据进行建模时,请尝试将您的实体视为以JSON表示的自包含文档。使用关系数据库时,我们总是规范化数据。

  • 对数据进行规范化通常涉及将一个实体(例如客户)并分解为谨慎的数据,例如联系方式和地址。

  • 要读取客户及其所有联系方式和地址,您需要使用JOINS在运行时有效地汇总数据。

现在让我们看一下如何将相同的数据建模为文档数据库中的独立实体。

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

如您所见,我们已经对客户记录进行了非规范化,客户的所有信息都嵌入到一个JSON文档中。

在NoSQL中,我们有一个免费的架构,因此您也可以添加不同格式的联系方式和地址。在NoSQL中,您可以通过一次读取操作从数据库中检索客户记录。同样,更新记录也是单个写入操作。

以下是使用.Net SDK创建文档的步骤。

步骤1-实例化DocumentClient。然后,我们将查询myfirstdb数据库,并查询MyCollection集合,我们将其存储在此私有变量集合中,以便可以在整个类中对其进行访问。

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); 
   }

}

步骤2-在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(); 
}

第一个文档将从该动态对象生成。这看起来像JSON,但事实并非如此。这是C#代码,我们正在创建一个真正的.NET对象,但是没有类定义。而是从初始化对象的方式推断属性。您还会注意到,我们没有为该文档提供Id属性。

步骤3-现在,让我们看一下CreateDocument,它看起来与创建数据库和集合时所看到的模式相同。

private async static Task 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; 
}

步骤4-这次我们调用CreateDocumentAsync,指定要向其添加文档的集合的SelfLink。我们返回一个带有资源属性的响应,在这种情况下,该资源属性代表具有系统生成的属性的新文档。

在下面的CreateDocuments任务中,我们创建了三个文档。

  • 在第一个文档中,Document对象是SDK中从资源继承的已定义类,因此它具有所有公共资源属性,但它还包含用于定义无模式文档本身的动态属性。

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(); 
}
  • 第二个文档仅使用原始JSON字符串。现在,我们进入CreateDocument的重载,该重载使用JavaScriptSerializer将字符串反序列化为对象,然后将其传递给我们用来创建第一个文档的相同CreateDocument方法。

  • 在第三个文档中,我们使用了在应用程序中定义的C#对象Customer。

让我们看一下这个客户,它有一个Id和address属性,其中地址是一个嵌套对象,其自身的属性包括location,这又是另一个嵌套对象。

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; } 
   } 
}

我们还拥有JSON属性属性,因为我们想在围栏的两侧保持适当的约定。

因此,我只创建了New Customer对象及其嵌套的子对象,然后再次调用CreateDocument。尽管我们的客户对象确实具有Id属性,但我们没有为其提供值,因此DocumentDB基于GUID生成了一个值,就像前两个文档所做的一样。

编译并执行上述代码后,您将收到以下输出。

**** 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