Entity Framework - realestateexplorer/SystemDesign GitHub Wiki

how to configure Entity Framework Core


Of course! Configuring Entity Framework Core involves a few key steps, from setting up the database connection to defining your data model.

This comprehensive guide will walk you through the entire process, from basic setup to more advanced configurations.

The 3 Main Areas of Configuration

  1. DbContext Configuration: Telling EF Core which database to connect to (e.g., SQL Server, PostgreSQL, SQLite) and providing the connection string. This is typically done with Dependency Injection.
  2. Model Configuration: Defining your entities (tables), their properties (columns), and the relationships between them (keys, foreign keys).
  3. Runtime Behavior Configuration: Adjusting how EF Core behaves, such as logging, query tracking, and connection resiliency.

Step 1: Prerequisites - Install NuGet Packages

First, you need to install the necessary packages. You'll always need a provider package for your specific database and the design package for tooling (like migrations).

  • For SQL Server:
    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    dotnet add package Microsoft.EntityFrameworkCore.Design
  • For PostgreSQL:
    dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
    dotnet add package Microsoft.EntityFrameworkCore.Design
  • For SQLite:
    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    dotnet add package Microsoft.EntityFrameworkCore.Design
  • For In-Memory (testing):
    dotnet add package Microsoft.EntityFrameworkCore.InMemory

Step 2: Create Your DbContext and Entities

The DbContext is the heart of EF Core. It represents a session with the database and allows you to query and save data. Your entity classes are simple C# objects (POCOs) that represent the tables in your database.

Example Entities:

// Models/Blog.cs
public class Blog
{
    public int BlogId { get; set; } // Will become the Primary Key by convention
    public string Url { get; set; }
    public List<Post> Posts { get; set; } = new List<Post>(); // Navigation property
}

// Models/Post.cs
public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; } // Foreign Key
    public Blog Blog { get; set; }  // Navigation property
}

The DbContext:

using Microsoft.EntityFrameworkCore;

// Data/BlogContext.cs
public class BlogContext : DbContext
{
    // The constructor is essential for dependency injection
    public BlogContext(DbContextOptions<BlogContext> options) : base(options)
    {
    }

    // Each DbSet represents a table in the database
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    // This is where you can configure the model using the Fluent API (see Step 4)
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Fluent API configuration goes here
    }
}

Step 3: Configure the DbContext with Dependency Injection (DI)

This is the most common and recommended way to configure EF Core, especially in an ASP.NET Core application.

  1. Add Connection String to appsettings.json:

    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyBlogDb;Trusted_Connection=True;"
      },
      // ... other settings
    }
  2. Register the DbContext in Program.cs (.NET 6+) or Startup.cs:

    This tells your application how to create instances of your BlogContext.

    In Program.cs (.NET 6+ Minimal API style):

    using Microsoft.EntityFrameworkCore;
    using YourApp.Data; // Replace with the namespace of your DbContext
    
    var builder = WebApplication.CreateBuilder(args);
    
    // 1. Get the connection string
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    
    // 2. Add the DbContext to the services container
    builder.Services.AddDbContext<BlogContext>(options =>
        options.UseSqlServer(connectionString)); // UseSqlServer, UseNpgsql, UseSqlite, etc.
    
    // ... Add other services ...
    
    var app = builder.Build();
    
    // ... Configure the HTTP request pipeline ...
    
    app.Run();

The AddDbContext method registers your DbContext with a scoped lifetime by default, which is the correct lifetime for a web application (one instance per HTTP request).


Step 4: Configure the Data Model

EF Core uses a set of conventions to build the model, but you often need to customize it. There are two primary ways to do this: Data Annotations and the Fluent API.

Best Practice: Use the Fluent API for most configurations. It is more powerful, flexible, and keeps your entity classes clean (POCOs). Data Annotations are fine for simple validation rules.

Method A: Data Annotations (Attributes on your entities)

This method is quick and easy for simple configurations.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

public class Post
{
    [Key] // Explicitly set as Primary Key
    public int PostId { get; set; }

    [Required] // Makes the column NOT NULL
    [MaxLength(200)] // Sets the max length (e.g., NVARCHAR(200))
    public string Title { get; set; }

    [Column("PostBody", TypeName = "ntext")] // Custom column name and type
    public string Content { get; set; }

    // Foreign Key configuration
    public int BlogId { get; set; }
    [ForeignKey("BlogId")]
    public Blog Blog { get; set; }
}

Method B: Fluent API (In OnModelCreating in your DbContext)

The Fluent API provides fine-grained control over your model configuration in a single, centralized place.

// Inside your BlogContext class
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Configure the Blog entity
    modelBuilder.Entity<Blog>(entity =>
    {
        // Set the primary key
        entity.HasKey(b => b.BlogId);

        // Configure a property
        entity.Property(b => b.Url)
            .IsRequired()
            .HasMaxLength(500);

        // Define the one-to-many relationship with Post
        entity.HasMany(b => b.Posts)      // A Blog has many Posts
              .WithOne(p => p.Blog)       // A Post has one Blog
              .HasForeignKey(p => p.BlogId); // The foreign key is BlogId on Post
    });

    // Configure the Post entity
    modelBuilder.Entity<Post>(entity =>
    {
        entity.ToTable("Posts"); // Set the table name explicitly

        entity.HasKey(p => p.PostId);

        entity.Property(p => p.Title)
            .IsRequired()
            .HasMaxLength(200);

        entity.Property(p => p.Content)
            .HasColumnName("PostBody")
            .HasColumnType("ntext");
    });
}

