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
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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:
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.
I’m adding the service endpoint from the Azure Cosmos DB account and a database name to the JSON configuration file appsettings.json
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"CosmosSettings": { | |
"ServiceEndpoint": "https://efcorecosmossample.documents.azure.com:443/", | |
"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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Book | |
{ | |
public Guid BookId { get; set; } | |
public string Title { get; set; } | |
public string Publisher { get; set; } | |
} |
The EF Core BooksContext
does not differ to other EF Core contexts that are used with SQL Server:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class BooksContext : DbContext | |
{ | |
public BooksContext(DbContextOptions<BooksContext> options) | |
: base(options) { } | |
public DbSet<Book> Books { get; set; } | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}"); | |
} | |
} | |
} |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static async Task Main(string[] args) | |
{ | |
ConfigureServices(); | |
var service = Container.GetRequiredService<BooksService>(); | |
await service.CreateTheDatabaseAsync(); | |
await service.WriteBooksAsync(); | |
service.ReadBooks(); | |
Console.WriteLine("completed"); | |
} |
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.
Using the Azure Cosmos DB Data Explorer, you can see all the book objects created.
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 | |
} |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
These shadow properties can be retrieved using the Entry
method of the DbContext
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
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.
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
Looks like they have unlisted the EF Core Cosmos Nuget package? Do you know why?
LikeLike
The package name was changed. It’s now Microsoft.EntityFrameworkCore.Cosmos.
LikeLike
Thanks for the great insights
LikeLike
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.
LikeLike
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 🙂
LikeLike
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.
LikeLiked by 1 person
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.
LikeLike
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.
LikeLike
Hi María,
the unicorn is from © Solvarseven, https://www.dreamstime.com/solarseven_info
Greetings,
Christian
LikeLike
Hi Christian, thanks for sharing, it’s possible to manage different entities in same collection?
And the nuget package now is in release?
LikeLiked by 1 person
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 🙂
LikeLike