Cosmonaut 2.4.0

A supercharged .NET SDK for Azure CosmosDB with ORM support

There is a newer version of this package available.
See the version list below for details.
Install-Package Cosmonaut -Version 2.4.0
dotnet add package Cosmonaut --version 2.4.0
<PackageReference Include="Cosmonaut" Version="2.4.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Cosmonaut --version 2.4.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

Cosmonaut

The word was derived from "kosmos" (Ancient Greek: κόσμος) which means world/universe and "nautes" (Ancient Greek: ναῦς) which means sailor/navigator

Cosmonaut 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.

Getting started

Usage

The idea is pretty simple. You can have one CosmoStore per entity (POCO/dtos etc)
This entity will be used to create a collection in the cosmosdb and it will offer all the data access for this object

Registering the CosmosStores in ServiceCollection for DI support

 var cosmosSettings = new CosmosStoreSettings("<<databaseName>>", 
    "<<cosmosUri>>"), 
    "<<authkey>>");
                
serviceCollection.AddCosmosStore<Book>(cosmosSettings);

//or just by using the Action extension

serviceCollection.AddCosmosStore<Book>("<<databaseName>>", "<<cosmosUri>>"), "<<authkey>>", settings =>
{
    settings.ConnectionPolicy = connectionPolicy;
    settings.DefaultCollectionThroughput = 5000;
    settings.IndexingPolicy = new IndexingPolicy(new RangeIndex(DataType.Number, -1),
        new RangeIndex(DataType.String, -1));
});

//or just initialise the object

ICosmosStore<Book> bookStore = new CosmosStore<Book>(cosmosSettings)

To use the AddCosmosStore extension methods you need to install the Cosmonaut.Extensions.Microsoft.DependencyInjection package.

Install-Package Cosmonaut.Extensions.Microsoft.DependencyInjection
or
dotnet add package Cosmonaut.Extensions.Microsoft.DependencyInjection
Retrieving an entity by id (and partition key)
var user = await cosmosStore.FindAsync("userId");
var user = await cosmosStore.FindAsync("userId", "partitionKey");
var user = await cosmosStore.FindAsync("userId", new RequestOptions());
Quering for entities using LINQ

In 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.
It is HIGHLY recommended that you use one of the Async methods to get the results back, such as ToListAsync or FirstOrDefaultAsync , when available.

var user = await cosmoStore.Query().FirstOrDefaultAsync(x => x.Username == "elfocrash");
var users = await cosmoStore.Query().Where(x => x.HairColor == HairColor.Black).ToListAsync(cancellationToken);
Quering for entities using SQL
// plain sql query
var user = await cosmoStore.QueryMultipleAsync("select * from c w.Firstname = 'Smith'");

// or parameterised sql query
var user = await cosmoStore.QueryMultipleAsync("select * from c w.Firstname = @name", new { name = "Smith" });
Pagination

Cosmonaut supports two types of pagination.

  • Page number + Page size
  • ContinuationToken + Page size

Both of there methods work by adding the .WithPagination() method after you used any of the Query methods.

var firstPage = await booksStore.Query().WithPagination(1, 10).OrderBy(x=>x.Name).ToListAsync();
var secondPage = await booksStore.Query().WithPagination(2, 10).OrderBy(x => x.Name).ToPagedListAsync();
var thirdPage = await booksStore.Query().WithPagination(secondPage.NextPageToken, 10).OrderBy(x => x.Name).ToPagedListAsync();
var fourthPage = await thirdPage.GetNextPageAsync();
var fifthPage = await booksStore.Query().WithPagination(5, 10).OrderBy(x => x.Name).ToListAsync();

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.

Pagination recommendations

Because page number + page size pagination goes though all the documents until it gets to the requested page, it's potentially slow and expensive.
The 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.

Keep 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.

Adding an entity in the entity store
var newUser = new User
{
    Name = "Nick"
};
var added = await cosmoStore.AddAsync(newUser);

var multiple = await cosmoStore.AddRangeAsync(manyManyUsers);
Updating entities

When it comes to updating you have two options.

Update...

await cosmoStore.UpdateAsync(entity);

... and Upsert

await cosmoStore.UpsertAsync(entity);

The main difference is of course in the functionality.
Update will only update if the item you are updating exists in the database with this id.
Upsert 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.

Removing entities
await cosmoStore.RemoveAsync(x => x.Name == "Nick"); // Removes all the entities that match the criteria
await cosmoStore.RemoveAsync(entity);// Removes the specific entity
await cosmoStore.RemoveByIdAsync("<<anId>>");// Removes an entity with the specified ID
Collection sharing

Cosmonaut is all about making the integration with CosmosDB easy as well as making things like cost optimisation part of the library.

That's why Cosmonaut support collection sharing between different types of entities.

Why would you do that?

Cosmos 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.

How can you do that?

Well it's actually pretty simple. Just implement the ISharedCosmosEntity interface and decorate your object with the SharedCosmosCollection attribute.

The attribute accepts two properties, SharedCollectionName which is mandatory and EntityPrefix which is optional.
The SharedCollectionName property will be used to name the collection that the entity will share with other entities.

The CosmosEntityName will be used to make the object identifiable for Cosmosnaut. Be default it will pluralize the name of the class, but you can specify it to override this behavior.

Once you set this up you can add individual CosmosStores with shared collections.

Cosmonaut

The word was derived from "kosmos" (Ancient Greek: κόσμος) which means world/universe and "nautes" (Ancient Greek: ναῦς) which means sailor/navigator

Cosmonaut 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.

Getting started

Usage

