How to Use EF Core with Azure Cosmos DB for Database Access
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
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.