Documentdb 简明教程

DocumentDB - Data Modeling

虽然无模式数据库(如DocumentDB)让您非常容易地接受对数据模型的更改,但您仍然应该花一些时间考虑您的数据。

  1. 你有许多选择。当然,您只能使用JSON对象图或甚至JSON文本的原始字符串,但您还可以使用动态对象,它允许您在运行时绑定到属性,而无需在编译时定义类。

  2. 您还可以处理真正的 C# 对象或实体(如其称谓),这可能是您的业务域类。

Relationships

让我们看一下文档的层次结构。它有一些顶级属性,例如必需的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
}
  1. 例如,parents属性以方括号表示的JSON数组形式提供。

  2. 我们还有另一个用于子级的数组,尽管在这个示例中数组中只含有一个子级。因此这就是您在文档中对一对多关系建模的方法。

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

  4. 因此一个家庭可以有多个父母和多个孩子,如果你查看孩子对象,它们有一个pets属性,它本身是一个嵌套数组,用于表示孩子和宠物之间的一对多关系。

  5. 对于Location属性,我们将三个相关属性(state、county和city)组合到一个对象中。

  6. 以这种方式嵌入对象而不是嵌入对象数组类似于在关系数据库中的两个单独表的两个行之间建立一对一关系。

Embedding Data

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

  1. 对数据进行规范化通常涉及采用一个实体(如客户),并将其分解为不同的数据片段,如联系方式和地址。

  2. 要读取一个客户以及其所有联系方式和地址,您需要使用JOIN在运行时有效地聚合您的数据。

现在让我们来看看如何在文档数据库中将相同的数据建模为自包含的实体。

{
   "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创建文档的步骤。

Step 1 − 实例化DocumentClient。然后我们将查询myfirstdb数据库,也查询MyCollection集合,我们将该集合存储在这个private变量集合中,以便整个类中都能访问它。

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 − 在 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 属性。

Step 3 - 现在让我们看看 CreateDocument,它看起来与我们看到的用于创建数据库和集合的模式相同。

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 - 这次,我们调用 CreateDocumentAsync,指定要向其中添加文档的集合的 SelfLink。我们收到响应,该响应具有 resource 属性,在此情况下,它表示具有其系统生成属性的新文档。

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

  1. 在第一个文档中,Document 对象是 SDK 中定义的类,它继承自 resource,因此具有所有公共资源属性,但它还包括定义无模式文档本身的动态属性。

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

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

让我们看看这个客户,它具有一个 Id 和 address 属性,其中 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