【MS Docs クイックスタート】Azure Cosmos DBをC#（.NET）で操作してみた

# はじめに

以下のMicrosoftドキュメントを参考に作成してみました。ドキュメント通りに動かなかった箇所があり、いくつか修正をして動くようにしたので記事にしてみました。

- [クイックスタート: Azure Cosmos DB SQL API アカウント リソースを管理するための .NET V4 SDK (プレビュー) を使用したコンソール アプリを構築する](https://docs.microsoft.com/ja-jp/azure/cosmos-db/create-sql-api-dotnet-v4)


# 前提・環境
- Windows 10 home
- visual studio 2019
- Azure portal上でCosmos DBのリソース作成済み
    - URIを取得できる。
    - プライマリキーを取得できる。
- NuGetパッケージ
    - Microsoft.Azure.Cosmos v3.17.1
    - Newtonsoft.Json v13.0.1

# 作成

## Family.csの作成

```cs
//using System.Text.Json;
//using System.Text.Json.Serialization;
using Newtonsoft.Json;

namespace todo
{
    public class Family
    {
        //[JsonPropertyName("id")]
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }
        public string LastName { get; set; }
        public Parent[] Parents { get; set; }
        public Child[] Children { get; set; }
        public Address Address { get; set; }
        public bool IsRegistered { get; set; }
        public override string ToString()
        {
            //return JsonSerializer.Serialize(this);
            return JsonConvert.SerializeObject(this);
        }
    }

    public class Parent
    {
        public string FamilyName { get; set; }
        public string FirstName { get; set; }
    }

    public class Child
    {
        public string FamilyName { get; set; }
        public string FirstName { get; set; }
        public string Gender { get; set; }
        public int Grade { get; set; }
        public Pet[] Pets { get; set; }
    }

    public class Pet
    {
        public string GivenName { get; set; }
    }

    public class Address
    {
        public string State { get; set; }
        public string County { get; set; }
        public string City { get; set; }
    }
}
```

ドキュメントの方では、

```cs
using System.Text.Json;
using System.Text.Json.Serialization;
```
を使って、JSONの処理をしますが、私がこの通りにやった時にうまく動かなかったため、「Newtonsoft.Json」を使いました。
おそらくFamilyクラスのIdをキーに使う際に小文字のidに適用させたいところ、うまく小文字のidにならなかったのが原因なのかなと思います。

## フィールド変数とMain関数の作成

```cs
class Program
    {
        private const string EndpointUrl = "https://<your-account>.documents.azure.com:443/";
        private const string AuthorizationKey = "<your-account-key>";
        private const string DatabaseId = "FamilyDatabase";
        private const string ContainerId = "FamilyContainer";

        static async Task Main(string[] args)
        {

            CosmosClient cosmosClient = new CosmosClient(EndpointUrl, AuthorizationKey);
            await CreateDatabaseAsync(cosmosClient);
            await CreateContainerAsync(cosmosClient);
            await AddItemsToContainerAsync(cosmosClient);
            await QueryItemsAsync(cosmosClient);
            await ReplaceFamilyItemAsync(cosmosClient);
            await DeleteFamilyItemAsync(cosmosClient);
            await DeleteDatabaseAndCleanupAsync(cosmosClient);
        }
}
```
ドキュメント通りです。
フィールド変数のEndpointUrlとAuthorizationKeyは前提で取得しておいたURIとプライマリキーに書き換えればOKです。
ここから、Main関数で実行されている関数たちを定義していきます。

## CreateDatabaseAsync関数の作成

```cs
        /// <summary>
        /// Create the database if it does not exist
        /// </summary>
        private static async Task CreateDatabaseAsync(CosmosClient cosmosClient)
        {
            // Create a new database
            Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync(DatabaseId);
            Console.WriteLine("Created Database: {0}\n", database.Id);
        }
```

databaseの型はCosmosDatabaseではなく、Databaseに書き換えています。

## CreateContainerAsync関数の作成

```cs
        /// <summary>
        /// Create the container if it does not exist. 
        /// Specify "/LastName" as the partition key since we're storing family information, to ensure good distribution of requests and storage.
        /// </summary>
        /// <returns></returns>
        private static async Task CreateContainerAsync(CosmosClient cosmosClient)
        {
            // Create a new container
            Container container = await cosmosClient.GetDatabase(DatabaseId).CreateContainerIfNotExistsAsync(ContainerId, "/LastName");
            Console.WriteLine("Created Container: {0}\n", container.Id);
        }
```

containerの型はCosmosContainerではなく、Containerに書き換えています。

## AddItemsToContainerAsync関数の作成

```cs
        /// <summary>
        /// Add Family items to the container
        /// </summary>
        private static async Task AddItemsToContainerAsync(CosmosClient cosmosClient)
        {
            // Create a family object for the Andersen family
            Family andersenFamily = new Family
            {
                Id = "Andersen.1",
                LastName = "Andersen",
                Parents = new Parent[]
                {
                    new Parent { FirstName = "Thomas" },
                    new Parent { FirstName = "Mary Kay" }
                },
                Children = new Child[]
                {
                    new Child
                    {
                        FirstName = "Henriette Thaulow",
                        Gender = "female",
                        Grade = 5,
                        Pets = new Pet[]
                        {
                            new Pet { GivenName = "Fluffy" }
                        }
                    }
                },
                Address = new Address { State = "WA", County = "King", City = "Seattle" },
                IsRegistered = false
            };

            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            try
            {
                // Read the item to see if it exists.  
                ItemResponse<Family> andersenFamilyResponse = await container.ReadItemAsync<Family>(andersenFamily.Id, new PartitionKey(andersenFamily.LastName));
                Console.WriteLine("Item in database with id: {0} already exists\n", andersenFamilyResponse.Resource.Id);
            }
            catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
            {
                // Create an item in the container representing the Andersen family. Note we provide the value of the partition key for this item, which is "Andersen"
                ItemResponse<Family> andersenFamilyResponse = await container.CreateItemAsync<Family>(andersenFamily, new PartitionKey(andersenFamily.LastName));

                // Note that after creating the item, we can access the body of the item with the Resource property off the ItemResponse.
                Console.WriteLine("Created item in database with id: {0}\n", andersenFamilyResponse.Resource.Id);
            }

            // Create a family object for the Wakefield family
            Family wakefieldFamily = new Family
            {
                Id = "Wakefield.7",
                LastName = "Wakefield",
                Parents = new Parent[]
                {
                    new Parent { FamilyName = "Wakefield", FirstName = "Robin" },
                    new Parent { FamilyName = "Miller", FirstName = "Ben" }
                },
                Children = new Child[]
                {
                    new Child
                    {
                        FamilyName = "Merriam",
                        FirstName = "Jesse",
                        Gender = "female",
                        Grade = 8,
                        Pets = new Pet[]
                        {
                            new Pet { GivenName = "Goofy" },
                            new Pet { GivenName = "Shadow" }
                        }
                    },
                    new Child
                    {
                        FamilyName = "Miller",
                        FirstName = "Lisa",
                        Gender = "female",
                        Grade = 1
                    }
                },
                Address = new Address { State = "NY", County = "Manhattan", City = "NY" },
                IsRegistered = true
            };

            // Create an item in the container representing the Wakefield family. Note we provide the value of the partition key for this item, which is "Wakefield"
            ItemResponse<Family> wakefieldFamilyResponse = await container.UpsertItemAsync<Family>(wakefieldFamily, new PartitionKey(wakefieldFamily.LastName));

            // Note that after creating the item, we can access the body of the item with the Resource property off the ItemResponse. We can also access the RequestCharge property to see the amount of RUs consumed on this request.
            Console.WriteLine("Created item in database with id: {0}\n", wakefieldFamilyResponse.Resource.Id);
        }
```

id取得の際にドキュメントでは
andersenFamilyResponse.Value.IdやwakefieldFamilyResponse.Value.Idといった形で取得していますが、うまく取得できませんでした。
私の場合はandersenFamilyResponse.Resource.IdやwakefieldFamilyResponse.Resource.Idに書き換えることで取得できるようになりました。

## QueryItemsAsync関数の作成

```cs
        /// <summary>
        /// Run a query (using Azure Cosmos DB SQL syntax) against the container
        /// </summary>
        private static async Task QueryItemsAsync(CosmosClient cosmosClient)
        {
            //var sqlQueryText = "SELECT * FROM c WHERE c.LastName = 'Andersen'";
            var sqlQueryText = "SELECT * FROM c";

            Console.WriteLine("Running query: {0}\n", sqlQueryText);

            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            QueryDefinition queryDefinition = new QueryDefinition(sqlQueryText);

            //List<Family> families = new List<Family>();

            using (FeedIterator<Family> feedIterator = container.GetItemQueryIterator<Family>(queryDefinition))
            {
                while (feedIterator.HasMoreResults)
                {
                    FeedResponse<Family> response = await feedIterator.ReadNextAsync();

                    foreach (Family family in response)
                    {
                        //families.Add(family);
                        Console.WriteLine("\tRead {0}\n", family);
                    }
                }

            }
        }
```

ここは、割とドキュメント通りには動きませんでした。上記のように修正することでうまく動くようになります。
やりたいことは同じだと思います。ちなみに、familiesのリストがありますが、使わなかったのでコメントアウトしています。

## ReplaceFamilyItemAsync関数の作成

```cs
        /// <summary>
        /// Replace an item in the container
        /// </summary>
        private static async Task ReplaceFamilyItemAsync(CosmosClient cosmosClient)
        {
            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            ItemResponse<Family> wakefieldFamilyResponse = await container.ReadItemAsync<Family>("Wakefield.7", new PartitionKey("Wakefield"));
            Family itemBody = wakefieldFamilyResponse;

            // update registration status from false to true
            itemBody.IsRegistered = true;
            // update grade of child
            itemBody.Children[0].Grade = 6;

            // replace the item with the updated content
            wakefieldFamilyResponse = await container.ReplaceItemAsync<Family>(itemBody, itemBody.Id, new PartitionKey(itemBody.LastName));
            Console.WriteLine("Updated Family [{0},{1}].\n \tBody is now: {2}\n", itemBody.LastName, itemBody.Id, wakefieldFamilyResponse.Resource);
        }
```
wakefieldFamilyResponse.Valueではなく、wakefieldFamilyResponse.Resourceぐらいでほとんどドキュメント通りです。

## DeleteFamilyItemAsync関数の作成

```cs
        /// <summary>
        /// Delete an item in the container
        /// </summary>
        private static async Task DeleteFamilyItemAsync(CosmosClient cosmosClient)
        {
            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            string partitionKeyValue = "Wakefield";
            string familyId = "Wakefield.7";

            // Delete an item. Note we must provide the partition key value and id of the item to delete
            ItemResponse<Family> wakefieldFamilyResponse = await container.DeleteItemAsync<Family>(familyId, new PartitionKey(partitionKeyValue));
            Console.WriteLine("Deleted Family [{0},{1}]\n", partitionKeyValue, familyId);
        }
```
containerの型はCosmosContainerではなく、Containerに書き換えています。

## DeleteDatabaseAndCleanupAsync関数の作成

```cs
        /// <summary>
        /// Delete the database and dispose of the Cosmos Client instance
        /// </summary>
        private static async Task DeleteDatabaseAndCleanupAsync(CosmosClient cosmosClient)
        {
            Database database = cosmosClient.GetDatabase(DatabaseId);
            DatabaseResponse databaseResourceResponse = await database.DeleteAsync();

            Console.WriteLine("Deleted Database: {0}\n", DatabaseId);
        }
```

databaseの型はCosmosDatabaseではなく、Databaseに書き換えています。

# 全体のソースコード

```cs
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using todo;

namespace AzureCosmosDBAPI
{
    class Program
    {
        private const string EndpointUrl = "https://<your-account>.documents.azure.com:443/";
        private const string AuthorizationKey = "<your-account-key>";
        private const string DatabaseId = "FamilyDatabase";
        private const string ContainerId = "FamilyContainer";

        static async Task Main(string[] args)
        {

            CosmosClient cosmosClient = new CosmosClient(EndpointUrl, AuthorizationKey);
            await CreateDatabaseAsync(cosmosClient);
            await CreateContainerAsync(cosmosClient);
            await AddItemsToContainerAsync(cosmosClient);
            await QueryItemsAsync(cosmosClient);
            await ReplaceFamilyItemAsync(cosmosClient);
            await DeleteFamilyItemAsync(cosmosClient);
            await DeleteDatabaseAndCleanupAsync(cosmosClient);
        }

        /// <summary>
        /// Create the database if it does not exist
        /// </summary>
        private static async Task CreateDatabaseAsync(CosmosClient cosmosClient)
        {
            // Create a new database
            Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync(DatabaseId);
            Console.WriteLine("Created Database: {0}\n", database.Id);
        }

        /// <summary>
        /// Create the container if it does not exist. 
        /// Specify "/LastName" as the partition key since we're storing family information, to ensure good distribution of requests and storage.
        /// </summary>
        /// <returns></returns>
        private static async Task CreateContainerAsync(CosmosClient cosmosClient)
        {
            // Create a new container
            Container container = await cosmosClient.GetDatabase(DatabaseId).CreateContainerIfNotExistsAsync(ContainerId, "/LastName");
            Console.WriteLine("Created Container: {0}\n", container.Id);
        }

        /// <summary>
        /// Add Family items to the container
        /// </summary>
        private static async Task AddItemsToContainerAsync(CosmosClient cosmosClient)
        {
            // Create a family object for the Andersen family
            Family andersenFamily = new Family
            {
                Id = "Andersen.1",
                LastName = "Andersen",
                Parents = new Parent[]
                {
                    new Parent { FirstName = "Thomas" },
                    new Parent { FirstName = "Mary Kay" }
                },
                Children = new Child[]
                {
                    new Child
                    {
                        FirstName = "Henriette Thaulow",
                        Gender = "female",
                        Grade = 5,
                        Pets = new Pet[]
                        {
                            new Pet { GivenName = "Fluffy" }
                        }
                    }
                },
                Address = new Address { State = "WA", County = "King", City = "Seattle" },
                IsRegistered = false
            };

            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            try
            {
                // Read the item to see if it exists.  
                ItemResponse<Family> andersenFamilyResponse = await container.ReadItemAsync<Family>(andersenFamily.Id, new PartitionKey(andersenFamily.LastName));
                Console.WriteLine("Item in database with id: {0} already exists\n", andersenFamilyResponse.Resource.Id);
            }
            catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
            {
                // Create an item in the container representing the Andersen family. Note we provide the value of the partition key for this item, which is "Andersen"
                ItemResponse<Family> andersenFamilyResponse = await container.CreateItemAsync<Family>(andersenFamily, new PartitionKey(andersenFamily.LastName));

                // Note that after creating the item, we can access the body of the item with the Resource property off the ItemResponse.
                Console.WriteLine("Created item in database with id: {0}\n", andersenFamilyResponse.Resource.Id);
            }

            // Create a family object for the Wakefield family
            Family wakefieldFamily = new Family
            {
                Id = "Wakefield.7",
                LastName = "Wakefield",
                Parents = new Parent[]
                {
                    new Parent { FamilyName = "Wakefield", FirstName = "Robin" },
                    new Parent { FamilyName = "Miller", FirstName = "Ben" }
                },
                Children = new Child[]
                {
                    new Child
                    {
                        FamilyName = "Merriam",
                        FirstName = "Jesse",
                        Gender = "female",
                        Grade = 8,
                        Pets = new Pet[]
                        {
                            new Pet { GivenName = "Goofy" },
                            new Pet { GivenName = "Shadow" }
                        }
                    },
                    new Child
                    {
                        FamilyName = "Miller",
                        FirstName = "Lisa",
                        Gender = "female",
                        Grade = 1
                    }
                },
                Address = new Address { State = "NY", County = "Manhattan", City = "NY" },
                IsRegistered = true
            };

            // Create an item in the container representing the Wakefield family. Note we provide the value of the partition key for this item, which is "Wakefield"
            ItemResponse<Family> wakefieldFamilyResponse = await container.UpsertItemAsync<Family>(wakefieldFamily, new PartitionKey(wakefieldFamily.LastName));

            // Note that after creating the item, we can access the body of the item with the Resource property off the ItemResponse. We can also access the RequestCharge property to see the amount of RUs consumed on this request.
            Console.WriteLine("Created item in database with id: {0}\n", wakefieldFamilyResponse.Resource.Id);
        }

        /// <summary>
        /// Run a query (using Azure Cosmos DB SQL syntax) against the container
        /// </summary>
        private static async Task QueryItemsAsync(CosmosClient cosmosClient)
        {
            //var sqlQueryText = "SELECT * FROM c WHERE c.LastName = 'Andersen'";
            var sqlQueryText = "SELECT * FROM c";

            Console.WriteLine("Running query: {0}\n", sqlQueryText);

            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            QueryDefinition queryDefinition = new QueryDefinition(sqlQueryText);

            //List<Family> families = new List<Family>();

            using (FeedIterator<Family> feedIterator = container.GetItemQueryIterator<Family>(queryDefinition))
            {
                while (feedIterator.HasMoreResults)
                {
                    FeedResponse<Family> response = await feedIterator.ReadNextAsync();

                    foreach (Family family in response)
                    {
                        //families.Add(family);
                        Console.WriteLine("\tRead {0}\n", family);
                    }
                }

            }
        }

        /// <summary>
        /// Replace an item in the container
        /// </summary>
        private static async Task ReplaceFamilyItemAsync(CosmosClient cosmosClient)
        {
            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            ItemResponse<Family> wakefieldFamilyResponse = await container.ReadItemAsync<Family>("Wakefield.7", new PartitionKey("Wakefield"));
            Family itemBody = wakefieldFamilyResponse;

            // update registration status from false to true
            itemBody.IsRegistered = true;
            // update grade of child
            itemBody.Children[0].Grade = 6;

            // replace the item with the updated content
            wakefieldFamilyResponse = await container.ReplaceItemAsync<Family>(itemBody, itemBody.Id, new PartitionKey(itemBody.LastName));
            Console.WriteLine("Updated Family [{0},{1}].\n \tBody is now: {2}\n", itemBody.LastName, itemBody.Id, wakefieldFamilyResponse.Resource);
        }

        /// <summary>
        /// Delete an item in the container
        /// </summary>
        private static async Task DeleteFamilyItemAsync(CosmosClient cosmosClient)
        {
            Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);

            string partitionKeyValue = "Wakefield";
            string familyId = "Wakefield.7";

            // Delete an item. Note we must provide the partition key value and id of the item to delete
            ItemResponse<Family> wakefieldFamilyResponse = await container.DeleteItemAsync<Family>(familyId, new PartitionKey(partitionKeyValue));
            Console.WriteLine("Deleted Family [{0},{1}]\n", partitionKeyValue, familyId);
        }

        /// <summary>
        /// Delete the database and dispose of the Cosmos Client instance
        /// </summary>
        private static async Task DeleteDatabaseAndCleanupAsync(CosmosClient cosmosClient)
        {
            Database database = cosmosClient.GetDatabase(DatabaseId);
            DatabaseResponse databaseResourceResponse = await database.DeleteAsync();

            Console.WriteLine("Deleted Database: {0}\n", DatabaseId);
        }
    }
}

```

# おわりに
全体的に動くように、細かい部分で書き換えています。
説明できずに書き換えている箇所もあるかもしれませんが、ご理解お願いします。