Azure Cosmos DB SQL API belongs to Document store databases (NoSQL). It uses JSON to store the documents, and it allows you to query the database with a similar syntax to SQL.
Stores the entities as documents (JSON, BSON, XML, etc.). They are different from the relational databases, but you can compare the taxonomy, like below.
Schema Free
You can fill in a document with whatever data you want. Documents might have different structures.
Denormalized
You do not have to be afraid of data duplication.
Strong Consistency / Eventual Consistency
Performance
You can index the fields for faster performance.
Easy to Scale-out
Because the data is combined in Aggregate, it is easy to scale the database out. In our example, we can handle one user with a sub-collection of posts as one. Aggregate is similar to Transactions-Concept in RDBM. For more information about the Aggregate and domain entities, please refer to Eric J. Evans' book -
Domain-Driven Design.
Alternatively, you can watch Martin Fowler's video on this
YouTube Link.
Example
In my demo, I have created a very simple community demo application. The domain model consists of two classes, User and Post, as shown below,
Image -4- Domain Entities
User contains a sub-collection for Posts. You can achieve this relationship with the document store database in one collection, for example, Users Collection includes the JSON documents for the Users Entities, and the Posts are embedded as shown below,
Image -4- Users Collection/Container
Entity Framework Core
Entity Framework is designed originally for the relational database system, which helps you to solve different RDBM problems like schema changes. However, with time we have seen that ORM Tools can do more than the relational database. ORM Tools offer an abstract layer between the databases and your business logic. So, they help you to change your backend without many efforts. That's why it makes sense even to use it with non-relation databases. I have before a long time tried to use EF with NoSQL but at that time. Unfortunately, the technology was not ripe enough so that I can use it in the production code.
Now with Entity Framework Core everything is changed. One important design decision in EF Core is to use it with NoSQL databases. This is not an easy job because of the nature of the NoSQL databases are very different. They are not based on a standard theory or of a standard query language. Fortunately, we have LINQ and Lambda Expressions. Those technologies became famous and well accepted among the developers so that they can help the EF Core team to offer a unique abstraction layer for all those NoSQL database models.
Blazor
Allows you to build interactive web UIs using C# instead of JavaScript. Because I am a C# Developer, I love to use it.
Enough theory and let us switch to our Community example.
Requirements for the Demo Application
Visual Studio 2019 Preview
https://docs.microsoft.com/de-de/visualstudio/releases/2019/release-notes-preview
.NET Core
.Net Core Version 3.0 Preview 6
.Net Core Version 2.2
https://dotnet.microsoft.com/download/dotnet-core/3.0
Azure Cosmos Emulator
https://docs.microsoft.com/de-de/azure/cosmos-db/local-emulator
First, I have created the domain entities
- public class User
- {
- public Guid UserId { get; set; }
-
- public string Name { get; set; }
-
- public ICollection<Post> Posts { get; set; }
- }
-
- public class Post
- {
- [Key]
- public Guid PostId { get; set; }
-
- public string Title { get; set; }
-
- public string Content { get; set; }
-
- public User User { get; set; }
- }
I have added the database context "CommunityDbContext".
- public class CommunityDbContext : DbContext
- {
- public DbSet<User> Users { get; set; }
-
- public DbSet<Post> Posts { get; set; }
-
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
-
- var Endpoint = "https://localhost:8081";
- var Key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
-
- optionsBuilder.UseCosmos(
- Endpoint,
- Key,
- "CommunityDatabase")
- .UseLoggerFactory(GenerateLoggerFactory())
- .EnableSensitiveDataLogging(true);
- }
-
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity<User>().ToContainer("Users");
- modelBuilder.Entity<User>().OwnsMany(s => s.Posts);
- base.OnModelCreating(modelBuilder);
- }
-
- private ILoggerFactory GenerateLoggerFactory()
- {
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddLogging(builder => builder.AddConsole().AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Trace));
-
- return serviceCollection.BuildServiceProvider().GetService<ILoggerFactory>();
- }
- }
Note
I have installed the Cosmos Db from NuGet,
Install-Package Microsoft.EntityFrameworkCore.Cosmos -Version 2.2.0-preview3-35497
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 2.2.6
Install-Package Microsoft.Extensions.Logging.Console -Version 2.2.0
I have used .NET Core Version 2.2 because the .NET Core 3.0 not work correctly with the Cosmos Provider does.
Note
Blazor Use .NET Core 3.x Preview!
I have copied the Endpoint and the Key from the CosmosDb Emulator as shown below,
Image -5- CosmosDb Emulator
I have enabled the query debugging as following:
- serviceCollection.AddLogging(builder => builder.AddConsole().AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Trace));
DbLoggerCategory.Database.Command dumps the database query. You can change it to Transaction or Connection depending on your needs.
Image -6- Query and Command Logs
The below line code is critical,
- modelBuilder.Entity<User>().OwnsMany(s => s.Posts);
If you remove this line and execute the application, then the Post will not be embedded inside the User Collection (Container).
More Information (mongoDB).
Model One-to-Many Relationships with Embedded Documents
https://docs.mongodb.com/manual/tutorial/model-embedded-one-to-many-relationships-between-documents/
Model One-to-Many Relationships with Document References
https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/
To make it more interesting, I have added a Repository class which is responsible for the CRUD operations.
- public class UserRepository : IDisposable
- {
- private readonly CommunityDbContext communityDbContext;
-
- public UserRepository(CommunityDbContext communityDbContext)
- {
- this.communityDbContext = communityDbContext;
- }
-
- public void Add(User user)
- {
- communityDbContext.Users.Add(user);
- }
-
- public void Delete(User user)
- {
- communityDbContext.Users.Remove(user);
- }
-
- public IQueryable<User> Find(Expression<Func<User, bool>> expression)
- {
- return communityDbContext.Users.Where(expression);
- }
-
- public ICollection<User> GetAll()
- {
- return communityDbContext.Users.ToList();
- }
-
- public void Commit()
- {
- communityDbContext.SaveChanges();
- }
-
- public void Include(string navigationPropertyPath)
- {
- _ = communityDbContext.Users.Include(navigationPropertyPath);
- }
-
- public void GenerateDatabase()
- {
- this.communityDbContext.Database.EnsureDeleted();
- this.communityDbContext.Database.EnsureCreated();
- }
-
- …
The below two lines are to delete and generate the database on the fly.
- this.communityDbContext.Database.EnsureDeleted();
- this.communityDbContext.Database.EnsureCreated();
- _ = communityDbContext.Users.Include(navigationPropertyPath);
This line to Include the Posts to the Users (Eager Loading). I do not like Lazy Loading, so please try to avoid it in your design too.
Let us take the second step. We need a UI for our application. Blazor is suitable for you as a C# Developer so, I have used it.
You do not have to install Blazor as in the past!
https://marketplace.visualstudio.com/items?itemName=aspnet.blazor
NOTE
This Blazor Visual Studio extension is obsolete and no longer required to use Blazor.
To get the latest Blazor templates install them from the command-line. Visual Studio (16.3 or later) will detect that the templates have been installed and surface them to you without the need for any additional extensions. If you already have this extension installed, you can go ahead and uninstall it.”
What I did - step by step
Your project should like,
I have to edit the WeatherTemplate and change to UserService as follows,
- using CosmosDbSQLAPI;
- using Entities;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
-
- namespace CommunityWebApp.Data
- {
- public class UserService
- {
- public UserService()
- {
-
- using (var userRepository = new UserRepository(new CommunityDbContext()))
- {
- userRepository.GenerateDatabase();
-
- for (var i = 0; i < 5; i++)
- {
- userRepository
- .Add(
- new User
- {
- Name = "Bassam_" + i,
- Posts = new Collection<Post> {
- new Post {
- Title = "www.bassam.ml",
- Content ="My page"
- }
- }
- }
- );
- }
-
- userRepository.Commit();
- }
- }
-
- public void AddPost(string username)
- {
-
- using (var userRepository = new UserRepository(new CommunityDbContext()))
- {
- var user = userRepository.Find(u => u.Name == username).Single();
- user.Posts.Add(new Post() { Content = "CSharp", Title = "I love you CosmosDb!" });
- userRepository.Commit();
- }
- }
-
- public void DeleteUser(string username)
- {
-
- using (var userRepository = new UserRepository(new CommunityDbContext()))
- {
- var user = userRepository.Find(u => u.Name == username).Single();
- userRepository.Delete(user);
- userRepository.Commit();
- }
- }
-
- public ICollection<User> GetUsers()
- {
-
- using (var userRepository = new UserRepository(new CommunityDbContext()))
- {
- userRepository.Include(nameof(User.Posts));
- return userRepository.GetAll();
- }
- }
- }
- }
The code above creates five users by initialization and adding for each user a dummy post.
The other methods are used to demonstrate CRUD operations.
I did everything synchronous just for demo in production code you have to make everything asynchronous.
Also, I have called the service on the Blazor page as follows,
- @page "/fetchdata"
- @using CommunityWebApp.Data
- @using Entities
- @inject UserService UserService
-
- <h1>Community members</h1>
-
- <p>This component demonstrates fetching data from Entity Framework Core and Cosmos DB.</p>
-
- @if (users == null)
- {
- <p><em>Loading... From Cosmos Emulator!</em></p>
- }
- else
- {
- <label style="color: red;">@ErrorText</label>
- <br />
- <input @bind-value="@Username" @bind-value:event="oninput" placeholder="Enter User Name" />
- <button class="btn btn-primary" @onclick="@AddPost">Add Post</button>
- <button class="btn btn-primary" @onclick=@DeleteUser>Delete User</button>
-
- <table class="table">
- <thead>
- <tr>
- <th>Name</th>
- <th>Id</th>
- <th>Post Count</th>
-
- </tr>
- </thead>
- <tbody>
- @foreach (var user in users)
- {
- <tr>
- <td>@user.Name</td>
- <td>@user.UserId</td>
- <td>@user.Posts.Count()</td>
-
- </tr>
- }
- </tbody>
- </table>
- }
-
- @code {
- string Username { get; set; } = string.Empty;
- string ErrorText { get; set; } = string.Empty;
-
- ICollection<User> users;
-
- void AddPost()
- {
- ErrorText = string.Empty;
- var user = users.SingleOrDefault(x => x.Name == Username);
- if (user == null)
- {
-
- ErrorText = "User does not exist!";
- return;
- }
-
- UserService.AddPost(Username);
-
-
- users = UserService.GetUsers();
- }
-
- void DeleteUser()
- {
- ErrorText = string.Empty;
- var user = users.SingleOrDefault(x => x.Name == Username);
- if (user == null)
- {
-
- ErrorText = "User does not exist!";
- return;
- }
-
- UserService.DeleteUser(Username);
-
-
- users.Remove(user);
- }
-
- protected override async Task OnInitAsync()
- {
- await Task.Run(() => users = UserService.GetUsers());
- }
- }
When you press F5, then you should see the data in the browser.
You can add more post to the user or delete a user. However, before we are doing that, let us check the database and the Logs.
Also, here you can see the Users Collection (Container is the new name). That's because I have defined that in the DbContext as follows,
- modelBuilder.Entity<User>().ToContainer("Users");
If this line does not exist, then everything stored in the CommunityDbContext as shown in the image -4- above. To get rid of the DbCummnityDbContext container, you have to change the default container name.
I have clicked a few times on the Add Post button, as shown below.
As you can see below, the Posts are added to the selected user. Let us see the database.
Finally, I have deleted the user with the name "Bassam_0".
So as we can see, the user is deleted ๐.
Summary
Azure Cosmos DB SQL API is a document-based database and it can be easily used with Entity Framework Core.
Entity Framework Core supports NoSQL databases and gives you a beautiful abstraction to your NoSQL storage models. The code sample for this article is mixing between three Microsoft technologies (Cosmos Db SQL API and Entity Framework, Blazor). I love all those technologies, and Blazor is my superhero so that I do not have to learn JavaScript.