Best Practice: Separate Configuration Files

For larger models, your OnModelCreating method can get crowded. You can split the configuration into separate classes.

  1. Create a configuration class for each entity:

    // Data/Configuration/BlogConfiguration.cs
    public class BlogConfiguration : IEntityTypeConfiguration<Blog>
    {
        public void Configure(EntityTypeBuilder<Blog> builder)
        {
            builder.HasKey(b => b.BlogId);
    
            builder.Property(b => b.Url)
                .IsRequired()
                .HasMaxLength(500);
    
            builder.HasMany(b => b.Posts)
                   .WithOne(p => p.Blog)
                   .HasForeignKey(p => p.BlogId);
        }
    }
  2. Apply these configurations in OnModelCreating:

    // Inside your BlogContext class
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Apply a single configuration
        modelBuilder.ApplyConfiguration(new BlogConfiguration());
    
        // Or, apply all configurations from an assembly automatically
        // modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogContext).Assembly);
    }

Step 5: Advanced & Runtime Configuration

You can configure additional behaviors in the AddDbContext call.

builder.Services.AddDbContext<BlogContext>(options =>
{
    options.UseSqlServer(
        connectionString,
        sqlServerOptions =>
        {
            // Enable connection resiliency for cloud databases like Azure SQL
            sqlServerOptions.EnableRetryOnFailure(
                maxRetryCount: 5,
                maxRetryDelay: TimeSpan.FromSeconds(30),
                errorNumbersToAdd: null);
        });

    // Log SQL queries to the console (great for debugging)
    options.LogTo(Console.WriteLine, LogLevel.Information);

    // For better performance in read-only scenarios, disable tracking globally
    // options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
});

Summary: A Complete Workflow

  1. Install Packages: Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Design.
  2. Create Models: Define your C# entity classes (e.g., Blog, Post).
  3. Create DbContext: Create a class that inherits from DbContext (e.g., BlogContext).
  4. Add Connection String: Put your database connection string in appsettings.json.
  5. Configure DI: Use builder.Services.AddDbContext<...>() in Program.cs.
  6. Configure Model (Recommended): Use the Fluent API inside OnModelCreating or, even better, in separate IEntityTypeConfiguration classes.
  7. Create Migrations: Run dotnet ef migrations add InitialCreate in the terminal.
  8. Apply Migrations: Run dotnet ef database update to create/update your database schema.

what is Fluent API


Excellent question! The Fluent API is a central concept in Entity Framework Core. It's one of the primary ways you configure your data model, and it's generally considered the most powerful and flexible method.

What is the Fluent API?

In short, the Fluent API is a way to configure your EF Core model using a chain of C# method calls. The name "fluent" comes from the way these methods are chained together, creating a readable, sentence-like structure.

This configuration happens inside the OnModelCreating method of your DbContext. You are given a ModelBuilder object, which is the entry point for all your configurations.

Here is the basic structure:

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    // The Fluent API lives here!
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Example: "For the Blog entity, the primary key is the BlogId property."
        modelBuilder.Entity<Blog>()
                    .HasKey(b => b.BlogId);

        // Example: "For the Post entity, the Title property is required and has a max length of 200."
        modelBuilder.Entity<Post>()
                    .Property(p => p.Title)
                    .IsRequired()
                    .HasMaxLength(200);
    }
}

Why Use the Fluent API? (vs. Data Annotations)

EF Core provides two ways to configure the model: Data Annotations (attributes like [Key], [Required]) and the Fluent API. While Data Annotations are simpler for basic cases, the Fluent API is superior for several reasons:

  1. More Powerful: There are many configurations that can only be done using the Fluent API. For example, configuring shadow properties, owned entity types, or complex index definitions.
  2. Keeps Models Clean (POCOs): Your entity classes (e.g., Blog, Post) remain "Plain Old C# Objects" (POCOs). They don't need any attributes or dependencies on Entity Framework. This is excellent for separating your domain logic from your persistence logic.
  3. Centralized Configuration: All of your database mapping logic is located in one place (OnModelCreating or related configuration classes). You don't have to hunt through multiple entity files to see how the database is structured.
  4. Better Separation of Concerns: Your domain model (Blog.cs) describes the business entity, while your Fluent API configuration describes how that entity is stored in the database. These are two separate concerns.

Fluent API vs. Data Annotations: A Quick Comparison

Feature Fluent API Data Annotations
Location OnModelCreating in DbContext Attributes on entity classes
Power Very High. Can configure everything. Limited. Covers common scenarios.
Clean Models Yes. Keeps entities free of EF attributes. No. Clutters domain models.
Centralization Yes. All config is in one place. No. Config is scattered across files.
Readability Can be very readable and "fluent". Easy to see a single property's rules.
Best For All non-trivial applications. Quick prototypes, simple validation rules.

