DocumentDB 数据建模
虽然像 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 数组提供。
我们还有另一个子数组,尽管在这个例子中数组中只有一个子数组。因此,这就是您在文档中模拟一对多关系的方式。
您只需使用数组,其中数组中的每个元素可以是一个简单值或另一个复杂对象,甚至是另一个数组。
因此,一个家庭可以有多个父母和多个孩子,如果您查看子对象,他们有一个宠物的属性,该属性本身就是一个嵌套数组,用于孩子和宠物之间的一对多关系。
对于位置属性,我们将州、县和城市这三个相关属性组合成一个对象。
以这种方式嵌入对象而不是嵌入对象数组类似于在关系数据库的不同表中的两行之间建立一对一关系。
嵌入数据
当您开始对文档存储(例如 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<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;
}
第 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 属性,其中 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