{"id":1802,"date":"2020-10-07T09:00:53","date_gmt":"2020-10-07T16:00:53","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cosmosdb\/?p=1802"},"modified":"2020-10-07T08:51:29","modified_gmt":"2020-10-07T15:51:29","slug":"azure-cosmos-db-repository-net-sdk-v-1-0-4","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cosmosdb\/azure-cosmos-db-repository-net-sdk-v-1-0-4\/","title":{"rendered":"Azure Cosmos DB Repository .NET SDK v.1.0.4"},"content":{"rendered":"<p>I&#8217;m excited to share the <a href=\"https:\/\/www.nuget.org\/packages\/IEvangelist.Azure.CosmosRepository\/1.0.4\" target=\"_blank\" rel=\"noopener noreferrer\">Azure Cosmos DB Repository .NET SDK<\/a>. It&#8217;s an <em>unofficial<\/em> SDK that wraps the official <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Azure.Cosmos\/3.13.0\">Azure Cosmos .NET SDK<\/a>. The Repository Pattern is a useful abstraction between your application&#8217;s data and the business logic layer. This has been a passion project of mine for a long time, dating four years back to <a href=\"https:\/\/github.com\/IEvangelist\/IEvangelist.DocumentDb\">&#8220;Document DB&#8221;<\/a>!<\/p>\n<p><div class=\"alert alert-success\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Lightbulb\"><\/i><strong>Just another tool \ud83d\udd27<\/strong><\/p>This is <strong>not<\/strong> a replacement of the existing Azure Cosmos DB .NET SDK. Instead, it&#8217;s another tool in your developer toolbox.<\/div><\/p>\n<p><a href=\"https:\/\/www.nuget.org\/packages\/IEvangelist.Azure.CosmosRepository\/1.0.4\" target=\"_blank\" rel=\"noopener noreferrer\">The repository SDK<\/a> is currently being used in production for <a href=\"https:\/\/dotnetdocs.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\">The .NET Docs Show<\/a> as part of <a href=\"https:\/\/dotnet.microsoft.com\/live\" target=\"_blank\" rel=\"noopener noreferrer\">.NET Live TV<\/a>, and is open-source on GitHub:<\/p>\n<p>\u2611 <a href=\"https:\/\/github.com\/IEvangelist\/DotNetDocs.Show\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/github.com\/IEvangelist\/DotNetDocs.Show<\/a><\/p>\n<p>While implementing the Repository Pattern, the SDK simplifies the consumption of Azure Cosmos DB by abstracting away some of the underlying complexities of the existing .NET SDK. There are always tradeoffs that you must consider. The repository SDK has a much smaller API surface area and is easier to get started with, whereas the proper SDK has many more capabilities and a much larger API surface area to learn.<\/p>\n<p>The repository SDK exposes all the common data access operations (CRUD) you&#8217;d expect to find in a modern, generic Repository Pattern-based interface.<\/p>\n<ul>\n<li>Create<\/li>\n<li>Read<\/li>\n<li>Update<\/li>\n<li>Delete<\/li>\n<\/ul>\n<pre class=\"prettyprint\">public interface IRepository&lt;TItem&gt; where TItem : Item\r\n{\r\n    \/\/ Create\r\n    ValueTask&lt;TItem&gt; CreateAsync(TItem value);\r\n    Task&lt;TItem[]&gt; CreateAsync(IEnumerable values);\r\n\r\n    \/\/ Read\r\n    ValueTask&lt;TItem&gt; GetAsync(string id);\r\n    ValueTask&lt;IEnumerable&lt;TItem&gt;&gt; GetAsync(\r\n        Expression&lt;Func&lt;TItem, bool&gt;&gt; predicate);\r\n\r\n    \/\/ Update\r\n    ValueTask&lt;TItem&gt; UpdateAsync(TItem value);\r\n\r\n    \/\/ Delete\r\n    ValueTask DeleteAsync(TItem value);\r\n    ValueTask DeleteAsync(string id);\r\n}\r\n<\/pre>\n<p>You may have noticed the generic type constraint of <code>Item<\/code>. The <code>Item<\/code> object is a required base class, which automatically assigns a globally unique identifier (GUID) and manages partitioning. By targeting .NET Standard 2.0, the SDK can be consumed by any supported <a href=\"https:\/\/docs.microsoft.com\/dotnet\/standard\/net-standard?WC.m_id=dapine#net-implementation-support\">.NET implementation<\/a> that aligns with .NET Standard 2.0. The SDK follows the common nomenclature and naming conventions established by .NET and supports <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/configuration?WC.m_id=dapine\" target=\"_blank\" rel=\"noopener noreferrer\">configuration<\/a> and <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/dependency-injection?WC.m_id=dapine\" target=\"_blank\" rel=\"noopener noreferrer\">dependency injection<\/a>.<\/p>\n<h2>Get started<\/h2>\n<p>All you need to get started is an Azure Cosmos DB resource, connection string, and the <a href=\"https:\/\/www.nuget.org\/packages\/IEvangelist.Azure.CosmosRepository\/1.0.4\">Azure Cosmos DB Repository .NET SDK<\/a>. Add Cosmos repository to your <code>IServiceCollection<\/code> instance passing the app&#8217;s <code>IConfiguration<\/code>:<\/p>\n<pre class=\"prettyprint\">public void ConfigureServices(IServiceCollection services)\r\n{\r\n    services.AddCosmosRepository(Configuration);\r\n}\r\n<\/pre>\n<p>An overload exposes the configuration options to manually configure your app:<\/p>\n<pre class=\"prettyprint\">public void ConfigureServices(IServiceCollection services)\r\n{\r\n    services.AddCosmosRepository(Configuration,\r\n        options =&gt;\r\n        {\r\n            options.CosmosConnectionString = \"&lt; Your connection string &gt;\";\r\n        });\r\n}\r\n<\/pre>\n<h2>Configuration<\/h2>\n<p>The only required configuration is the Cosmos DB connection string. Optionally, consumers can specify a database and container identifier. If these identifiers are not set, default values are used.<\/p>\n<pre class=\"prettyprint\">public class RepositoryOptions\r\n{\r\n    public string CosmosConnectionString { get; set; }\r\n\r\n    public string DatabaseId { get; set; } = \"database\";\r\n    public string ContainerId { get; set; } = \"container\";\r\n\r\n    public bool OptimizeBandwidth { get; set; } = true;\r\n    public bool ContainerPerItemType { get; set; } = false;\r\n}\r\n<\/pre>\n<p>When <code>OptimizeBandwidth<\/code> is <code>true<\/code> (its default value), the repository SDK reduces networking and CPU load by not sending the resource back over the network and serializing it to the client. This is specific to writes, such as create, update, and delete. For more information, see <a href=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/enable-content-response-on-write\/\" target=\"_blank\" rel=\"noopener noreferrer\">Optimizing bandwidth in the Azure Cosmos DB .NET SDK<\/a>.<\/p>\n<p>There is much debate with how to structure your database and corresponding containers. Many developers with relational database design experience might prefer to have a single container per item type, while others understand that Azure Cosmos DB will handle things correctly regardless. By default, the <code>ContainerPerItemType<\/code> option is <code>false<\/code> and all items are persisted into the same container. However, when it is <code>true<\/code>, each distinct subclass of <code>Item<\/code> gets its own container named by the class itself. For example, an item defined as:<\/p>\n<pre class=\"prettyprint\">public class Foo : Item \r\n{\r\n    \/\/ Omitted for brevity...\r\n}\r\n<\/pre>\n<p>would be stored in a container named &#8220;Foo&#8221;. This could be useful for administrative purposes from the Azure portal and the Azure Cosmos DB resource Data Explorer.<\/p>\n<p>Databases and containers do not have to exist prior to persisting to them. Again, only the connection string to the existing Azure Cosmos DB resource is required \u2013 databases and containers will be automatically created if they do not already exist.<\/p>\n<h3>Well-known keys<\/h3>\n<p>Depending on the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/extensions\/configuration-providers?WC.m_id=dapine\">.NET configuration provider<\/a> your app is using, there are several well-known keys that map to the repository options that configure your usage of the repository SDK. When using environment variables, such as those in Azure App Service configuration or Azure Key Vault secrets, the following keys map to the <code>RepositoryOptions<\/code> instance:<\/p>\n<table style=\"border-collapse: collapse; width: 100%;\">\n<tbody>\n<tr>\n<td style=\"width: 60%;\">Key<\/td>\n<td style=\"width: 20%;\">Data type<\/td>\n<td style=\"width: 20%;\">Default value<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 60%;\"><code>RepositoryOptions__CosmosConnectionString<\/code><\/td>\n<td style=\"width: 20%;\">string<\/td>\n<td style=\"width: 20%;\"><code>null<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"width: 60%;\"><code>RepositoryOptions__DatabaseId<\/code><\/td>\n<td style=\"width: 20%;\">string<\/td>\n<td style=\"width: 20%;\"><code>\"database\"<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"width: 60%;\"><code>RepositoryOptions__ContainerId<\/code><\/td>\n<td style=\"width: 20%;\">string<\/td>\n<td style=\"width: 20%;\"><code>\"container\"<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"width: 60%;\"><code>RepositoryOptions__OptimizeBandwidth<\/code><\/td>\n<td style=\"width: 20%;\">boolean<\/td>\n<td style=\"width: 20%;\"><code>true<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"width: 60%;\"><code>RepositoryOptions__ContainerPerItemType<\/code><\/td>\n<td style=\"width: 20%;\">boolean<\/td>\n<td style=\"width: 20%;\"><code>false<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If you&#8217;re using the JSON configuration provider, you map to the following JSON:<\/p>\n<pre class=\"prettyprint\">{\r\n    \"RepositoryOptions\": {\r\n        \"CosmosConnectionString\": \"&lt; your connection string &gt;\",\r\n        \"DatabaseId\": \"database\",\r\n        \"ContainerId\": \"container\",\r\n        \"OptimizeBandwidth\": true,\r\n        \"ContainerPerItemType\": false\r\n}\r\n<\/pre>\n<h2>Item objects<\/h2>\n<p>To use the <code>IRespository&lt;TItem&gt;<\/code>, you must first have at least one object that you want to persist. Any item that you want to store must be a subclass of the <code>Item<\/code> type. As an example, imagine the two following C# classes:<\/p>\n<pre class=\"prettyprint\">using System.Collections.Generic;\r\n\r\npublic class GameResult : Item\r\n{\r\n    public Name { get; set; }\r\n    public GameOutcome Outcome { get; set; }\r\n    public TimeSpan Duration { get; set; }\r\n    public IEnumerable&lt;Player&gt; Players { get; set; }\r\n}\r\n\r\npublic enum GameOutcome\r\n{\r\n    NotCompleted = 0,\r\n    Tie,\r\n    PlayerOneWins,\r\n    PlayerTwoWins\r\n}\r\n<\/pre>\n<p>Next, a simple <code>Player<\/code> object:<\/p>\n<pre class=\"prettyprint\">public class Player\r\n{\r\n    public Username { get; set; }\r\n    public PlayerPosition Position { get; set; }\r\n}\r\n\r\npublic enum PlayerPosition\r\n{\r\n    One, Two\r\n}\r\n<\/pre>\n<h2>Use the repository<\/h2>\n<p>The repository SDK comes dependency injection ready, meaning once it&#8217;s added to the service collection \u2013 you can request it to be injected where you need it. This is where the magic of generic dependency injection comes into fruition. Any subclass of <code>Item<\/code> gets its own corresponding <code>IRepository&lt;TItem&gt;<\/code> instance. Imagine you have a consuming service that is represented as a game engine class:<\/p>\n<pre class=\"prettyprint\">public class GameEngineService\r\n{\r\n    readonly IRepository&lt;GameResult&gt; _gameResultRepository;\r\n\r\n    public GameEngineService(\r\n        IRepository&lt;GameResult&gt; gameResultRepository) =&gt;\r\n        _gameResultRepository = gameResultRepository;\r\n}\r\n<\/pre>\n<p>The same is true when using <code>IRepository&lt;TItem&gt;<\/code> instances in ASP.NET Core controllers, or background services \u2013 where dependency injection is common form.<\/p>\n<h3>Create<\/h3>\n<p>To create items, use either of the provided <code>CreateAsync<\/code> overloads. Consider the following code, which persists a single game result.<\/p>\n<pre class=\"prettyprint\">public async ValueTask&lt;GameResult&gt; CreateSingleGameResultExampleAsync()\r\n{\r\n    GameResult gameResult =\r\n        await _gameResultRepository.CreateAsync(new GameResult\r\n        {\r\n            Name = \"Chess\",\r\n            Outcome = GameOutcome.PlayerTwoWins,\r\n            Duration = TimeSpan.FromMinutes(20),\r\n            Players = new[]\r\n            {\r\n                new Player\r\n                {\r\n                    Username = \"davidpine\",\r\n                    Position = PlayerPosition.Two\r\n                },\r\n                new Player\r\n                {\r\n                    Username = \"bradygaster\",\r\n                    Position = PlayerPosition.One\r\n                }\r\n            }\r\n        });\r\n\r\n    return gameResult; \/\/ gameResult.Id = \"7F8D7CA9-1434-4A0C-841A-94D59BF22121\"\r\n}\r\n<\/pre>\n<h3>Read<\/h3>\n<p>Much like the create functionality, there are two overloads for reading items. Given the previous example, imagine the <code>gameResult<\/code> had an <code>Id<\/code> of &#8220;7F8D7CA9-1434-4A0C-841A-94D59BF22121&#8221; \u2013 you could read it by passing the identifier to the <code>GetAsync<\/code> function.<\/p>\n<pre class=\"prettyprint\">public async ValueTask ReadSingleGameResultExampleAsync()\r\n{\r\n    GameResult gameResult =\r\n        await _gameResultRepository.GetAsync(\r\n            \"7F8D7CA9-1434-4A0C-841A-94D59BF22121\");\r\n\r\n    \/\/ Interact with result object\r\n}\r\n<\/pre>\n<p>The other overload exposes the ability to express a predicate, much like Linq to SQL.<\/p>\n<pre class=\"prettyprint\">public async ValueTask ReadMultipleGameResultsExampleAsync()\r\n{\r\n    IEnumerable&lt;GameResult&gt; tiedGameResults =\r\n        await _gameResultRepository.GetAsync(\r\n            game =&gt; game.Outcome == GameOutcome.Tie);\r\n\r\n    \/\/ Interact with tied result objects\r\n}\r\n<\/pre>\n<p>In the preceding code example, the game repository would read all of the game result instances where the outcome of the game was a tie.<\/p>\n<h3>Update<\/h3>\n<p>To update an item, it must first be read into memory so that the <code>Id<\/code> member is hydrated. Mutations of the item occur, then you call <code>UpdateAsync<\/code> to persist all changes:<\/p>\n<pre class=\"prettyprint\">public async ValueTask UpdateGameResultsExampleAsync()\r\n{\r\n    GameResult gameResult =\r\n        await _gameResultRepository.GetAsync(\r\n            \"7F8D7CA9-1434-4A0C-841A-94D59BF22121\");\r\n\r\n    gameResult.Outcome = GameOutcome.Tied;\r\n    _ = await _gameResultRepository.UpdateAsync(gameResult);\r\n}\r\n<\/pre>\n<p>In this case, the game result instance was the same as the one that was read out \u2013 it can be discarded.<\/p>\n<h3>Delete<\/h3>\n<p>The delete operations boast two overloads of <code>DeleteAsync<\/code>. One method takes the <code>TItem<\/code> instance to delete and the other takes the <code>Id<\/code> of the item to delete.<\/p>\n<pre class=\"prettyprint\">public async ValueTask DeleteSingleGameResultExampleAsync()\r\n{\r\n    await _gameResultRepository.DeleteAsync(\r\n        \"7F8D7CA9-1434-4A0C-841A-94D59BF22121\");\r\n}\r\n<\/pre>\n<h2>Conclusion<\/h2>\n<p>As a reminder, the <a href=\"https:\/\/www.nuget.org\/packages\/IEvangelist.Azure.CosmosRepository\/1.0.4\">Azure Cosmos DB Repository .NET SDK<\/a> is <em>unofficial<\/em>, but hopefully one day it will be adopted by the engineering team \u2013 and supported in an official capacity. For now, enjoy it as an open-source project from a passionate developer at Microsoft \ud83e\udd18\ud83c\udffc.<\/p>\n<p>Feel free to give it a star, fork it, post issues against it, or provide pull requests to improve it. I look forward to collaborating with you all and hope that you find value in this!<\/p>\n<p><div  class=\"d-flex justify-content-center\"><a class=\"cta_button_link btn-primary mb-24\" href=\"https:\/\/www.nuget.org\/packages\/IEvangelist.Azure.CosmosRepository\" target=\"_blank\">Repository .NET SDK - NuGet package<\/a><\/div> <div  class=\"d-flex justify-content-center\"><a class=\"cta_button_link btn-primary mb-24\" href=\"https:\/\/github.com\/IEvangelist\/azure-cosmos-dotnet-repository\" target=\"_blank\">Open-source GitHub repo<\/a><\/div><\/p>\n<p><div class=\"alert alert-info\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Scaling and partitioning<\/strong><\/p>As long as your scale needs fit on a single physical partition (configured at 50 GB and 10 K RU\/sec), partition keys do not matter. However, if you need more this SDK is <strong>not<\/strong> currently ideal.<\/div><\/p>\n","protected":false},"excerpt":{"rendered":"<p>David Pine shares the unofficial Azure Cosmos DB Repository .NET SDK. The repository SDK aims to simplify the consumption of Azure Cosmos DB by abstracting away some of the complexities of the existing .NET SDK and exposes a modern generic repository pattern-based interface.<\/p>\n","protected":false},"author":24662,"featured_media":1830,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[12,14],"tags":[539,740,499],"class_list":["post-1802","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-announcements","category-core-sql-api","tag-net","tag-appdev","tag-azure-cosmos-db"],"acf":[],"blog_post_summary":"<p>David Pine shares the unofficial Azure Cosmos DB Repository .NET SDK. The repository SDK aims to simplify the consumption of Azure Cosmos DB by abstracting away some of the complexities of the existing .NET SDK and exposes a modern generic repository pattern-based interface.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/1802","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/users\/24662"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/comments?post=1802"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/1802\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/media\/1830"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/media?parent=1802"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/categories?post=1802"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/tags?post=1802"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}