Common Fluent API Examples

Let's explore some of the most common configuration tasks you'll perform with the Fluent API.

1. Configuring an Entity and its Table

modelBuilder.Entity<Post>(entity =>
{
    // Map the entity to a specific table name
    entity.ToTable("BlogPosts");

    // Set the primary key
    entity.HasKey(p => p.PostId);

    // Create an index on the Url property and make it unique
    entity.HasIndex(p => p.Url).IsUnique();
});

2. Configuring Properties

modelBuilder.Entity<Post>(entity =>
{
    // Make a property required (NOT NULL in the database)
    entity.Property(p => p.Title)
          .IsRequired();

    // Set a maximum length (e.g., NVARCHAR(200))
    entity.Property(p => p.Title)
          .HasMaxLength(200);

    // Change the column name and database type
    entity.Property(p => p.Content)
          .HasColumnName("PostBody")
          .HasColumnType("ntext");

    // Set a default value in the database
    entity.Property(p => p.PublishedOn)
          .HasDefaultValueSql("GETDATE()"); // SQL Server specific
});

3. Configuring Relationships (This is where it truly shines)

Let's say we have Blog and Post entities. A Blog has many Posts, and a Post belongs to one Blog.

// Inside OnModelCreating...

// Configure the One-to-Many relationship
modelBuilder.Entity<Blog>()
    .HasMany(b => b.Posts)      // A Blog has many Posts...
    .WithOne(p => p.Blog)       // ...and a Post has one Blog...
    .HasForeignKey(p => p.BlogId); // ...using the BlogId property as the foreign key.

This is far more explicit and powerful than trying to let conventions or annotations figure it out, especially for complex relationships.

4. Configuring a Shadow Property

A shadow property is a property that is not defined on your .NET entity class but exists in the EF Core model and is mapped to a database column. This is impossible with Data Annotations.

// Add a "LastUpdated" shadow property to the Post entity
modelBuilder.Entity<Post>()
    .Property<DateTime>("LastUpdated");

You can then access this property through EF Core's Change Tracker API.


Best Practice: Separate Configuration Files

For larger applications, the OnModelCreating method can become extremely large. The best practice is to separate the configuration for each entity into its own class using the IEntityTypeConfiguration<T> interface.

  1. Create a Configuration Class:

    // Data/Configurations/PostConfiguration.cs
    public class PostConfiguration : IEntityTypeConfiguration<Post>
    {
        public void Configure(EntityTypeBuilder<Post> builder)
        {
            builder.ToTable("BlogPosts");
    
            builder.HasKey(p => p.PostId);
    
            builder.Property(p => p.Title)
                   .IsRequired()
                   .HasMaxLength(200);
    
            // ... other configurations for the Post entity
        }
    }
  2. Apply the Configuration in OnModelCreating:

    // Inside your DbContext's OnModelCreating method
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Apply a single configuration
        modelBuilder.ApplyConfiguration(new PostConfiguration());
        modelBuilder.ApplyConfiguration(new BlogConfiguration());
    
        // OR, even better, apply all configurations from an assembly at once
        // modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogContext).Assembly);
    }

Summary

The Fluent API is the de facto standard for configuring EF Core models in professional applications. It provides maximum power, promotes a clean domain model, and centralizes your persistence logic, making your application more maintainable and robust.


provide examples of CRUD operation for Entity Framework and Azure Cosmos DB in C#


Of course! Here are detailed examples of CRUD (Create, Read, Update, Delete) operations for Azure Cosmos DB using Entity Framework Core in C#.

This guide will walk you through setting up a simple console application to demonstrate these operations.

Prerequisites

  1. .NET SDK installed (e.g., .NET 6, .NET 7, or .NET 8).
  2. An Azure Cosmos DB Account.
    • Make sure you are using the NoSQL API (formerly Core SQL API).
    • Have your Endpoint URI and Primary Key ready.
  3. A code editor like Visual Studio or VS Code.

Step 1: Set Up the Project and Install NuGet Packages

  1. Create a new C# Console Application.

    dotnet new console -n EfCosmosCrud
    cd EfCosmosCrud
  2. Install the EF Core Cosmos DB provider package.

    dotnet add package Microsoft.EntityFrameworkCore.Cosmos

Step 2: Define the Data Model

This is the C# class that will represent the documents in your Cosmos DB container. For this example, we'll create an Order class.

Key Points for Cosmos DB Models:

  • You must have a property named id (lowercase) of type string. EF Core will map this to Cosmos DB's required id field.
  • You must define a partition key for performance and scalability.

Create a new file named Order.cs:

// Order.cs
using Newtonsoft.Json;

public class Order
{
    // The unique ID for the document. Mapped to 'id' in Cosmos DB.
    [JsonProperty("id")]
    public Guid Id { get; set; }

    // This property will be used as the Partition Key.
    public string CustomerName { get; set; } = string.Empty;

    public string ShippingAddress { get; set; } = string.Empty;

    public decimal TotalPrice { get; set; }

    public DateTime OrderDate { get; set; }

