Using Azure CosmosDB With .NET Core – Part 2 – EF Core

This post is part of a series on using Azure CosmosDB with .NET Core

Part 1 – Introduction to CosmosDB with .NET Core
Part 2 – Azure CosmosDB with .NET Core EF Core


When I first found out EntityFramework supported Azure CosmosDB, I was honestly pretty excited. Not because I thought it would be revolutionary, but because if there was a way to get new developers using Cosmos by leveraging what they already know (Entity Framework), then that would actually be a pretty cool pathway.

But honestly, after hitting many many bumps along the road, I don’t think it’s quite there yet. I’ll first talk about setting up your own small test, and then at the end of this post I’ll riff a little on some challenges I ran into.

Setting Up EFCore For Cosmos

I’m going to focus on Cosmos only information here, and not get too bogged down in details around EF Core. If you already know EF Core, this should be pretty easy to follow!

The first thing you need to do is install the nuget package for EF Core with Cosmos. So from your Package Manager Console :

Install-Package Microsoft.EntityFrameworkCore.Cosmos

In your startup.cs, you will need a line such as this :

services.AddDbContext(options =>
    options.UseCosmos("CosmosEndPoint",
    "CosmosKey",
    "CosmosDatabase")
);

Now.. This is the first frustration of many. There is no overload to pass in a connection string here (Yah know, the thing that literally every other database context allows). So when you put this into config, you have to have them separated out instead of just being part of your usual “ConnectionStrings” configuration.

Let’s say I am trying to store the following model :

public class People
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string City { get; set; }
    public string ZipCode { get; set; }
}

Then I would make my context resemble something pretty close to :

public class CosmosDbContext : DbContext
{
    public DbSet People { get; set; }

    public CosmosDbContext(DbContextOptions options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity()
            .ToContainer("People")
            .OwnsOne(x => x.Address);
    }
}

Now a couple of notes here.

For reasons known only to Microsoft, the default name of the collection it tries to pull is the name of the context. e.g. When it goes to Cosmos, it looks for a collection called “CosmosDbContext” even if my DbSet itself is called People. I have no idea why it’s built like this because, again, in every other use for EntityFramework, the table/container name takes after the DbSet not the entire Context. So we have to add in an explicit call to map the container.

Secondly, Cosmos in EFCore seems unable to work out sub documents. I kind of understand this one because in my model, Is Address it’s own collection, or is it a subdocument of People? But the default should be subdocument as it’s unlikely people are doing “joins” across CosmosDB collections, and if they are, they aren’t expecting EF Core to handle that for them through navigation properties. So if you don’t have that “OwnsOne” config, it thinks that Address is it’s own collection and throws a wobbly with :

'The entity type 'Address' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.'

And honestly, that’s all you need to get set up with EF Core and Cosmos. That’s your basic configuration!

Now Here’s The Bad

Here’s what I found while trying to set up even a simple configuration in Cosmos with EF Core.

  • As mentioned, the default naming of the Collections in Cosmos using the Context name is illogical. Given that in most cases you will have only a single DbContext within your application, but you may have multiple collections you need to access, 9 times out of 10 you are going to need to re-define the container names for each DBSet.
  • The default mappings aren’t what you would expect from Cosmos. As pointed out, the fact that it can’t handle subdocuments out of the box seems strange to me given that if I used the raw .NET Core library it works straight away.
  • You have no control (Or less control anyway) over naming conventions. I couldn’t find a way at all to use camelCase naming conventions at all and it had to use Pascal. I personally prefer NOSQL stores to always be in camelcase, but you don’t get the option here.
  • Before I knew about it trying to connect to a collection with the same name as the context, I wasn’t getting any results back from my queries (Since I was requesting data from a non-existent collection), but my code wasn’t throwing any exceptions, it just returned nothing. Maybe this is by design but it’s incredibly frustrating that I can call a non existent resource and not have any error messages show.
  • Because you might already have a DBContext for SQL Server in your project, things can become hectic when you introduce a second one for Cosmos (Since you can’t use the same Context). Things like migration CLI commands now need an additional flag to say which context it should run on (Even though Cosmos doesn’t use Migrations).

Should You Use It?

Honestly your mileage may vary. I have a feeling that the abstraction of using EF Core may be a little too much for some (e.g. The naming conventions) and that many would prefer to have a bit more control over what’s going on behind the scenes. I feel like EntityFramework really shines when working with a large amount of tables with foreign keys between them using Navigation Properties, something that CosmosDB won’t really have. And so I don’t see a great value prop in wrangling EF Core for a single Cosmos table. But have a try and let me know what you think!

ENJOY THIS POST?
Join over 3.000 subscribers who are receiving our weekly post digest, a roundup of this weeks blog posts.
We hate spam. Your email address will not be sold or shared with anyone else.

1 comment

  1. I had to add <DbContext> to AddDbContext in Startup.cs to make it work. See:

    services.AddDbContext<DbContext>(options =>
    options.UseCosmos(“CosmosEndPoint”,
    “CosmosKey”,
    “CosmosDatabase”)
    );
    

Leave a Reply

Your email address will not be published. Required fields are marked *