First experiments using EF Core with Azure Cosmos DB

Entity Framework Core (EF Core) was designed to not being a framework for only relational databases. It just takes some time to get the first non-relational providers. Now is the time.

Microsoft now has the first preview for an EF Core provider for Cosmos DB. With this I have to do my first experiments using Cosmos DB from EF Core.

Updated to Entity Framework Core 2.2 Preview 3

Unicorn in galaxy nebula cloud

Azure Cosmos DB

Azure Cosmos DB is a NoSQL database from Microsoft. It is globally distributed and offers multiple models. You can store data with key/values pairs, records with multiple columns (table storage), documents, and data linked within graphs. If you need massive amounts of data, reads, and writes with fast responses and high throughput, Azure Cosmos DB gives you great options.

EF Core Providers

Available EF Core providers are listed in the document Database Providers. The provider I’m using most of the time is of course the provider for SQL Server. There are some other providers as well, e.g. from Microsoft for SQLite, and in-memory. From the Npgsql Development Team you can get a provider for PostreSQL. MySQL and MariaDB providers are from the Pomelo Foundation Project. Erik Ejlskov Jensen created a provider for SQL Server Compact. Oracle offers a provider for MySQL. Jiří Činčura and Rafael Almeida have providers for Firebird. IBM is in the game with providers for Db2 and Informix. Paid high quality providers for Oracle, PostgreSQL, SQLite, and MySQL are from DevArt. There’s even one for Microsoft Access from Bubi.

Missing are Non-SQL providers, and an Oracle provider from Oracle. Oracle already has a Statement of Direction for .NET Core and Entity Framework Core, so a provider from Oracle is coming soon. Oracle is not left out as there’s a provider from DevArt as previously mentioned. Microsoft now has a sample provider for Oracle as well, but this one has some limitations.

With NoSQL Providers, Microsoft started to work on a Azure Table Storage provider. Because of time constraints, this was delayed. Now the Cosmos DB provider has a bigger focus. Preview 3 for Microsoft.EntityFrameworkCore.Cosmos is now available on the NuGet server.

Project Preparation

To start, I’m creating a .NET Core 2.2 console application and adding these packages:

  • Microsoft.EntityFrameworkCore.Cosmos
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.Configuration.UserSecrets
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Logging.Debug

From the first preview, the name of the provider package changed from Microsoft.EntityFrameworkCore.Cosmos.Sql to Microsoft.EntityFrameworkCore.Cosmos.

The configuration for the Cosmos DB is read from a JSON configuration file. I’m not storing the secret key in the source code repository. Instead, app secrets are used during development time with the package Microsoft.Extensions.Configuration.UserSecrets.

The EF Core context is instantiated by using dependency injection, that’s why the package Microsoft.Extensions.DependencyInjection is added. EF Core could be used without creating a DI container. However as in all production apps I’m creating nowadays I’m using a DI container, I’m using it with this small sample as well.

For logging I’m using the ILogger interface, to see what information is traced by the EF Core provider – that’s the reason for the Microsoft.Extensions.Logging.Debug package.

.NET Core 2.2 is in preview at the time of this writing, and can be downloaded from the .NET Core 2.2 downloads.

The project file for the sample app lists the packages:


<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
<UserSecretsId>1E2D66CC-11C9-4DE7-B25E-F1EAA5F0154A</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos.Sql" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0-preview1-35029" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Creating Azure Cosmos DB

Before building the application, I’m creating a new Azure Cosmos DB account:

Create Azure Cosmos DB Account

Creating an Azure Cosmos DB account, you need to decide and select one of the APIs:

  • SQL
  • MongoDB
  • Cassandra
  • Azure Table
  • Gremlin (graph)

Azure Cosmos DB offers different storage technologies, such as JSON documents, table storage, and storing relations using a graph API. SQL is the successor of DocumentDB, and this is where this new provider can be used.

To access this account from the provider, from the portal the URI and the key is needed. All this information can be accessed by selecting the Keys settings from the portal.

Use Keys and URI

I’m adding the service endpoint from the Azure Cosmos DB account and a database name to the JSON configuration file appsettings.json:


{
"CosmosSettings": {
"ServiceEndpoint": "https://efcorecosmossample.documents.azure.com:443/&quot;,
"DatabaseName": "efcoresample"
}
}

I’m not directly adding the key to the JSON configuration file, but instead use app secrets. Because an identifier for app secrets (UserSecretsId) was added to CosmosDBWithEFCore.csproj, and the NuGet package Microsoft.Extensions.Configuration.UserSecrets was added, the dotnet CLI can be used to configure the authentication key:

dotnet user-secrets set CosmosSettings:AuthKey "add your auth-key from Azure Cosmos DB"