    // We must have a ToString() to make our object readable in the console.
    public override string ToString()
    {
        return $"Order [Id: {Id}, Customer: {CustomerName}, Price: {TotalPrice:C}, Date: {OrderDate.ToShortDateString()}]";
    }
}

Note: Using [JsonProperty("id")] on a Guid property is a common pattern to let EF Core manage the required lowercase id string property in the background.


Step 3: Create the DbContext

The DbContext is the bridge between your C# code and the database. It's where you configure the connection and define your data sets (DbSet<T>).

Create a new file named OrderContext.cs:

// OrderContext.cs
using Microsoft.EntityFrameworkCore;

public class OrderContext : DbContext
{
    // Represents the collection/container of Orders in the database.
    public DbSet<Order> Orders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // --- IMPORTANT ---
        // Replace with your own Cosmos DB Endpoint and Primary Key.
        // In a real application, use a secure configuration method (e.g., appsettings.json, Azure Key Vault).
        string endpoint = "YOUR_COSMOS_DB_ENDPOINT";
        string key = "YOUR_COSMOS_DB_KEY";
        string databaseName = "EFCosmosDemoDb";

        optionsBuilder.UseCosmos(endpoint, key, databaseName);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure the Order entity
        modelBuilder.Entity<Order>(entity =>
        {
            // Set the container name
            entity.ToContainer("Orders");

            // Define the partition key
            entity.HasPartitionKey(o => o.CustomerName);
            
            // By default, EF Core adds a "discriminator" property to distinguish between
            // different entity types in the same container. Since we only have one type,
            // we can disable it. This is a good practice for single-model containers.
            entity.HasNoDiscriminator();
        });
    }
}

Step 4: Implement CRUD Operations in Program.cs

Now, let's put everything together in your Program.cs file to perform the CRUD operations.

// Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine("Entity Framework Core with Cosmos DB - CRUD Demo");
        Console.WriteLine("==============================================");

        // DbContext is a lightweight unit of work. Create and dispose it for each operation.
        // The 'using' statement handles disposal automatically.
        using var context = new OrderContext();
        
        // Ensure the database and container are created. Great for demos, not for production.
        // In production, you'd use Infrastructure as Code (Bicep, ARM, Terraform).
        await context.Database.EnsureCreatedAsync();
        Console.WriteLine("Database and container are ready.\n");

        // We'll store the ID of the created order to use it in other operations.
        Guid createdOrderId = Guid.Empty;
        string customerName = "John Doe"; // This is our partition key value

        // === 1. CREATE (Add a new Order) ===
        Console.WriteLine("--- 1. CREATE Operation ---");
        var newOrder = new Order
        {
            Id = Guid.NewGuid(),
            CustomerName = customerName,
            ShippingAddress = "123 Main St, Anytown, USA",
            TotalPrice = 199.99m,
            OrderDate = DateTime.UtcNow
        };

        await context.Orders.AddAsync(newOrder);
        await context.SaveChangesAsync();
        createdOrderId = newOrder.Id;
        Console.WriteLine($"Created new order: {newOrder}\n");


        // === 2. READ Operations ===
        Console.WriteLine("--- 2. READ Operations ---");

        // A. Read a single item (most efficient way)
        // FindAsync is optimized to use the ID and Partition Key for a fast point-read.
        Console.WriteLine("Reading a single order by ID and Partition Key...");
        var foundOrder = await context.Orders.FindAsync(createdOrderId, customerName);
        if (foundOrder != null)
        {
            Console.WriteLine($"Found order: {foundOrder}\n");
        }
        else
        {
            Console.WriteLine("Order not found.\n");
        }

        // B. Read multiple items using a LINQ query
        Console.WriteLine("Reading all orders for customer 'John Doe'...");
        var customerOrders = await context.Orders
            .Where(o => o.CustomerName == customerName) // This query includes the partition key, making it efficient.
            .ToListAsync();
        
        Console.WriteLine($"Found {customerOrders.Count} orders for {customerName}:");
        foreach (var order in customerOrders)
        {
            Console.WriteLine($"- {order}");
        }
        Console.WriteLine();


        // === 3. UPDATE Operation ===
        Console.WriteLine("--- 3. UPDATE Operation ---");
        var orderToUpdate = await context.Orders.FindAsync(createdOrderId, customerName);
        if (orderToUpdate != null)
        {
            Console.WriteLine($"Updating order {orderToUpdate.Id}...");
            orderToUpdate.ShippingAddress = "456 Oak Ave, Sometown, USA"; // Change a property
            // EF Core's change tracker automatically detects the modification.
            
            await context.SaveChangesAsync(); // This single call persists the change.
            Console.WriteLine($"Updated order: {orderToUpdate}\n");
        }


        // === 4. DELETE Operation ===
        Console.WriteLine("--- 4. DELETE Operation ---");
        var orderToDelete = await context.Orders.FindAsync(createdOrderId, customerName);
        if (orderToDelete != null)
        {
            Console.WriteLine($"Deleting order {orderToDelete.Id}...");
            context.Orders.Remove(orderToDelete); // Mark the entity for deletion.
            
            await context.SaveChangesAsync(); // Commit the deletion to the database.
            Console.WriteLine("Order deleted successfully.\n");
        }
        
        // Verify deletion
        var deletedOrder = await context.Orders.FindAsync(createdOrderId, customerName);
        Console.WriteLine(deletedOrder == null 
            ? "Verification successful: Order no longer exists." 
            : "Verification failed: Order still exists.");
    }
}

