{"id":13767131,"url":"https://github.com/Elfocrash/Cosmonaut","last_synced_at":"2025-05-10T22:31:31.502Z","repository":{"id":41478867,"uuid":"126886464","full_name":"Elfocrash/Cosmonaut","owner":"Elfocrash","description":"🌐 A supercharged Azure CosmosDB .NET SDK with ORM support","archived":true,"fork":false,"pushed_at":"2020-07-12T16:41:00.000Z","size":2004,"stargazers_count":340,"open_issues_count":29,"forks_count":44,"subscribers_count":23,"default_branch":"develop","last_synced_at":"2025-05-06T20:55:46.920Z","etag":null,"topics":["azure","azure-cosmos-db","azure-cosmosdb","cosmonaut","cosmos-db","cosmos-sdk","cosmosdb","cosmosdb-orm","cosmosdbsdk","csharp","dotnetcore","library","netstandard","object-mapper","orm","storage"],"latest_commit_sha":null,"homepage":"https://cosmonaut.readthedocs.io","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Elfocrash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["Elfocrash"]}},"created_at":"2018-03-26T20:32:27.000Z","updated_at":"2025-04-22T04:35:23.000Z","dependencies_parsed_at":"2022-09-13T02:23:15.226Z","dependency_job_id":null,"html_url":"https://github.com/Elfocrash/Cosmonaut","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elfocrash%2FCosmonaut","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elfocrash%2FCosmonaut/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elfocrash%2FCosmonaut/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Elfocrash%2FCosmonaut/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Elfocrash","download_url":"https://codeload.github.com/Elfocrash/Cosmonaut/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253492529,"owners_count":21916959,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure","azure-cosmos-db","azure-cosmosdb","cosmonaut","cosmos-db","cosmos-sdk","cosmosdb","cosmosdb-orm","cosmosdbsdk","csharp","dotnetcore","library","netstandard","object-mapper","orm","storage"],"created_at":"2024-08-03T16:01:05.052Z","updated_at":"2025-05-10T22:31:30.712Z","avatar_url":"https://github.com/Elfocrash.png","language":"C#","readme":"### !Attention! Cosmonaut is now obsolete and in read-only mode. You can still safely use it until Microsoft announces a deprecation of the v2 SDK but I highly recommend you use the official Microsoft SDK. \n\n[![Build Status](https://dev.azure.com/nickchapsas/Cosmonaut/_apis/build/status/Elfocrash.Cosmonaut)](https://dev.azure.com/nickchapsas/Cosmonaut/_build/latest?definitionId=2) [![NuGet Package](https://img.shields.io/nuget/v/Cosmonaut.svg)](https://www.nuget.org/packages/Cosmonaut) [![NuGet](https://img.shields.io/nuget/dt/cosmonaut.svg)](https://www.nuget.org/packages/cosmonaut) [![Documentation Status](https://readthedocs.org/projects/cosmonaut/badge/?version=latest)](https://cosmonaut.readthedocs.io/en/latest/?badge=latest) [![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Elfocrash/Cosmonaut/blob/master/LICENSE)\n\n# Cosmonaut\n\n![](https://raw.githubusercontent.com/Elfocrash/Cosmonaut/develop/logo.png)\n\n\u003e The word was derived from \"kosmos\" (Ancient Greek: κόσμος) which means world/universe and \"nautes\" (Ancient Greek: ναῦς) which means sailor/navigator\n\nCosmonaut is a supercharged SDK with object mapping capabilities that enables .NET developers to work with CosmosDB. It eliminates the need for most of the data-access code that developers usually need to write.\n\n### [Documentation](https://cosmonaut.readthedocs.io/en/latest)\n\n### Getting started\n\n- [How to easily start using CosmosDB in your C# application in no time with Cosmonaut](http://chapsas.com/how-to-easily-start-using-cosmosdb-in-your-c-application-in-no-time-with-cosmonaut/)\n- [(Video) Getting started with .NET Core and CosmosDB using Cosmonaut](http://chapsas.com/video-getting-started-with-net-core-and-cosmosdb-using-cosmonaut/)\n- [(Video) How to save money in CosmosDB with Cosmonaut's Collection Sharing](http://chapsas.com/video-how-to-save-money-in-cosmosdb-with-cosmonauts-collection-sharing/)\n- [CosmosDB Fluent Pagination with Cosmonaut](http://chapsas.com/cosmosdb-fluent-pagination-with-cosmonaut/)\n- [Implementing server side CosmosDB pagination in a Blazor Web App (Part 1: Page Number and Page Size)\n](https://chapsas.com/implementing-skiptake-server-side-cosmosdb-pagination-in-a-blazor-web-app/)\n- [Implementing server side CosmosDB pagination in a Blazor Web App (Part 2: Next/Previous Page)\n](https://chapsas.com/implementing-server-side-cosmosdb-pagination-in-a-blazor-web-app-part-2-next-page-previous-page/)\n\n### Samples\n - The `samples` folder in this project\n - [Web app server-side pagination for CosmosDB](https://github.com/Elfocrash/CosmosDBPaginationSample)\n\n### Usage \nThe idea is pretty simple. You can have one CosmosStore per entity (POCO/dtos etc).\nThis entity will be used to create a collection or use part of a one in CosmosDB and it will offer all the data access for this object.\n\nRegistering the CosmosStores in ServiceCollection for DI support\n```csharp\n var cosmosSettings = new CosmosStoreSettings(\"\u003c\u003cdatabaseName\u003e\u003e\", \"\u003c\u003ccosmosUri\u003e\u003e\", \"\u003c\u003cauthkey\u003e\u003e\");\n                \nserviceCollection.AddCosmosStore\u003cBook\u003e(cosmosSettings);\n\n//or just by using the Action extension\n\nserviceCollection.AddCosmosStore\u003cBook\u003e(\"\u003c\u003cdatabaseName\u003e\u003e\", \"\u003c\u003ccosmosUri\u003e\u003e\", \"\u003c\u003cauthkey\u003e\u003e\", settings =\u003e\n{\n    settings.ConnectionPolicy = connectionPolicy;\n    settings.DefaultCollectionThroughput = 5000;\n    settings.IndexingPolicy = new IndexingPolicy(new RangeIndex(DataType.Number, -1),\n        new RangeIndex(DataType.String, -1));\n});\n\n//or just initialise the object\n\nICosmosStore\u003cBook\u003e bookStore = new CosmosStore\u003cBook\u003e(cosmosSettings)\n```\n\nTo use the `AddCosmosStore` extension methods you need to install the `Cosmonaut.Extensions.Microsoft.DependencyInjection` package.\n\n```\nInstall-Package Cosmonaut.Extensions.Microsoft.DependencyInjection\nor\ndotnet add package Cosmonaut.Extensions.Microsoft.DependencyInjection\n```\n\n##### Retrieving an entity by id (and partition key)\n\n```csharp\nvar user = await cosmosStore.FindAsync(\"userId\");\nvar user = await cosmosStore.FindAsync(\"userId\", \"partitionKey\");\nvar user = await cosmosStore.FindAsync(\"userId\", new RequestOptions());\n```\n\n##### Querying for entities using LINQ\n\nIn order to query for entities all you have to do is call the `.Query()` method and then use LINQ to create the query you want.\nIt is HIGHLY recommended that you use one of the `Async` methods to get the results back, such as `ToListAsync` or `FirstOrDefaultAsync` , when available.\n\n```csharp\nvar user = await cosmoStore.Query().FirstOrDefaultAsync(x =\u003e x.Username == \"elfocrash\");\nvar users = await cosmoStore.Query().Where(x =\u003e x.HairColor == HairColor.Black).ToListAsync(cancellationToken);\n```\n\n##### Querying for entities using SQL\n\n```csharp\n// plain sql query\nvar user = await cosmoStore.Query(\"select * from c where c.Firstname = 'Smith'\").ToListAsync();\nor\nvar user = await cosmoStore.QueryMultipleAsync(\"select * from c where c.Firstname = 'Smith'\");\n\n// or parameterised sql query\nvar user = await cosmoStore.QueryMultipleAsync(\"select * from c where c.Firstname = @name\", new { name = \"Smith\" });\n```\n\n#### Collection sharing\nCosmonaut is all about making integrating with Cosmos DB easy as well as making things such as cost optimisation part of the library.\n\nThat's why Cosmonaut supports transparent collection sharing between different types of entities.\n\nWhy would you do that?\n\nCosmos is charging you based on how many RU/s your individual collection is provisioned at. This means that if you don't need to have one collection per entity because you won't use it that much, even on the minimum 400 RU/s, you will be charged money. That's where the magic of schemaless comes in.\n\nHow can you do that?\n\nWell it's actually pretty simple. Just implement the `ISharedCosmosEntity` interface and decorate your object with the `SharedCosmosCollection` attribute.\n\nThe attribute accepts two properties, `SharedCollectionName` which is mandatory and `EntityName` which is optional.\nThe `SharedCollectionName` property will be used to name the collection that the entity will share with other entities. \n\nThe `EntityName` will be used to make the object identifiable for Cosmosnaut. By default it will pluralize the name of the class, but you can specify it to override this behavior. You can override this by providing your own name by setting the `EntityName` value at the attribute level.\n\nOnce you set this up you can add individual CosmosStores with shared collections.\n\n\n#### Collection naming\n\nYour collections will automatically be named based on the plural of the object you are using in the generic type.\nHowever you can override that by decorating the class with the `CosmosCollection` attribute.\n\nExample:\n```csharp\n[CosmosCollection(\"somename\")]\n```\n\nBy default you are required to specify your collection name in the attribute level shared entities like this:\n\n```c#\n[SharedCosmosCollection(\"shared\")]\npublic class Car : ISharedCosmosEntity\n{\n    public string Id { get; set; }\n    public string CosmosEntityName { get; set; }\n}\n```\n\nEven though this is convenient I understand that you might need to have a dynamic way of setting this. \nThat's why the `CosmosStore` class has some extra constructors that allow you to specify the `overriddenCollectionName` property. This property will override any collection name specified at the attribute level and will use that one instead.\n\nNote: If you have specified a `CollectionPrefix` at the `CosmosStoreSettings` level it will still be added. You are only overriding the collection name that the attribute would normally set.\n\nExample\n\nClass implementation:\n```c#\n[SharedCosmosCollection(\"shared\")]\npublic class Car : ISharedCosmosEntity\n{\n    public string Id { get; set; }\n    public string CosmosEntityName { get; set; }\n}\n```\n\nCosmosStore initialisation:\n```c#\nvar cosmosStore = new CosmosStore\u003cCar\u003e(someSettings, \"oldcars\");\n```\n\nThe outcome of this would be a collection named `oldcars` because the `shared` collection name is overridden in the constructor. \nThere are also method overloads for the same property at the dependency injection extension level.\n\n#### Pagination\n\nCosmonaut supports two types of pagination.\n\n* Page number + Page size\n* ContinuationToken + Page size\n\nBoth of these methods work by adding the `.WithPagination()` method after you used any of the `Query` methods.\n\n```csharp\nvar firstPage = await booksStore.Query().WithPagination(1, 10).OrderBy(x=\u003ex.Name).ToListAsync();\nvar secondPage = await booksStore.Query().WithPagination(2, 10).OrderBy(x =\u003e x.Name).ToPagedListAsync();\nvar thirdPage = await booksStore.Query().WithPagination(secondPage.NextPageToken, 10).OrderBy(x =\u003e x.Name).ToPagedListAsync();\nvar fourthPage = await thirdPage.GetNextPageAsync();\nvar fifthPage = await booksStore.Query().WithPagination(5, 10).OrderBy(x =\u003e x.Name).ToListAsync();\n```\n\n`ToListAsync()` on a paged query will just return the results. `ToPagedListAsync()` on the other hand will return a `CosmosPagedResults` object. This object contains the results but also a boolean indicating whether there are more pages after the one you just got but also the continuation token you need to use to get the next page.\n\n##### Pagination recommendations\n\nBecause page number + page size pagination goes though all the documents until it gets to the requested page, it's potentially slow and expensive.\nThe recommended approach would be to use the page number + page size approach once for the first page and get the results using the `.ToPagedListAsync()` method. This method will return the next continuation token and it will also tell you if there are more pages for this query. Then use the continuation token alternative of `WithPagination` to continue from your last query.\n\nKeep in mind that this approach means that you have to keep state on the client for the next query, but that's what you'd do if you where using previous/next buttons anyway.\n\n##### Adding an entity in the entity store\n```csharp\nvar newUser = new User\n{\n    Name = \"Nick\"\n};\nvar added = await cosmoStore.AddAsync(newUser);\n\nvar multiple = await cosmoStore.AddRangeAsync(manyManyUsers);\n```\n\n##### Updating entities\nWhen it comes to updating you have two options.\n\nUpdate...\n```csharp\nawait cosmoStore.UpdateAsync(entity);\n```\n\n... and Upsert\n```csharp\nawait cosmoStore.UpsertAsync(entity);\n```\n\nThe main difference is of course in the functionality.\nUpdate will only update if the item you are updating exists in the database with this id.\nUpsert on the other hand will either add the item if there is no item with this id or update it if an item with this id exists.\n\n##### Removing entities\n```csharp\nawait cosmoStore.RemoveAsync(x =\u003e x.Name == \"Nick\"); // Removes all the entities that match the criteria\nawait cosmoStore.RemoveAsync(entity);// Removes the specific entity\nawait cosmoStore.RemoveByIdAsync(\"\u003c\u003canId\u003e\u003e\");// Removes an entity with the specified ID\n```\n\n#### Response Handling\nCosmonaut follows a different approach when it comes to error handling. The CosmosDB SDK is throwing exceptions for almost every type of error. Cosmonaut follows a different approach.\n\nIn Cosmonaut methods that return `CosmosResponse` or `CosmosMultipleResponse` won't throw exceptions for the following errors: `ResourceNotFound`, `PreconditionFailed` and `Conflict`. \nThey will instead return a `CosmosResponse` with the `IsSuccess` flag to `false`, the `CosmosOperationStatus` enum explaining what the error was and the `Exception` object containing the exceptions that caused the request to fail.\n\nOn top of that, any methods that return `ResourceResponse\u003cT\u003e` in the CosmonautClient will not throw an exception for `ResourceNotFound` and they will instead return `null`.\n\n#### Restrictions\nBecause of the way the internal `id` property of Cosmosdb works, there is a mandatory restriction made.\nYou cannot have a property named Id or a property with the attribute `[JsonProperty(\"id\")]` without it being a string.\nA cosmos id needs to exist somehow on your entity model. For that reason if it isn't part of your entity you can just extend the `CosmosEntity` class.\n\nIt is **HIGHLY RECOMMENDED** that you decorate your Id property with the `[JsonProperty(\"id\")]` attribute to prevent any unexpected behaviour.\n\n#### CosmonautClient\n\nCosmonaut has its own version of a `DocumentClient` called `CosmonautClient`. The difference is that the `CosmonautClient` interface is more user friendly and it looks more like something you would use in a real life scenario. It won't throw not found exceptions if an item is not found but it will return `null` instead. It will also retry automatically when you get 429s (too many requests).\n\nIt also has support for logging and monitoring as you are going to see in the logging section of this page.\n\n#### Transactions\n\nThere is currently no way to reliably do transactions with the current CosmosDB SDK. Because Cosmonaut is a wrapper around the CosmosDB SDK it doesn't support them either. However there are plans for investigating potential other ways to achieve transactional operations such as server side stored procedures that Cosmonaut could provision and call.\n\nEvery operational call (Add, Update, Upsert, Delete) however returns it's status back alongside the reason it failed, if it failed, and the entity so you can add your own retry logic.\n\n#### Partitioning\nCosmonaut supports partitions out of the box. You can specify which property you want to be your Partition Key by adding the `[CosmosPartitionKey]` attribute above it.\n\nUnless you really know what you're doing, it is recommended make your `Id` property the Partition Key. This will enable random distribution for your collection.\n\nIf you do not set a Partition Key then the collection created will be single partition. Here is a quote from Microsoft about single partition collections: \n\u003e Single-partition collections have lower price options and the ability to execute queries and perform transactions across all collection data. They have the scalability and storage limits of a single partition (10GB and 10,000 RU/s). You do not have to specify a partition key for these collections. For scenarios that do not need large volumes of storage or throughput, single partition collections are a good fit.\n[link](https://azure.microsoft.com/en-gb/blog/10-things-to-know-about-documentdb-partitioned-collections/)\n\n##### Known hiccups\nPartitions are great but you should these 3 very important things about them and about the way Cosmonaut will react.\n\n* Once a collection is created with a partition key, it cannot be removed or changed.\n* You cannot add a partition key later to a single partition collection.\n* If you use the the Upsert method to update an entity that had the value of the property that is the partition key changed, then CosmosDB won't update the document but instead it will create a whole different document with the same id but the changed partition key value.\n\nMore on the third issue here [Unique keys in Azure Cosmos DB](https://docs.microsoft.com/en-us/azure/cosmos-db/unique-keys)\n\n#### Optimizing for performance\n\nCosmonaut by default will create one `CosmonautClient` (which is really a wrapper around the `DocumentClient`) per `CosmosStore`. The logic behind that decision was that each `CosmosStore` might have different configuration from another even on the client level. However in scenarios where you have tens of CosmosStores this can cause socket starvation. The recommendation in such scenarios is to either reuse the same `CosmonautClient` or to cache the CosmosStores internally and swap them around for different CosmosStores. You can see this issue where a multi tenant scenario is discussed and resolved by the use of a client cache.\n\nIt is also a good idea in general to create a `CosmonautClient` outside of the CosmosStore logic and  reuse the `CosmonautClient` instead of creating one each time if the configuration for the client is the same.\n\n### Logging\n\n#### Event source\n\nCosmonaut uses the .NET Standard's `System.Diagnostics` to log it's actions as dependency events. \nBy default, this system is deactivated. In order to activated and actually do something with those events you need to create an  `EventListener` which will activate the logging and give you the option do something with the logs.\n\n#### `Cosmonaut.ApplicationInsights`\n\nBy using this package you are able to log the events as dependencies in [Application Insights](https://azure.microsoft.com/en-gb/services/application-insights/) in detail. The logs are batched and send in intervals OR automatically sent when the batch buffer is filled to max.\n\nJust initialise the AppInsightsTelemetryModule in your Startup or setup pipeline like this.\nExample: `AppInsightsTelemetryModule.Instance.Initialize(new TelemetryConfiguration(\"InstrumentationKey\"))`\n\nIf you already have initialised `TelemetryConfiguration` for your application then use `TelemetryConfiguration.Active` instead of `new TelemetryConfiguration` because if you don't there will be no association between the dependency calls and the parent request.\n","funding_links":["https://github.com/sponsors/Elfocrash"],"categories":["Libraries","C# #"],"sub_categories":["C#/.NET ###"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FElfocrash%2FCosmonaut","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FElfocrash%2FCosmonaut","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FElfocrash%2FCosmonaut/lists"}