EF Core Context and Model and Services

Next, I’m creating the EF Core context and the model. The model is defined with the Book class and simple properties:


public class Book
{
public Guid BookId { get; set; }
public string Title { get; set; }
public string Publisher { get; set; }
}

view raw

Book.cs

hosted with ❤ by GitHub

The EF Core BooksContext does not differ to other EF Core contexts that are used with SQL Server:


public class BooksContext : DbContext
{
public BooksContext(DbContextOptions<BooksContext> options)
: base(options) { }
public DbSet<Book> Books { get; set; }
}

view raw

BooksContext.cs

hosted with ❤ by GitHub

The class BooksService is used to read and write Book objects. It’s not different to other services using an EF Core context. With the constructor, the BooksContext is injected. This context is used when creating the database, adding new Book objects, and creating a query to read the Book objects:


public class BooksService
{
private BooksContext _booksContext;
public BooksService(BooksContext booksContext) =>
_booksContext = booksContext ?? throw new ArgumentNullException(nameof(booksContext));
public async Task CreateTheDatabaseAsync()
{
var created = await _booksContext.Database.EnsureCreatedAsync();
if (created)
{
Console.WriteLine("database created");
}
else
{
Console.WriteLine("database already exists");
}
}
public async Task WriteBooksAsync()
{
_booksContext.Books.Add(new Book { BookId = Guid.NewGuid(), Title = "Professional C# 7 and .NET Core 2.0", Publisher = "Wrox Press" });
_booksContext.Books.Add(new Book { BookId = Guid.NewGuid(), Title = "Professional C# 6 and .NET Core 1.0", Publisher = "Wrox Press" });
_booksContext.Books.Add(new Book { BookId = Guid.NewGuid(), Title = "Enterprise Services with the .NET Framework", Publisher = "Addison Wesley" });
int changed = await _booksContext.SaveChangesAsync();
Console.WriteLine($"created {changed} records");
}
public void ReadBooks()
{
var books = _booksContext.Books.Where(b => b.Publisher == "Wrox Press");
foreach (var book in books)
{
Console.WriteLine($"{book.Title} {book.Publisher} {book.BookId}");
}
}
}

view raw

BooksService.cs

hosted with ❤ by GitHub

First I tried to create the database and the collection in the portal. However, I didn’t see how to map the names at first, and documentation was not existing yet. It turned out that creating the database directly from EF Core is already working which helped finding the default database name.

The dependency injection container is configured in the Program class. Having the NuGet package for the EF Core provider for Azure Cosmos DB referenced, allows using the UseCosmos extension method. This method needs the configuration for the service endpoint, the key, and the database name. These settings are coming from appsettings.json, and the app secrets.


public static void ConfigureServices()
{
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
#if DEBUG
configurationBuilder.AddUserSecrets("1E2D66CC-11C9-4DE7-B25E-F1EAA5F0154A");
#endif
IConfigurationRoot config = configurationBuilder.Build();
IConfigurationSection configSection = config.GetSection("CosmosSettings");
var services = new ServiceCollection();
services.AddDbContext<BooksContext>(options => options.UseCosmos(configSection["ServiceEndpoint"], configSection["AuthKey"], configSection["DatabaseName"]));
services.AddTransient<BooksService>();
services.AddLogging(options =>
options.AddDebug().SetMinimumLevel(LogLevel.Trace));
Container = services.BuildServiceProvider();
}
public static ServiceProvider Container { get; private set; }

view raw

Program.cs

hosted with ❤ by GitHub

The Main method just connects everything together. First, the DI container is configured. The BooksService is returned from the container to a) create the database, b) write book records, and to c) query books.


static async Task Main(string[] args)
{
ConfigureServices();
var service = Container.GetRequiredService<BooksService>();
await service.CreateTheDatabaseAsync();
await service.WriteBooksAsync();
service.ReadBooks();
Console.WriteLine("completed");
}

view raw

Program.cs

hosted with ❤ by GitHub

Running the App

Running the app results in creation of the database with the name passed to the UseCosmos method, and the collection with the name of the context. You can see this in the Azure Portal.

Azure Cosmos DB database created

Using the Azure Cosmos DB Data Explorer, you can see all the book objects created.

Azure Cosmos DB Data Explorer

With the collection name, you can change a different default, or assign a collection name for an entity using the ModelBuilder.

Accessing Azure Cosmos DB generated values

A book is represented within Cosmos DB with the properties defined in the model, and some additional properties used by Azure Cosmos DB:


{
"id": "208c4a3f-d4cf-4b2f-9ece-a4bc207b3aeb",
"BookId": "208c4a3f-d4cf-4b2f-9ece-a4bc207b3aeb",
"Discriminator": "Book",
"Publisher": "Wrox Press",
"Title": "Professional C# 7 and .NET Core 2.0",
"_rid": "Q-86ALyw0XkBAAAAAAAAAA==",
"_self": "dbs/Q-86AA==/colls/Q-86ALyw0Xk=/docs/Q-86ALyw0XkBAAAAAAAAAA==/",
"_etag": "\"05002269-0000-0000-0000-5b87d7630000\"",
"_attachments": "attachments/",
"_ts": 1535629155
}

view raw

book1.json

hosted with ❤ by GitHub

Here’s some information about these values:

  • etag – This is the resource etag, required for optimistic concurrency. This value is changed with every update.
  • id – A unique name with a max length of 255 characters. This value, must be set on insert, and be unique within the database. Using EF Core, it has the same value as specified by the key in the model.
  • rid – This is a system generated hierarchical unique identifier, and used for navigation.
  • ts – The timestamp shows the last update time.
  • self – An addressable url to the document.
  • attachment – Documents can have attachments. This value gives references to attachment resources.

The type of the class is stored with the Discriminator value. Don’t be tempted to use different collections for every type. You can save different types in the same collection. Retrieving objects on the type is fast, this information is indexed. Calculate costs for Azure Cosmos DB per collection.

Using EF Core, you can either add properties to your model to retrieve these values, or create shadow properties in the context definition.


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>().Property<string>("_self").ValueGeneratedOnAdd();
modelBuilder.Entity<Book>().Property<string>("_etag").ValueGeneratedOnAddOrUpdate();
modelBuilder.Entity<Book>().Property<long>("_ts").ValueGeneratedOnAddOrUpdate();
}

view raw

BooksContext.cs

hosted with ❤ by GitHub

These shadow properties can be retrieved using the Entry method of the DbContext:


public void ShowCosmosDBState()
{
foreach (var book in Books)
{
Console.WriteLine($"{book.Title}, etag: {Entry<Book>(book).Property("_etag").CurrentValue}, " +
$"self: {Entry(book).Property("_self").CurrentValue}, " +
$"ts: {Entry(book).Property("_ts").CurrentValue}");
Console.WriteLine();
}
}

view raw

BooksContext.cs

hosted with ❤ by GitHub

Summary

This just have been the first experiments using Azure Cosmos DB using the new EF Core provider. Azure Cosmos DB integrates nicely into EF Core. You’re creating the models and the context as you are used to, and can create the database, write documents, and create queries.
Additional properties such as the etag and self implemented by Azure Cosmos DB do not need to be part of your model, but can be easily retrieved using the shadow properties from EF Core.

Expect more information on EF Core and Azure Cosmos DB to come in the following months.

If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

Buy Me A Coffee

More information on EF Core and writing data-driven applications is in my book Professional C# 7 and .NET Core 2.0, and in my Data Programming workshops.

Enjoy learning and programming!

Christian

12 thoughts on “First experiments using EF Core with Azure Cosmos DB

  1. I have found no references or samples anywhere on the web that discuss EF Cosmos Preview 3. This is making me a bit scared, in terms of taking this path that may in fact be not productive down the line. Should I continue hoping (hope is the last to die) for this RTM release, and perhaps some samples to view, or should I move back to the SQL API (which I truly hate).
    Thanks Christian.

    Like

    1. It just takes more time for a release with .NET Core 3.0. First it was planned with 2.2. I expect the release in 2019, but not in the next months. I have another article on EF Core with Cosmos DB in the queue 🙂

      Like

      1. Did you happen to get etag’s to retrieve using Core 3.0? I tried using a variation of this article, but have been unsuccessful. I can add the record, but when I retrieve the record, the etag is null. I tried both shadow properties and hard properties.

        Liked by 1 person

      2. On creating the items, did you use the same context instance to read the etag? I experienced to not get it on creation of the item, but later on. However you need to use a new context where the item is not cached, or re-load it from Cosmos. I need to try this out with the current preview 9.

        Like

  2. Im´ from Buenos Aires, Argentina. I can´t speak or write a good english. Hope you can understand this message. Is so amazing your unicorn. Is beauty and magic. Thanks for your art. Gracias por tu arte.

    Like

  3. Hi Christian, thanks for sharing, it’s possible to manage different entities in same collection?
    And the nuget package now is in release?

    Liked by 1 person

  4. Hi Francisco, yes, different entities can be in the same container (collection). The container can be specified with the API. Regarding cost and containers, Cosmos DB now allows when creating a database to share the request units (RU) with all the containers. Having different RUs by container is great when different scalability is needed based on different containers.
    And yes, the NuGet package is now released 🙂

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.