How to Run the Example

  1. Replace "YOUR_COSMOS_DB_ENDPOINT" and "YOUR_COSMOS_DB_KEY" in OrderContext.cs with your actual credentials.
  2. Open your terminal in the project directory.
  3. Run the application:
    dotnet run

Expected Output

You will see a console output that walks through each CRUD step, confirming the creation, reading, updating, and final deletion of the order document in your Cosmos DB container.

Entity Framework Core with Cosmos DB - CRUD Demo
==============================================
Database and container are ready.

--- 1. CREATE Operation ---
Created new order: Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

--- 2. READ Operations ---
Reading a single order by ID and Partition Key...
Found order: Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

Reading all orders for customer 'John Doe'...
Found 1 orders for John Doe:
- Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

--- 3. UPDATE Operation ---
Updating order a1b2c3d4...
Updated order: Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

--- 4. DELETE Operation ---
Deleting order a1b2c3d4...
Order deleted successfully.

Verification successful: Order no longer exists.

provide examples of CRUD operation for Entity Framework and Azure Cosmos DB in C#


Of course! Here are detailed examples of CRUD (Create, Read, Update, Delete) operations for Azure Cosmos DB using Entity Framework Core in C#.

This guide will walk you through setting up a simple console application to demonstrate these operations.

Prerequisites

  1. .NET SDK installed (e.g., .NET 6, .NET 7, or .NET 8).
  2. An Azure Cosmos DB Account.
    • Make sure you are using the NoSQL API (formerly Core SQL API).
    • Have your Endpoint URI and Primary Key ready.
  3. A code editor like Visual Studio or VS Code.

Step 1: Set Up the Project and Install NuGet Packages

  1. Create a new C# Console Application.

    dotnet new console -n EfCosmosCrud
    cd EfCosmosCrud
  2. Install the EF Core Cosmos DB provider package.

    dotnet add package Microsoft.EntityFrameworkCore.Cosmos

Step 2: Define the Data Model

This is the C# class that will represent the documents in your Cosmos DB container. For this example, we'll create an Order class.

Key Points for Cosmos DB Models:

  • You must have a property named id (lowercase) of type string. EF Core will map this to Cosmos DB's required id field.
  • You must define a partition key for performance and scalability.

Create a new file named Order.cs:

// Order.cs
using Newtonsoft.Json;

public class Order
{
    // The unique ID for the document. Mapped to 'id' in Cosmos DB.
    [JsonProperty("id")]
    public Guid Id { get; set; }

    // This property will be used as the Partition Key.
    public string CustomerName { get; set; } = string.Empty;

    public string ShippingAddress { get; set; } = string.Empty;

    public decimal TotalPrice { get; set; }

    public DateTime OrderDate { get; set; }

    // We must have a ToString() to make our object readable in the console.
    public override string ToString()
    {
        return $"Order [Id: {Id}, Customer: {CustomerName}, Price: {TotalPrice:C}, Date: {OrderDate.ToShortDateString()}]";
    }
}

Note: Using [JsonProperty("id")] on a Guid property is a common pattern to let EF Core manage the required lowercase id string property in the background.


Step 3: Create the DbContext

The DbContext is the bridge between your C# code and the database. It's where you configure the connection and define your data sets (DbSet<T>).

Create a new file named OrderContext.cs:

// OrderContext.cs
using Microsoft.EntityFrameworkCore;

public class OrderContext : DbContext
{
    // Represents the collection/container of Orders in the database.
    public DbSet<Order> Orders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // --- IMPORTANT ---
        // Replace with your own Cosmos DB Endpoint and Primary Key.
        // In a real application, use a secure configuration method (e.g., appsettings.json, Azure Key Vault).
        string endpoint = "YOUR_COSMOS_DB_ENDPOINT";
        string key = "YOUR_COSMOS_DB_KEY";
        string databaseName = "EFCosmosDemoDb";

        optionsBuilder.UseCosmos(endpoint, key, databaseName);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure the Order entity
        modelBuilder.Entity<Order>(entity =>
        {
            // Set the container name
            entity.ToContainer("Orders");

            // Define the partition key
            entity.HasPartitionKey(o => o.CustomerName);
            
            // By default, EF Core adds a "discriminator" property to distinguish between
            // different entity types in the same container. Since we only have one type,
            // we can disable it. This is a good practice for single-model containers.
            entity.HasNoDiscriminator();
        });
    }
}

Step 4: Implement CRUD Operations in Program.cs

Now, let's put everything together in your Program.cs file to perform the CRUD operations.

// Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine("Entity Framework Core with Cosmos DB - CRUD Demo");
        Console.WriteLine("==============================================");

        // DbContext is a lightweight unit of work. Create and dispose it for each operation.
        // The 'using' statement handles disposal automatically.
        using var context = new OrderContext();
        
        // Ensure the database and container are created. Great for demos, not for production.
        // In production, you'd use Infrastructure as Code (Bicep, ARM, Terraform).
        await context.Database.EnsureCreatedAsync();
        Console.WriteLine("Database and container are ready.\n");

        // We'll store the ID of the created order to use it in other operations.
        Guid createdOrderId = Guid.Empty;
        string customerName = "John Doe"; // This is our partition key value

        // === 1. CREATE (Add a new Order) ===
        Console.WriteLine("--- 1. CREATE Operation ---");
        var newOrder = new Order
        {
            Id = Guid.NewGuid(),
            CustomerName = customerName,
            ShippingAddress = "123 Main St, Anytown, USA",
            TotalPrice = 199.99m,
            OrderDate = DateTime.UtcNow
        };

        await context.Orders.AddAsync(newOrder);
        await context.SaveChangesAsync();
        createdOrderId = newOrder.Id;
        Console.WriteLine($"Created new order: {newOrder}\n");


        // === 2. READ Operations ===
        Console.WriteLine("--- 2. READ Operations ---");

        // A. Read a single item (most efficient way)
        // FindAsync is optimized to use the ID and Partition Key for a fast point-read.
        Console.WriteLine("Reading a single order by ID and Partition Key...");
        var foundOrder = await context.Orders.FindAsync(createdOrderId, customerName);
        if (foundOrder != null)
        {
            Console.WriteLine($"Found order: {foundOrder}\n");
        }
        else
        {
            Console.WriteLine("Order not found.\n");
        }

        // B. Read multiple items using a LINQ query
        Console.WriteLine("Reading all orders for customer 'John Doe'...");
        var customerOrders = await context.Orders
            .Where(o => o.CustomerName == customerName) // This query includes the partition key, making it efficient.
            .ToListAsync();
        
        Console.WriteLine($"Found {customerOrders.Count} orders for {customerName}:");
        foreach (var order in customerOrders)
        {
            Console.WriteLine($"- {order}");
        }
        Console.WriteLine();


        // === 3. UPDATE Operation ===
        Console.WriteLine("--- 3. UPDATE Operation ---");
        var orderToUpdate = await context.Orders.FindAsync(createdOrderId, customerName);
        if (orderToUpdate != null)
        {
            Console.WriteLine($"Updating order {orderToUpdate.Id}...");
            orderToUpdate.ShippingAddress = "456 Oak Ave, Sometown, USA"; // Change a property
            // EF Core's change tracker automatically detects the modification.
            
            await context.SaveChangesAsync(); // This single call persists the change.
            Console.WriteLine($"Updated order: {orderToUpdate}\n");
        }


        // === 4. DELETE Operation ===
        Console.WriteLine("--- 4. DELETE Operation ---");
        var orderToDelete = await context.Orders.FindAsync(createdOrderId, customerName);
        if (orderToDelete != null)
        {
            Console.WriteLine($"Deleting order {orderToDelete.Id}...");
            context.Orders.Remove(orderToDelete); // Mark the entity for deletion.
            
            await context.SaveChangesAsync(); // Commit the deletion to the database.
            Console.WriteLine("Order deleted successfully.\n");
        }
        
        // Verify deletion
        var deletedOrder = await context.Orders.FindAsync(createdOrderId, customerName);
        Console.WriteLine(deletedOrder == null 
            ? "Verification successful: Order no longer exists." 
            : "Verification failed: Order still exists.");
    }
}

How to Run the Example

  1. Replace "YOUR_COSMOS_DB_ENDPOINT" and "YOUR_COSMOS_DB_KEY" in OrderContext.cs with your actual credentials.
  2. Open your terminal in the project directory.
  3. Run the application:
    dotnet run

Expected Output

You will see a console output that walks through each CRUD step, confirming the creation, reading, updating, and final deletion of the order document in your Cosmos DB container.

Entity Framework Core with Cosmos DB - CRUD Demo
==============================================
Database and container are ready.

--- 1. CREATE Operation ---
Created new order: Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

--- 2. READ Operations ---
Reading a single order by ID and Partition Key...
Found order: Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

Reading all orders for customer 'John Doe'...
Found 1 orders for John Doe:
- Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

--- 3. UPDATE Operation ---
Updating order a1b2c3d4...
Updated order: Order [Id: a1b2c3d4..., Customer: John Doe, Price: $199.99, Date: 10/27/2023]

--- 4. DELETE Operation ---
Deleting order a1b2c3d4...
Order deleted successfully.

Verification successful: Order no longer exists.

explain NoSQL API in examples above


Excellent question. This is a key concept to understand.

The NoSQL API (formerly called the Core SQL API) in Azure Cosmos DB is the default and most widely used interface. The name can be a bit confusing. Let's break down what it means and how it's reflected in the C# examples.

What "NoSQL API" Really Means

Think of it as "Not only SQL, with a SQL-like query language."

It's a document database. This means:

  1. Data is stored in JSON documents: Instead of rows and columns in a table, your data is stored as flexible JSON objects.
  2. Schema-on-read: Unlike a traditional SQL database where you must define a rigid table schema upfront, a document database is more flexible. You can have documents with different structures within the same collection (called a "container").
  3. SQL-like query language: You can query these JSON documents using a familiar, SQL-like syntax. This is the "SQL" part of the original name and a major selling point.