The idea is pretty simple. You can have one CosmoStore per entity (POCO/dtos etc)
This entity will be used to create a collection in the cosmosdb and it will offer all the data access for this object

Registering the CosmosStores in ServiceCollection for DI support

 var cosmosSettings = new CosmosStoreSettings("<<databaseName>>", 
    "<<cosmosUri>>"), 
    "<<authkey>>");
                
serviceCollection.AddCosmosStore<Book>(cosmosSettings);

//or just by using the Action extension

serviceCollection.AddCosmosStore<Book>("<<databaseName>>", "<<cosmosUri>>"), "<<authkey>>", settings =>
{
    settings.ConnectionPolicy = connectionPolicy;
    settings.DefaultCollectionThroughput = 5000;
    settings.IndexingPolicy = new IndexingPolicy(new RangeIndex(DataType.Number, -1),
        new RangeIndex(DataType.String, -1));
});

//or just initialise the object

ICosmosStore<Book> bookStore = new CosmosStore<Book>(cosmosSettings)

To use the AddCosmosStore extension methods you need to install the Cosmonaut.Extensions.Microsoft.DependencyInjection package.

Install-Package Cosmonaut.Extensions.Microsoft.DependencyInjection
or
dotnet add package Cosmonaut.Extensions.Microsoft.DependencyInjection
Retrieving an entity by id (and partition key)
var user = await cosmosStore.FindAsync("userId");
var user = await cosmosStore.FindAsync("userId", "partitionKey");
var user = await cosmosStore.FindAsync("userId", new RequestOptions());
Quering for entities using LINQ

In 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.
It is HIGHLY recommended that you use one of the Async methods to get the results back, such as ToListAsync or FirstOrDefaultAsync , when available.

var user = await cosmoStore.Query().FirstOrDefaultAsync(x => x.Username == "elfocrash");
var users = await cosmoStore.Query().Where(x => x.HairColor == HairColor.Black).ToListAsync(cancellationToken);
Quering for entities using SQL
// plain sql query
var user = await cosmoStore.QueryMultipleAsync("select * from c w.Firstname = 'Smith'");

// or parameterised sql query
var user = await cosmoStore.QueryMultipleAsync("select * from c w.Firstname = @name", new { name = "Smith" });
Pagination

Cosmonaut supports two types of pagination.

  • Page number + Page size
  • ContinuationToken + Page size

Both of there methods work by adding the .WithPagination() method after you used any of the Query methods.

var firstPage = await booksStore.Query().WithPagination(1, 10).OrderBy(x=>x.Name).ToListAsync();
var secondPage = await booksStore.Query().WithPagination(2, 10).OrderBy(x => x.Name).ToPagedListAsync();
var thirdPage = await booksStore.Query().WithPagination(secondPage.NextPageToken, 10).OrderBy(x => x.Name).ToPagedListAsync();
var fourthPage = await thirdPage.GetNextPageAsync();
var fifthPage = await booksStore.Query().WithPagination(5, 10).OrderBy(x => x.Name).ToListAsync();

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.

Pagination recommendations

Because page number + page size pagination goes though all the documents until it gets to the requested page, it's potentially slow and expensive.
The 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.

Keep 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.

Adding an entity in the entity store
var newUser = new User
{
    Name = "Nick"
};
var added = await cosmoStore.AddAsync(newUser);

var multiple = await cosmoStore.AddRangeAsync(manyManyUsers);
Updating entities

When it comes to updating you have two options.

Update...

await cosmoStore.UpdateAsync(entity);

... and Upsert

await cosmoStore.UpsertAsync(entity);

The main difference is of course in the functionality.
Update will only update if the item you are updating exists in the database with this id.
Upsert 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.

Removing entities
await cosmoStore.RemoveAsync(x => x.Name == "Nick"); // Removes all the entities that match the criteria
await cosmoStore.RemoveAsync(entity);// Removes the specific entity
await cosmoStore.RemoveByIdAsync("<<anId>>");// Removes an entity with the specified ID
Collection sharing

Cosmonaut is all about making the integration with CosmosDB easy as well as making things like cost optimisation part of the library.

That's why Cosmonaut support collection sharing between different types of entities.

Why would you do that?

Cosmos 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.

How can you do that?

Well it's actually pretty simple. Just implement the ISharedCosmosEntity interface and decorate your object with the SharedCosmosCollection attribute.

The attribute accepts two properties, SharedCollectionName which is mandatory and EntityPrefix which is optional.
The SharedCollectionName property will be used to name the collection that the entity will share with other entities.

The CosmosEntityName will be used to make the object identifiable for Cosmosnaut. Be default it will pluralize the name of the class, but you can specify it to override this behavior.

Once you set this up you can add individual CosmosStores with shared collections.

Release Notes

Please report any issues on Github.

Showing the top 1 GitHub repositories that depend on Cosmonaut:

Repository Stars
Elfocrash/Cosmonaut
🌐 A supercharged Azure CosmosDB .NET SDK with ORM support

Version History

Version Downloads Last updated
2.11.3 2,592 7/5/2019
2.10.0 17,916 3/23/2019
2.9.0 2,432 2/28/2019
2.8.2 4,865 1/17/2019
2.8.1 217 1/10/2019
2.7.1 3,864 12/11/2018
2.7.0 8,632 12/3/2018
2.6.2 1,078 11/15/2018
2.5.0 867 11/8/2018
2.4.2 2,588 10/8/2018
2.4.0 1,374 9/24/2018
2.3.2 873 9/20/2018
2.2.0 6,737 9/12/2018
2.0.4 1,570 9/7/2018
2.0.3 525 8/9/2018
Show less