Helpwiz.CosmosDbFramework 1.0.9

dotnet add package Helpwiz.CosmosDbFramework --version 1.0.9
                    
NuGet\Install-Package Helpwiz.CosmosDbFramework -Version 1.0.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Helpwiz.CosmosDbFramework" Version="1.0.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Helpwiz.CosmosDbFramework" Version="1.0.9" />
                    
Directory.Packages.props
<PackageReference Include="Helpwiz.CosmosDbFramework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Helpwiz.CosmosDbFramework --version 1.0.9
                    
#r "nuget: Helpwiz.CosmosDbFramework, 1.0.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Helpwiz.CosmosDbFramework@1.0.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Helpwiz.CosmosDbFramework&version=1.0.9
                    
Install as a Cake Addin
#tool nuget:?package=Helpwiz.CosmosDbFramework&version=1.0.9
                    
Install as a Cake Tool

Helpwiz CosmosDB Framework

This package contains a foundation library for getting up-and-running with CosmosDB quickly. Helpwiz is a marketplace for domestic cleaners - we allow clients who want to find a cleaner for their houses to choose and book a cleaner in a simple, one-pass process.

We provide this package to anyone who wants to use it, but please give us a mention if you can.

Basic Elements

The basic elements of this package are:

  • DocumentTransaction - A unit-of-work / repository pattern for CosmosDB.
  • ChangeTrackingDocumentBase - A base class for documents that automatically track changes.
  • DocumentBase - A base class for documents without change tracking.

Getting Started

Creating or accessing a Database and Collection

To either access an existing database and collection, or to create a new one if it doesn't exist, and to create a read-only transaction for querying documents, you do the following:

var documentTransaction = await DocumentTransactionFactory.CreateDocumentTransactionAsync(
    DocumentDataTarget.CreateDefault(
        ACCOUNT_ENDPOINT_URL,
        ACCOUNT_KEY,
        THROUGHPUT, 
        DATABASE_NAME, 
        COLLECTION_NAME), 
        TransactionBehaviourEnum.ReadOnly, 
        ConcurrencyBehaviourEnum.OverwriteIfNewest,
        transactionSaveErrorsCallback: CALLBACK_FN)
);

Implementing a Document

Your documents must derive from ChangeTrackingDocumentBase<T> or DocumentBase<T>

To implement a document, you

  • Derive from one of the bases.
  • Add two constructors, one with parameters for creation, one parameterless for deserialization
  • Add any properties you want to serialize

Here is an example:

internal sealed class MyDocument : ChangeTrackingDocumentBase<MyDocument>
{
    private static readonly int currentVersion = 2;

    // Creation constructor
    public MyDocument(int myData, string stringData, Guid customerId, Guid cityId)
        : base(Guid.NewGuid(), cityId.ToString(), currentVersion)
    {
        MyData = myData;
        StringData = stringData;
        CityId = cityId;
        CustomerId = customerId;
    }

    // Json Deserialization
    public MyDocument()
    {
    }

    //Upgrades
    [OnDeserialized]
    private void OnDeserialized(StreamingContext ctx)
    {
        //Added city id - all data was in New York before.
        DoUpgradeIfNecessary(0, () => CityId = TestDocumentBase.CityIds.NewYork);

        //Added string data - defaults to null
        DoUpgradeIfNecessary(1, () => { });
    }

    public int MyData { get; set; }

    // Added in version 2.
    public string StringData { get; set; }

    // Added in version 1
    public Guid CityId { get; set; }

    public Guid CustomerId { get; set; }
}

This document will automatically track changes and add to the transaction if needed.

The document shown here has had two previous versions. When changing the version number, you should upgrade the document via the [OnDeserialized] callback, using the DoUpgradeIfNecessary helper method, as shown.

Adding a document to a DataTransaction

Here is what adding a document to the database looks like:

//Create the transaction.
using (var transaction = await CreateTransaction(TransactionBehaviourEnum.AbandonIfNotSaved))
{
    var document = new MyDocument(customerIntegerData, customerStringData, customerId, cityId);
    transaction.AddDocument(document);
    transaction.Save();
}

Finding a specific document

You can find a specific document using the asynchronous FindDocument<T> method:

var document = await transaction.FindDocument<MyDocument>(id, cityKey.ToString());

You can find multiple documents under a partition using the FindDocuments<T> method:

var multipleDocuments = await transaction.FindDocuments<MyDocument>(new[] {id, id2, id3}, cityKey.ToString());

Querying documents

To query for matching documents:

var matchingDocuments = await transaction.Builder.MakeQueryWhereAsync<MyDocument>(t => t.MyData < 100);

To query for matching ordered documents:

var ordered = await transaction.Builder.MakeQueryWhereOrderByDescendingLimitAsync<MyDocument, int>(
    t => t.CustomerId == id, //Find documents with this customer id.
    t => t.MyData,           //Order by descending MyData values.
    10);                     //Limit to first 10 results.

To get an ascending query, just invert the calculation.

Query Caching

For the lifetime of a transaction, all queries are cached. Running an equivalent query twice will only result in one execution against the database. In addition if changes (additions, modifications, deletions) have been made locally to any documents, the query will return the correct result as if the changes had already been committed.

Deletion

To delete a document, using the DocumentTransaction.Delete() method:

transaction.Delete(document)

DocumentTransaction also supports soft deletion. To soft-delete a document, using the DocumentTransaction.SoftDelete() method:

transaction.SoftDelete(document)

To get soft deleted documents, you have to use the special methods for this:

  • DocumentTransaction.FindDocumentIncDeleted<T>
  • DocumentTransaction.Builder.MakeQueryWhereIncDelete<T>
  • DocumentTransaction.Builder.MakeQueryWhereOrderByDescendingLimit<T>

Transaction Behaviours

When creating a transaction, you specify the transaction behaviour from TransactionBehaviourEnum. The options are:

  • ReadOnly: A read-only transaction.
  • AbandonIfNotSaved: A writable transaction that will abandon changes unless you Save()
  • SaveIfNotAbandoned: A writable transaction that will save changes unless Abandon()

Committing Changes

No changes are committed to the database unless you dispose the transaction, either by calling dispose, or putting it in a using statement.

Handling DateTime values

To store dates, it is recommended to store them as doubles (or nullable doubles in the case of nullable DateTime values):

internal sealed class StoringDateTimeDocument : ChangeTrackingDocumentBase<StoringDateTimeDocument>
{
    //...
	// Value is stored as a double...
    [JsonProperty] 
    private double DoubleTimeStamp { get; set; }

    // But used as a date time...
    [JsonIgnore]
    public DateTime TimeStamp
    {
        get => DoubleTimeStamp.ToDotNetDateTime();
        set => DoubleTimeStamp = value.ToDocumentTime();
    }
}

The doubles allow efficient indexing, but the program can use DateTime. The extension methods also support nullables.

Unit Testing Your Implementations

We provide a meta-tests to help you unit test your documents:

public void ExampleTestMethod()
{
    var testContext = DocumentMetaTestContext.CreateFromAssembly(typeof(MyDocument).Assembly);
    var testResults = DocumentMetaTests.TestDocumentsHaveNoDateTimeProperties(testContext);
    var failures = testResults.Where(t => !t.IsSuccess).Select(t => t.Error).ToArray();

    Assert.That(failures, Is.Empty);
}

We recommend that you use all of the meta tests in your test fixture.

Mentioning Us

If you do get any use out of this library, please link to our website at helpwiz.com and mention us. Thanks.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.9 484 5/21/2021
1.0.8 409 2/18/2021
1.0.7 413 2/16/2021
1.0.6 425 2/16/2021
1.0.5 412 2/16/2021
1.0.4 514 8/27/2020
1.0.3 501 8/27/2020
1.0.2 545 7/23/2020
1.0.1 577 7/9/2020
1.0.0 560 7/8/2020

Previously: Added base classes for documents, include a base class for auto-change tracking. Added concurrency levels to DocumentTransactionFactory. Added concurrency behaviour on transaction save and an error callback.
1.0.3 Fixed deletion and LoadExisting methods.
1.0.4 Fixed accidentally reintroduced issue.
1.0.5 Changed transaction timestamp to UtcNow, instead of BristishNow - also works on unix systems.
1.0.6 Added support for single-partition queries
1.0.7 Added CommitAsync() method to Transaction, to allow transaction to cache documents but commit modifications on request.
1.0.8 Added protections to make CommitAsync() multi-call safe (updating Etag of upserted documents, resetting changes etc...)
1.0.9 Fixed updates to loaded dictionary from deleted, added and modified elements on CommitAsync().