How to Use EF Core with Azure Cosmos DB for Database Access

Tarik ShaikhTarik Shaikh
4 min read

Entity Framework core is an Object-Database mapper which is used to connect the application to the database. It has support for a number of databases, namely SQL Server, Oracle, MongoDB, Cosmos DB, etc.

It is a lightweight version of its predecessor Entity Framework also known as EF6. Comparison between the two, can be read here.

I will be creating a .NET 8 API project for the purpose of this demonstration.

To configure EF core to access an Azure Cosmos DB instance, we would need take a number of steps as discussed below. For the sake of understanding, I am creating a ProductsDB and performing operations on the same.

Add necessary NuGet packages

First of all, to add EF core with Cosmos DB support to the application, add the following packages.

  • Microsoft.EntityFrameworkCore

  • Microsoft.EntityFrameworkCore.Cosmos

Create the DbContext

As in Entity Framework, EF Core also requires us to create a DbContext class to interact with the database objects. In order to achieve this, you need to create a class and inherit it from DbContext which is present in the Microsoft.EntityFrameworkCore namespace.

public class ProductsDbContext : DbContext
    {
        public ProductsDbContext()
        {
        }

        public ProductsDbContext(DbContextOptions<ProductsDbContext> options): base(options)
        {
            this.Database.EnsureCreated();
        }

        public DbSet<Product> Products { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Product>()
                .ToContainer("Products");

            modelBuilder.Entity<Product>()
                .Property(x => x.id)
                .HasConversion<string>()
                .HasValueGenerator<SequentialGuidValueGenerator>();

            modelBuilder.Entity<Product>()
                .HasPartitionKey(nameof(Product.id));
        }
    }

Database settings in appsettings.json

Add the following Cosmos DB related settings in the config file. Here I am using the Azure Cosmos DB Emulator, more on which can be read here.

"Cosmos": {
    "AccountEndpoint": "https://localhost:8081/",
    "AccountKey": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
    "DatabaseName": "ProductsDB"
  }

Setup the database provider in Program.cs

In the application startup, specify the DbContext class to be used for the Cosmos DB connection.

builder.Services.AddDbContext<ProductsDbContext>(options =>
                options.UseCosmos(
                    builder.Configuration["Cosmos:AccountEndpoint"],
                    builder.Configuration["Cosmos:AccountKey"],
                    builder.Configuration["Cosmos:DatabaseName"])
            );

Here we tell the application that for the specified Cosmos DB account and database, use the ProductsDbContext and provide us the ability to interact with the database.

Create the Products repository

Here we will make use of the repository pattern to abstract out the database communication logic. First create an Interface IProductRepository having all the basic CRUD functions and then create a Class ProductRepository implementing the same.

public interface IProductRepository
    {
        Task<Product> GetProductAsync(Guid id);
        Task<bool> AddProductAsync(Product product);
        Task<bool> UpdateProductAsync(Product product);
        Task<bool> DeleteProductAsync(Guid id);
        Task<IList<Product>> GetAllProductsAsync();
    }
public class ProductRepository : IProductRepository
    {
        private readonly ProductsDbContext _db;
        public ProductRepository(ProductsDbContext db) => _db = db;

        public async Task<Product> GetProductAsync(Guid id)
        {
            try
            {
                var keyValues = new object[] { id };
                var product = await _db.Products.FindAsync(keyValues);
                return product;
            }
            catch (Exception)
            {
                // Log the exception
                return null;
            }
        }

        public async Task<bool> AddProductAsync(Product product)
        {
            try
            {
                _db.Products.Add(product);
                await _db.SaveChangesAsync();
                return true;
            }
            catch (Exception)
            {
                // Log the exception
                return false;
            }
        }

        public async Task<bool> UpdateProductAsync(Product product)
        {
            try
            {
                _db.Products.Update(product);
                await _db.SaveChangesAsync();
                return true;
            }
            catch (Exception)
            {
                // Log the exception
                return false;
            }
        }

        public async Task<bool> DeleteProductAsync(Guid id)
        {
            try
            {
                var product = await GetProductAsync(id);

                if (product != null)
                {
                    _db.Products.Remove(product);
                    await _db.SaveChangesAsync();
                    return true;
                }

                return false;
            }
            catch (Exception)
            {
                // Log the exception
                return false;
            }
        }

        public async Task<IList<Product>> GetAllProductsAsync()
        {
            try
            {
                var allProducts = await _db.Products.ToListAsync();
                return allProducts;
            }
            catch (Exception)
            {
                // Log the exception
                return null;
            }
        }
    }

The ProductRepository will have the ProductDbContext injected in the constructor and the same will be used throughout the class.

Register the repository and its dependency

Once the repository is ready, you need to register it in Program.cs, so that its dependency is injected, and it is accessible throughout the application wherever required.

builder.Services.AddScoped<IProductRepository, ProductRepository>();

Add a Products controller to handle user requests

Finally, after all the background tasks, create a Controller which will handle user requests and call the appropriate repository method for the user requested operation.

 [Route("api/[controller]")]
 [ApiController]
 public class ProductsController : ControllerBase
 {
         private readonly IProductRepository _productRepository;

         public ProductsController(IProductRepository productRepository) 
         { 
             _productRepository = productRepository;
         }

        // GET: api/<ProductsController>
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var products = await _productRepository.GetAllProductsAsync();
            return Ok(products);
        }

        // GET api/<ProductsController>/5
        [HttpGet("{id}")]
        public async Task<IActionResult> Get(Guid id)
        {
            var product = await _productRepository.GetProductAsync(id);
            return Ok(product);
        }

        // POST api/<ProductsController>
        [HttpPost]
        public async Task<IActionResult> Post([FromBody] Product value)
        {
            var result = await _productRepository.AddProductAsync(value);
            return Ok(result);
        }

        // PUT api/<ProductsController>/5
        [HttpPut]
        public async Task<IActionResult> Put([FromBody] Product value)
        {
            var result = await _productRepository.UpdateProductAsync(value);
            return Ok(result);
        }

        // DELETE api/<ProductsController>/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(Guid id)
        {
            var result = await _productRepository.DeleteProductAsync(id);
            return Ok(result);
        }
 }

The ProductsController will have the IProductRepository injected in the constructor and will be used to call the ProductRepository implementation as specified in the Program.cs.

Code

Find the entire code for this demo below.

References

0
Subscribe to my newsletter

Read articles from Tarik Shaikh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Tarik Shaikh
Tarik Shaikh

As a software engineer by profession, I am here to share my knowledge and experience.