Let's connect these concepts directly to the C# code you saw.


How the NoSQL API is Represented in the C# Example

1. Your C# Class is the Blueprint for a JSON Document

The Order class you defined is a direct representation of the structure of the JSON document that will be stored in Cosmos DB.

Your C# Class (Order.cs):

public class Order
{
    [JsonProperty("id")]
    public Guid Id { get; set; } // Will become the 'id' field in JSON
    public string CustomerName { get; set; }
    public string ShippingAddress { get; set; }
    public decimal TotalPrice { get; set; }
    public DateTime OrderDate { get; set; }
}

What EF Core and Cosmos DB store (the JSON Document): When you save an instance of this class, it gets serialized into a JSON document that looks like this in the database:

{
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "CustomerName": "John Doe",
    "ShippingAddress": "123 Main St, Anytown, USA",
    "TotalPrice": 199.99,
    "OrderDate": "2023-10-27T10:30:00Z",
    "_rid": "...", // System-generated properties
    "_self": "...",
    "_etag": "...",
    "_attachments": "...",
    "_ts": 1698394200
}

The NoSQL API is all about managing these JSON documents.

2. DbSet<Order> Maps to a Cosmos DB "Container"

In the NoSQL world, you don't have "tables"; you have "collections" or, in Cosmos DB's terminology, containers. A container is simply a logical grouping of your JSON documents.

Your C# Code (OrderContext.cs):

public DbSet<Order> Orders { get; set; } // This represents the container

// ... in OnModelCreating ...
modelBuilder.Entity<Order>().ToContainer("Orders"); // Explicitly names the container

This code tells EF Core: "I want to work with a container named Orders, and the documents inside it will have the structure of my Order class."

3. LINQ Queries are Translated into the NoSQL API's Query Language

This is the most powerful feature. You get to write expressive, type-safe C# LINQ queries, and EF Core translates them into the Cosmos DB SQL-like query language for you.

Your C# LINQ Query (Program.cs):

var customerOrders = await context.Orders
    .Where(o => o.CustomerName == "John Doe" && o.TotalPrice > 100)
    .ToListAsync();

What EF Core translates this into (Cosmos DB SQL Query): Behind the scenes, EF Core generates a query string that it sends to the NoSQL API. The query looks very similar to standard SQL:

SELECT *
FROM c
WHERE c.CustomerName = 'John Doe' AND c.TotalPrice > 100

(Here, c is just an alias for the documents in the container, similar to how you'd alias a table in SQL).

The NoSQL API engine executes this query against the JSON documents in your container and returns the results, which EF Core then deserializes back into a List<Order>.

4. The id and Partition Key are Core Concepts of the NoSQL API

The NoSQL API model is built for massive scale, and two properties are critical for this:

  • id: A unique identifier for a document within a logical partition. The [JsonProperty("id")] attribute maps your Guid property to this required lowercase string field.
  • Partition Key: This is the most important property for performance and scalability. It determines how Cosmos DB physically distributes your data across different servers.

Your C# Code (OrderContext.cs):

// Defines which property in your document is the partition key
entity.HasPartitionKey(o => o.CustomerName);

By choosing CustomerName as the partition key, you are telling Cosmos DB to group all orders for the same customer together physically.

Why this matters for the API:

  • Efficient Reads: When you perform a "point read" using both the id and the partition key value, it's the fastest and cheapest operation possible. This is what FindAsync does.
    // This is a highly efficient "point read"
    var foundOrder = await context.Orders.FindAsync(createdOrderId, customerName);
  • Efficient Queries: Queries that include the partition key in the WHERE clause (like our LINQ example) are very efficient because Cosmos DB knows exactly which physical server(s) to search. These are called "in-partition" queries.
  • Cross-Partition Queries: If you were to query without the partition key (e.g., context.Orders.Where(o => o.TotalPrice > 500)), Cosmos DB would have to "fan out" the query to all physical partitions, making it slower and more expensive.

Summary Table

C# / EF Core Concept Cosmos DB NoSQL API Concept Explanation
class Order { ... } JSON Document Your C# object model defines the structure of the data.
DbSet<Order> Container A collection that holds all your Order JSON documents.
Where(o => ...) SQL-like Query Your LINQ query is translated into a SQL-like query string.
[JsonProperty("id")] id property The unique identifier for a document within a partition.
HasPartitionKey(...) Partition Key The property used to distribute data for scale and performance.

In short, the NoSQL API provides a document-oriented database model that you can interact with using a familiar SQL-like syntax. The Entity Framework Core provider acts as a brilliant abstraction layer, allowing you to work with this model using standard C# classes and LINQ queries.


explain NoSQL API in examples above


Excellent question. This is a key concept to understand.

The NoSQL API (formerly called the Core SQL API) in Azure Cosmos DB is the default and most widely used interface. The name can be a bit confusing. Let's break down what it means and how it's reflected in the C# examples.

What "NoSQL API" Really Means

Think of it as "Not only SQL, with a SQL-like query language."

It's a document database. This means:

  1. Data is stored in JSON documents: Instead of rows and columns in a table, your data is stored as flexible JSON objects.
  2. Schema-on-read: Unlike a traditional SQL database where you must define a rigid table schema upfront, a document database is more flexible. You can have documents with different structures within the same collection (called a "container").
  3. SQL-like query language: You can query these JSON documents using a familiar, SQL-like syntax. This is the "SQL" part of the original name and a major selling point.

Let's connect these concepts directly to the C# code you saw.


How the NoSQL API is Represented in the C# Example

1. Your C# Class is the Blueprint for a JSON Document

The Order class you defined is a direct representation of the structure of the JSON document that will be stored in Cosmos DB.

Your C# Class (Order.cs):

public class Order
{
    [JsonProperty("id")]
    public Guid Id { get; set; } // Will become the 'id' field in JSON
    public string CustomerName { get; set; }
    public string ShippingAddress { get; set; }
    public decimal TotalPrice { get; set; }
    public DateTime OrderDate { get; set; }
}

What EF Core and Cosmos DB store (the JSON Document): When you save an instance of this class, it gets serialized into a JSON document that looks like this in the database:

{
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "CustomerName": "John Doe",
    "ShippingAddress": "123 Main St, Anytown, USA",
    "TotalPrice": 199.99,
    "OrderDate": "2023-10-27T10:30:00Z",
    "_rid": "...", // System-generated properties
    "_self": "...",
    "_etag": "...",
    "_attachments": "...",
    "_ts": 1698394200
}

The NoSQL API is all about managing these JSON documents.

2. DbSet<Order> Maps to a Cosmos DB "Container"

In the NoSQL world, you don't have "tables"; you have "collections" or, in Cosmos DB's terminology, containers. A container is simply a logical grouping of your JSON documents.

Your C# Code (OrderContext.cs):

public DbSet<Order> Orders { get; set; } // This represents the container

// ... in OnModelCreating ...
modelBuilder.Entity<Order>().ToContainer("Orders"); // Explicitly names the container

This code tells EF Core: "I want to work with a container named Orders, and the documents inside it will have the structure of my Order class."

3. LINQ Queries are Translated into the NoSQL API's Query Language

This is the most powerful feature. You get to write expressive, type-safe C# LINQ queries, and EF Core translates them into the Cosmos DB SQL-like query language for you.

Your C# LINQ Query (Program.cs):

var customerOrders = await context.Orders
    .Where(o => o.CustomerName == "John Doe" && o.TotalPrice > 100)
    .ToListAsync();

What EF Core translates this into (Cosmos DB SQL Query): Behind the scenes, EF Core generates a query string that it sends to the NoSQL API. The query looks very similar to standard SQL:

SELECT *
FROM c
WHERE c.CustomerName = 'John Doe' AND c.TotalPrice > 100

(Here, c is just an alias for the documents in the container, similar to how you'd alias a table in SQL).

The NoSQL API engine executes this query against the JSON documents in your container and returns the results, which EF Core then deserializes back into a List<Order>.

4. The id and Partition Key are Core Concepts of the NoSQL API

The NoSQL API model is built for massive scale, and two properties are critical for this:

  • id: A unique identifier for a document within a logical partition. The [JsonProperty("id")] attribute maps your Guid property to this required lowercase string field.
  • Partition Key: This is the most important property for performance and scalability. It determines how Cosmos DB physically distributes your data across different servers.

Your C# Code (OrderContext.cs):

// Defines which property in your document is the partition key
entity.HasPartitionKey(o => o.CustomerName);

By choosing CustomerName as the partition key, you are telling Cosmos DB to group all orders for the same customer together physically.

Why this matters for the API:

  • Efficient Reads: When you perform a "point read" using both the id and the partition key value, it's the fastest and cheapest operation possible. This is what FindAsync does.
    // This is a highly efficient "point read"
    var foundOrder = await context.Orders.FindAsync(createdOrderId, customerName);
  • Efficient Queries: Queries that include the partition key in the WHERE clause (like our LINQ example) are very efficient because Cosmos DB knows exactly which physical server(s) to search. These are called "in-partition" queries.
  • Cross-Partition Queries: If you were to query without the partition key (e.g., context.Orders.Where(o => o.TotalPrice > 500)), Cosmos DB would have to "fan out" the query to all physical partitions, making it slower and more expensive.

Summary Table

C# / EF Core Concept Cosmos DB NoSQL API Concept Explanation
class Order { ... } JSON Document Your C# object model defines the structure of the data.
DbSet<Order> Container A collection that holds all your Order JSON documents.
Where(o => ...) SQL-like Query Your LINQ query is translated into a SQL-like query string.
[JsonProperty("id")] id property The unique identifier for a document within a partition.
HasPartitionKey(...) Partition Key The property used to distribute data for scale and performance.

In short, the NoSQL API provides a document-oriented database model that you can interact with using a familiar SQL-like syntax. The Entity Framework Core provider acts as a brilliant abstraction layer, allowing you to work with this model using standard C# classes and LINQ queries.


⚠️ **GitHub.com Fallback** ⚠️