Entity Framework Core Database First Approach

Entity Framework Core Database First Approach

In this article, I will discuss the Entity Framework Core (EF Core) Database First Approach with Examples. Please read our previous article discussing Global Query Filters in Entity Framework Core with Examples.

Entity Framework Core Database First Approach

The Database First approach in Entity Framework Core (EF Core) is a methodology where the data access layer (comprising models and a DbContext class) is generated from an existing database. This strategy is particularly useful when integrating EF Core with a pre-existing database or when database schema design is managed by a database administrator or an external team. So, this approach is particularly useful in the following scenarios:

  • Working with an existing database: When the database already exists and is managed by database administrators or external teams.
  • Database-centric development: When the database schema design is finalized before the application development begins.

With the Database First approach, EF Core will generate the necessary models, relationships, and DbContext class based on the structure of the database, enabling developers to access the database and perform database CRUD operations. This is often used in scenarios where the database schema has already been defined, and you want to avoid manually creating models and contexts.

Entity Framework Core Database First Approach with an E-commerce Application

Let us understand how to use the Entity Framework Core (EF Core) Database First approach step by step with an existing E-commerce database. This example will cover:

  • Creating the Database Schema: Designing the tables and their relationships.
  • Scaffolding the DbContext: Generating the DbContext and model classes from the database.
  • Performing CRUD Operations: Creating, reading, updating, and deleting data.
  • Utilizing Views, Stored Procedures, and Functions: Enhancing data operations with SQL Server features.
Key Entities in the E-commerce Database

The E-commerce database will manage several key entities essential for an online shopping platform. The key tables and their purposes are:

  • Customers: Stores personal information of customers.
  • Addresses: Holds multiple addresses associated with each customer.
  • Categories: Organizes products into various classifications.
  • Products: Contains detailed information about each product.
  • Orders: Records customer orders.
  • OrderItems: Lists the products included in each order.
  • Payments: Tracks payment details for orders.
SQL Scripts for Database Creation

Let’s start by creating the EcommerceDB database and all necessary tables with proper relationships, views, stored procedures, and functions. So, please execute the following SQL script in SQL Server Management Studio (SSMS).

CREATE DATABASE EcommerceDB;
GO

USE EcommerceDB;
GO

-- Create Customers Table
-- Holds customer details like name, email, phone, and date of birth.
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY IDENTITY,
    FirstName NVARCHAR(50) NOT NULL,
    LastName NVARCHAR(50) NOT NULL,
    Email NVARCHAR(100) NOT NULL UNIQUE,
    Phone NVARCHAR(20),
    DateOfBirth DATE,
    CreatedDate DATETIME DEFAULT GETDATE()
);
GO

-- Create Addresses Table
-- Stores multiple addresses per customer, including whether an address is the default.
CREATE TABLE Addresses (
    AddressID INT PRIMARY KEY IDENTITY,
    CustomerID INT NOT NULL,
    AddressLine1 NVARCHAR(100) NOT NULL,
    AddressLine2 NVARCHAR(100),
    City NVARCHAR(50) NOT NULL,
    State NVARCHAR(50) NOT NULL,
    PostalCode NVARCHAR(20) NOT NULL,
    Country NVARCHAR(50) NOT NULL,
    IsDefault BIT DEFAULT 0,
    CreatedDate DATETIME DEFAULT GETDATE(),
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);
GO

-- Create Categories Table
-- Organizes products into categories, supporting hierarchical relationships with ParentCategoryID.
CREATE TABLE Categories (
    CategoryID INT PRIMARY KEY IDENTITY,
    CategoryName NVARCHAR(100) NOT NULL,
    Description NVARCHAR(500),
    ParentCategoryID INT NULL,
    CreatedDate DATETIME DEFAULT GETDATE(),
    FOREIGN KEY (ParentCategoryID) REFERENCES Categories(CategoryID)
);
GO

-- Create Products Table
-- Contains product information, pricing, stock levels, and category association.
CREATE TABLE Products (
    ProductID INT PRIMARY KEY IDENTITY,
    ProductName NVARCHAR(100) NOT NULL,
    Description NVARCHAR(500),
    Price DECIMAL(18, 2) NOT NULL,
    CategoryID INT NOT NULL,
    StockQuantity INT NOT NULL,
    CreatedDate DATETIME DEFAULT GETDATE(),
    FOREIGN KEY (CategoryID) REFERENCES Categories(CategoryID)
);
GO

-- Create Orders Table
-- Records each order's details, including the customer, shipping address, total amount, and status.
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY IDENTITY,
    CustomerID INT NOT NULL,
    OrderDate DATETIME DEFAULT GETDATE(),
    ShippingAddressID INT NOT NULL,
    TotalAmount DECIMAL(18,2),
    Status NVARCHAR(50),
    CreatedDate DATETIME DEFAULT GETDATE(),
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID),
    FOREIGN KEY (ShippingAddressID) REFERENCES Addresses(AddressID)
);
GO

-- Create OrderItems Table
-- Lists individual products within an order, their quantities, and pricing.
CREATE TABLE OrderItems (
    OrderItemID INT PRIMARY KEY IDENTITY,
    OrderID INT NOT NULL,
    ProductID INT NOT NULL,
    Quantity INT NOT NULL,
    UnitPrice DECIMAL(18,2) NOT NULL,
    TotalPrice DECIMAL(18,2) NOT NULL,
    CreatedDate DATETIME DEFAULT GETDATE(),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID),
    FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
);
GO

-- Create Payments Table
-- Tracks payment information related to orders, such as payment method and transaction status.
CREATE TABLE Payments (
    PaymentID INT PRIMARY KEY IDENTITY,
    OrderID INT NOT NULL,
    PaymentDate DATETIME DEFAULT GETDATE(),
    Amount DECIMAL(18,2) NOT NULL,
    PaymentMethod NVARCHAR(50) NOT NULL,
    TransactionID NVARCHAR(100),
    Status NVARCHAR(50),
    CreatedDate DATETIME DEFAULT GETDATE(),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID)
);
GO

-- View to Display Order Details
-- This view provides detailed information about orders, including customer details, order items, and shipping addresses.
CREATE VIEW OrderDetailsView AS
SELECT 
    o.OrderID,
    o.OrderDate,
    o.TotalAmount,
    o.Status AS OrderStatus,
    c.FirstName + ' ' + c.LastName AS CustomerName,
    ca.AddressLine1 + ', ' + ca.City + ', ' + ca.State + ', ' + ca.Country AS ShippingAddress,
    p.ProductName,
    oi.Quantity,
    oi.UnitPrice,
    oi.TotalPrice
FROM Orders o
INNER JOIN Customers c ON o.CustomerID = c.CustomerID
INNER JOIN Addresses ca ON o.ShippingAddressID = ca.AddressID
INNER JOIN OrderItems oi ON o.OrderID = oi.OrderID
INNER JOIN Products p ON oi.ProductID = p.ProductID;
GO

-- Stored Procedure to Get Orders by CustomerID
-- This stored procedure retrieves all orders placed by a specific customer.
CREATE OR ALTER PROCEDURE [dbo].[GetOrdersByCustomerID]
    @CustomerID INT
AS
BEGIN
    SELECT 
        o.OrderID,
        o.OrderDate,
        o.TotalAmount,
        o.Status,
        o.CreatedDate,
        o.CustomerID,
        o.ShippingAddressId
    FROM Orders o
    INNER JOIN Customers c ON o.CustomerID = c.CustomerID
    WHERE o.CustomerID = @CustomerID;
END;
GO

-- Function to Calculate Discount
-- This function calculates discounts based on the total amount of an order.
CREATE FUNCTION CalculateDiscount 
(
    @TotalAmount DECIMAL(18,2)
)
RETURNS DECIMAL(18,2)
AS
BEGIN
    DECLARE @Discount DECIMAL(18,2);
    IF @TotalAmount > 100
        SET @Discount = @TotalAmount * 0.10;
    ELSE
        SET @Discount = 0;
    RETURN @Discount;
END;
GO

Once you execute the above SQL Script, the database with the required database objects is created, as shown in the below image:

Entity Framework Core Database First Approach

Creating a New Console Application:

We can use the EF Core Database First Approach with any type of Dot Net Core Application, including ASP.NET Core MVC, ASP.NET Core Web API, Console Application, etc. So, let us create a new Console Application named ECommerceApp.

Once you create the Console Application, we need to add the Microsoft.EntityFrameworkCore.SqlServer (EF Core provider for SQL Server) and Microsoft.EntityFrameworkCore.Tools (Tools for EF Core, including the Scaffold-DbContext command) packages. Please execute the following commands in the Package Manager Console to install these two packages.

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.Tools
Implementing EF Core Database First Approach

Now, we will see how to create the Context and Entity classes from our existing EcommerceDB database using the Entity Framework Core Database First Approach. Creating Context and Entity classes for an existing database is called Database-First Approach.

Entity Framework Core does not support Visual Designer for DB model and wizard to create entity and context classes similar to Entity Framework 6. So, we need to use the Scaffold-DbContext command, which is also called Reverse Engineering.

The Scaffold-DbContext command creates entities and context classes based on the schema of the existing database. We need to do this using Package Manager Console (PMC) tools.

Scaffolding the Database in EF Core

Scaffolding is the process of generating the DbContext and entity classes based on the existing database schema. Let us first understand the syntax of the Scaffold-DbContext

Syntax: Scaffold-DbContext “Connection String” Microsoft.EntityFrameworkCore.SqlServer -o Models -f

Parameters:
  • Connection String: Specifies how to connect to the database.
  • Microsoft.EntityFrameworkCore.SqlServer: Specifies the EF Core provider.
  • -o Models: Output directory for the generated classes.
  • -f: Forces scaffolding by overwriting existing files.
Example: Connecting to SQL Server EcommerceDB database

Scaffold-Dbcontext “Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV; Database=EcommerceDB; Trusted_Connection=True; TrustServerCertificate=True;” Microsoft.EntityFrameworkCore.SqlServer -O Models -f

So, open the Package Manager Console and execute the following command to create the entities and context class based on the EcommerceDB database:

Scaffolding the Database in EF Core

Once you execute the above command, it should create the Models folder. It should have created the DbContext class and the required entities inside that folder, as shown in the image below.

Implementing EF Core Database First Approach

Here, you can see that entities are created for all database tables and views, and the EcommerceDbContext class manages the database operations using these entities.

Performing CRUD Operations using EF Core DB First Approach

Now, let us Proceed and see how we can perform database CRUD Operations using the Entity Framework Core Database First Approach. Later, I will show how to use Views, Stored Procedures, and Stored Functions.

Adding Data

Let us see how to add new categories, products, customers, addresses, orders, order items, and payments. To better understand, please modify the Program class as follows. The following example code is self-explained, so please read the comment lines.

using ECommerceApp.Models;
using Microsoft.EntityFrameworkCore;

namespace ECommerceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using var context = new EcommerceDbContext();
            var dbTransction = context.Database.BeginTransaction();

            try
            {
                // *** Create Operations ***

                // Adding Categories
                Console.WriteLine("Adding new categories...");

                // Create a new 'Electronics' category
                var electronicsCategory = new Category
                {
                    CategoryName = "Electronics",
                    Description = "Electronic devices and gadgets",
                    CreatedDate = DateTime.Now
                };
                context.Categories.Add(electronicsCategory);

                // Create a new 'Clothing' category
                var clothingCategory = new Category
                {
                    CategoryName = "Clothing",
                    Description = "Men's and Women's Clothing",
                    CreatedDate = DateTime.Now
                };
                context.Categories.Add(clothingCategory);

                // Save categories to the database
                context.SaveChanges();
                Console.WriteLine("Categories 'Electronics' and 'Clothing' added successfully.\n");

                // Adding Products
                Console.WriteLine("Adding new products...");

                // Create a new product 'Smartphone' under 'Electronics' category
                var product1 = new Product
                {
                    ProductName = "Smartphone",
                    Description = "Latest model smartphone",
                    Price = 699.99M,
                    StockQuantity = 50,
                    CategoryId = electronicsCategory.CategoryId,
                    CreatedDate = DateTime.Now
                };
                context.Products.Add(product1);

                // Create a new product 'Jeans' under 'Clothing' category
                var product2 = new Product
                {
                    ProductName = "Jeans",
                    Description = "Blue denim jeans",
                    Price = 49.99M,
                    StockQuantity = 100,
                    CategoryId = clothingCategory.CategoryId,
                    CreatedDate = DateTime.Now
                };
                context.Products.Add(product2);

                // Save products to the database
                context.SaveChanges();
                Console.WriteLine("Products 'Smartphone' and 'Jeans' added successfully.\n");

                // Adding a Customer
                Console.WriteLine("Adding a new customer...");

                // Create a new customer 'John Doe'
                var customer = new Customer
                {
                    FirstName = "John",
                    LastName = "Doe",
                    Email = "john.doe@example.com",
                    Phone = "1234567890",
                    DateOfBirth = new DateOnly(1990, 1, 1),
                    CreatedDate = DateTime.Now
                };
                context.Customers.Add(customer);

                // Save customer to the database
                context.SaveChanges();
                Console.WriteLine($"Customer '{customer.FirstName} {customer.LastName}' added successfully with CustomerID: {customer.CustomerId}\n");

                // Adding Customer Address
                Console.WriteLine("Adding address for the customer...");

                // Create a new address for the customer
                var address1 = new Address
                {
                    CustomerId = customer.CustomerId,
                    AddressLine1 = "123 Main Street",
                    AddressLine2 = null,
                    City = "Anytown",
                    State = "Anystate",
                    PostalCode = "12345",
                    Country = "USA",
                    IsDefault = false,
                    CreatedDate = DateTime.Now
                };
                context.Addresses.Add(address1);

                var address2 = new Address
                {
                    CustomerId = customer.CustomerId,
                    AddressLine1 = "123 Main Street",
                    AddressLine2 = null,
                    City = "Anytown",
                    State = "Anystate",
                    PostalCode = "12345",
                    Country = "USA",
                    IsDefault = true,
                    CreatedDate = DateTime.Now
                };
                context.Addresses.Add(address2);

                // Save address to the database
                context.SaveChanges();
                Console.WriteLine("Customer addresses added successfully.\n");

                // Creating an Order
                Console.WriteLine("Creating a new order for the customer...");

                // Create a new order for the customer with the shipping address
                var order = new Order
                {
                    CustomerId = customer.CustomerId,
                    ShippingAddressId = address2.AddressId,
                    TotalAmount = product1.Price, // We are setting Total amount is the price of the product. But we need to calculate the Total Amount
                    Status = "Pending",
                    CreatedDate = DateTime.Now
                };
                context.Orders.Add(order);

                // Save order to the database
                context.SaveChanges();
                Console.WriteLine($"Order created successfully with OrderID: {order.OrderId}\n");

                // Adding Order Item
                Console.WriteLine("Adding order item to the order...");

                // Create a new order item for the order
                var orderItem = new OrderItem
                {
                    OrderId = order.OrderId,
                    ProductId = product1.ProductId,
                    Quantity = 1,
                    UnitPrice = product1.Price,
                    TotalPrice = product1.Price * 1,
                    CreatedDate = DateTime.Now
                };
                orderItem.TotalPrice = orderItem.UnitPrice * orderItem.Quantity;

                context.OrderItems.Add(orderItem);

                // Save order item to the database
                context.SaveChanges();
                Console.WriteLine("Order item added successfully.\n");

                // Processing Payment
                Console.WriteLine("Processing payment for the order...");

                // Create a new payment for the order
                var payment = new Payment
                {
                    OrderId = order.OrderId,
                    Amount = Convert.ToDecimal(order.TotalAmount),
                    PaymentMethod = "Credit Card",
                    TransactionId = "TXN123456",
                    Status = "Pending",
                    CreatedDate = DateTime.Now
                };
                context.Payments.Add(payment);

                // Save payment to the database
                context.SaveChanges();

                dbTransction.Commit();
                Console.WriteLine("Payment processed successfully.\n");
            }
            catch (DbUpdateException ex)
            {
                dbTransction.Rollback();
                Console.WriteLine($"Database Error: {ex.InnerException?.Message ?? ex.Message}");
            }
            catch (Exception ex)
            {
                dbTransction.Rollback();
                // Display any errors that occur during the operations
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}
Output:

Performing CRUD Operations using EF Core DB First Approach

Reading Data:

Let us retrieve and display orders with the customer, order item, and payment details. To better understand, please modify the Program class as follows. The following example code is self-explained, so please read the comment lines.

using ECommerceApp.Models;
using Microsoft.EntityFrameworkCore;

namespace EcommerceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                // Create an instance of the DbContext to interact with the database
                using var context = new EcommerceDbContext();

                // *** Retrieve and Display Orders with Details ***

                Console.WriteLine("Fetching and displaying all orders with customer, order items, and payment details...\n");

                // Fetch orders including related data:
                var orders = context.Orders
                    .Include(o => o.Customer) // Include customer information
                    .Include(o => o.OrderItems) // Include order items
                        .ThenInclude(oi => oi.Product) // Include product details for each order item
                    .Include(o => o.Payments) // Include payments associated with the order
                    .Include(o => o.ShippingAddress) // Include shipping address
                    .ToList();

                // Check if any orders exist
                if (orders.Any())
                {
                    // Iterate through each order
                    foreach (var order in orders)
                    {
                        // Display basic order information
                        Console.WriteLine($"Order ID: {order.OrderId}, Date: {order.OrderDate}, Status: {order.Status}, Total Amount: {order.TotalAmount}");
                        
                        // Display customer information
                        Console.WriteLine($"Customer: {order.Customer.FirstName} {order.Customer.LastName}");
                        Console.WriteLine($"Email: {order.Customer.Email}");
                        Console.WriteLine($"Phone: {order.Customer.Phone}");

                        // Display shipping address
                        var address = order.ShippingAddress;
                        Console.WriteLine("\nShipping Address:");
                        Console.WriteLine($"\t{address.AddressLine1}");
                        if (!string.IsNullOrEmpty(address.AddressLine2))
                        {
                            Console.WriteLine($"\t{address.AddressLine2}");
                        }
                        Console.WriteLine($"\t{address.City}, {address.State}, {address.PostalCode}, {address.Country}");

                        // Display order items
                        Console.WriteLine("\nOrder Items:");
                        foreach (var item in order.OrderItems)
                        {
                            Console.WriteLine($"\tProduct: {item.Product.ProductName}, Quantity: {item.Quantity}, Unit Price: {item.UnitPrice}, Total Price: {item.TotalPrice}");
                            Console.WriteLine(); // Blank line for readability
                        }

                        // Display payment details
                        Console.WriteLine("Payments:");
                        foreach (var payment in order.Payments)
                        {
                            Console.WriteLine($"\tPayment ID: {payment.PaymentId}, Amount: {payment.Amount}, Payment Method: {payment.PaymentMethod}");
                            Console.WriteLine($"\tPayment Date: {payment.PaymentDate}, Transaction ID: {payment.TransactionId}, Payment Status: {payment.Status}");
                            Console.WriteLine(); // Blank line for readability
                        }

                        Console.WriteLine(); // Separator for readability
                    }
                }
                else
                {
                    Console.WriteLine("No orders found in the database.");
                }
            }
            catch (DbUpdateException ex)
            {
                Console.WriteLine($"Database Error: {ex.InnerException?.Message ?? ex.Message}");
            }
            catch (Exception ex)
            {
                // Display any errors that occur during the operations
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}
Output:

Performing CRUD Operations using Entity Framework Core DB First Approach

Updating Data

Let us update the status of an order and payment and adjust the stock quantities of the products in the order. To better understand, please modify the Program class as follows. The following example code is self-explained, so please read the comment lines.

using ECommerceApp.Models;
using Microsoft.EntityFrameworkCore;

namespace EcommerceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using var context = new EcommerceDbContext();
            var dbTransaction = context.Database.BeginTransaction();

            try
            {
                // *** Update Order Status, Payment Status, and Adjust Stock Quantities ***

                // Specify the Order ID that you want to update
                int orderIdToUpdate = 1; // Change this to the actual Order ID

                Console.WriteLine($"Updating Order ID: {orderIdToUpdate}\n");

                // Retrieve the order including related entities:
                // - Include Order Items and their associated Products
                // - Include Payments associated with the order
                var order = context.Orders
                    .Include(o => o.OrderItems)
                        .ThenInclude(oi => oi.Product)
                    .Include(o => o.Payments)
                    .FirstOrDefault(o => o.OrderId == orderIdToUpdate);

                // Check if the order exists
                if (order != null)
                {
                    // Update payment status if payment exists
                    //var payment = order.Payments.FirstOrDefault();

                    foreach (var payment in order.Payments)
                    {
                        // Display current payment status
                        Console.WriteLine($"Current Payment Status: {payment.Status}");

                        // Update the payment status to 'Completed'
                        payment.Status = "Completed";

                        // Display updated payment status
                        Console.WriteLine($"Updated Payment Status: {payment.Status}\n");
                    }

                    //Updating Order Status
                    // Display current order status
                    Console.WriteLine($"Current Order Status: {order.Status}");

                    // Update the order status to 'Processing'
                    order.Status = "Processing";

                    // Display updated order status
                    Console.WriteLine($"Updated Order Status: {order.Status}\n");

                    // Adjust stock quantities for each product in the order
                    Console.WriteLine("Adjusting stock quantities for ordered products...");
                    foreach (var orderItem in order.OrderItems)
                    {
                        var product = orderItem.Product;

                        // Display current stock quantity
                        Console.WriteLine($"Product: {product.ProductName}, Current Stock Quantity: {product.StockQuantity}");

                        // Decrease the stock quantity by the quantity ordered
                        product.StockQuantity = product.StockQuantity - orderItem.Quantity;

                        // Ensure stock quantity does not go negative
                        if (product.StockQuantity < 0)
                        {
                            product.StockQuantity = 0;
                        }

                        // Display updated stock quantity
                        Console.WriteLine($"Updated Stock Quantity: {product.StockQuantity}\n");
                    }

                    // Save all changes to the database
                    context.SaveChanges();

                    dbTransaction.Commit();
                    Console.WriteLine("Order status, payment status, and stock quantities updated successfully.\n");
                }
                else
                {
                    Console.WriteLine($"Order with ID {orderIdToUpdate} not found.\n");
                }

                // Display the updated order and product details for confirmation
                Console.WriteLine("Fetching updated order and product details...\n");

                // Retrieve the updated order
                var updatedOrder = context.Orders
                    .Include(o => o.OrderItems)
                        .ThenInclude(oi => oi.Product)
                    .Include(o => o.Payments)
                    .FirstOrDefault(o => o.OrderId == orderIdToUpdate);

                if (updatedOrder != null)
                {
                    // Display order status
                    Console.WriteLine($"Order ID: {updatedOrder.OrderId}, Order Status: {updatedOrder.Status}");

                    // Display payment status
                    var updatedPayment = updatedOrder.Payments.FirstOrDefault();
                    if (updatedPayment != null)
                    {
                        Console.WriteLine($"Payment Status: {updatedPayment.Status}");
                    }

                    // Display updated stock quantities of products
                    Console.WriteLine("\nUpdated Product Stock Quantities:");
                    foreach (var orderItem in updatedOrder.OrderItems)
                    {
                        var product = orderItem.Product;
                        Console.WriteLine($"Product: {product.ProductName}, Stock Quantity: {product.StockQuantity}");
                    }
                }
                else
                {
                    Console.WriteLine("Order not found after update.\n");
                }
            }
            catch (DbUpdateException ex)
            {
                dbTransaction.Rollback();
                Console.WriteLine($"Database Error: {ex.InnerException?.Message ?? ex.Message}");
            }
            catch (Exception ex)
            {
                dbTransaction.Rollback();
                // Display any errors that occur during the operations
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}
Output:

CRUD Operations using EF Core Database First Approach

Deleting Data

Let us delete an order and the associated Order Items and payments. To better understand, please modify the Program class as follows. The following example code is self-explained, so please read the comment lines.

using ECommerceApp.Models;
using Microsoft.EntityFrameworkCore;

namespace EcommerceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                using var context = new EcommerceDbContext();

                // *** Delete an Order and Associated Order Items and Payments ***

                // Specify the Order ID that you want to delete
                int orderIdToDelete = 1; // Change this to the actual Order ID you want to delete

                Console.WriteLine($"Attempting to delete Order ID: {orderIdToDelete}\n");

                // Retrieve the order including related entities:
                // - Include Order Items
                // - Include Payments
                var order = context.Orders
                    .Include(o => o.OrderItems)
                    .Include(o => o.Payments)
                    .FirstOrDefault(o => o.OrderId == orderIdToDelete);

                // Check if the order exists
                if (order != null)
                {
                    // Display order details
                    Console.WriteLine("Order Details:");
                    Console.WriteLine($"Order ID: {order.OrderId}, Date: {order.OrderDate}, Status: {order.Status},Total Amount: {order.TotalAmount}\n");

                    // Display associated order items
                    Console.WriteLine("Associated Order Items:");
                    foreach (var orderItem in order.OrderItems)
                    {
                        Console.WriteLine($"\tOrder Item ID: {orderItem.OrderItemId}, Product ID: {orderItem.ProductId}, Quantity: {orderItem.Quantity}, Total Price: {orderItem.TotalPrice}");
                    }

                    // Display associated payments
                    Console.WriteLine("\nAssociated Payments:");
                    foreach (var payment in order.Payments)
                    {
                        Console.WriteLine($"\tPayment ID: {payment.PaymentId}, Amount: {payment.Amount}, Status: {payment.Status}");
                    }

                    Console.WriteLine("\nProceeding to delete the order and its associated order items and payments...\n");

                    // Remove order items first due to foreign key constraints
                    if (order.OrderItems.Any())
                    {
                        context.OrderItems.RemoveRange(order.OrderItems);
                        Console.WriteLine("Order items deleted successfully.");
                    }

                    // Remove payments associated with the order
                    if (order.Payments.Any())
                    {
                        context.Payments.RemoveRange(order.Payments);
                        Console.WriteLine("Payments deleted successfully.");
                    }

                    // Remove the order
                    context.Orders.Remove(order);
                    Console.WriteLine("Order deleted successfully.");

                    // Save all changes to the database
                    context.SaveChanges();
                    Console.WriteLine("\nAll changes have been saved to the database.");

                    // Confirm Deletion
                    Console.WriteLine("\nVerifying that the order and associated records have been deleted...");

                    // Attempt to retrieve the order again
                    var deletedOrder = context.Orders
                        .Include(o => o.OrderItems)
                        .Include(o => o.Payments)
                        .FirstOrDefault(o => o.OrderId == orderIdToDelete);

                    if (deletedOrder == null)
                    {
                        Console.WriteLine("Order deletion confirmed. The order and its associated order items and payments have been successfully deleted.");
                    }
                    else
                    {
                        Console.WriteLine("Order deletion failed. The order still exists in the database.");
                    }
                }
                else
                {
                    Console.WriteLine($"Order with ID {orderIdToDelete} not found. No deletion performed.");
                }
            }
            catch (DbUpdateException ex)
            {
                Console.WriteLine($"Database Error: {ex.InnerException?.Message ?? ex.Message}");
            }
            catch (Exception ex)
            {
                // Display any errors that occur during the operations
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}
Output:

CRUD Operations using Entity Framework Core Database First Approach

Stored Function using EF Core:

Even though we are using a Database-First approach, we need to manually define the function in our DbContext class, as EF Core does not automatically import functions through the scaffolding process. So, add the following method in EcommerceDbContext that represents the scalar function. Use the DbFunction attribute to link it to the SQL function.

// Define the stored function
[DbFunction("CalculateDiscount", "dbo")]
public static decimal? CalculateDiscount(decimal? totalAmount)
{
    // This method is for EF Core to know how to call the function.
    throw new NotImplementedException();
}

Once you have defined the function in the EcommerceDbContext class, you can call it like any other static method in your code.

Using Views, Stored Procedures, and Functions in EF Core DB First Approach:

Please modify the Program class as follows: Here, I am showing how to use Stored procedures and views with Entity Framework Core Database First Approach using the FromSqlRaw method. At the same time, we can call the CalculateDiscount method, which will execute the CalculateDiscount function. The following code is self-explained, so please read the comment lines for a better understanding.

using Microsoft.EntityFrameworkCore;
using Microsoft.Data.SqlClient;
using ECommerceApp.Models;

namespace EcommerceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Create an instance of the DbContext
            using var context = new EcommerceDbContext();

            var dbTransaction = context.Database.BeginTransaction();

            try
            {
                // *** Step 1: Create a New Order with Discount Applied ***

                Console.WriteLine("Creating a new order with discount applied...\n");

                // Define the existing CustomerID and ProductID
                int existingCustomerId = 1; // Replace with actual CustomerID
                int existingProductId = 1;   // Replace with actual ProductID

                // Retrieve the existing customer and product from the database
                var customer = context.Customers.FirstOrDefault(c => c.CustomerId == existingCustomerId);
                var product1 = context.Products.FirstOrDefault(p => p.ProductId == existingProductId);
                var product2 = context.Products.FirstOrDefault(p => p.ProductId == 2);

                if (customer == null || product1 == null || product2 == null)
                {
                    Console.WriteLine("Customer or Product not found. Cannot proceed with order creation.");
                    return;
                }

                // Retrieve the customer's default shipping address
                var shippingAddress = context.Addresses
                    .FirstOrDefault(a => a.CustomerId == customer.CustomerId && a.IsDefault == true);

                if (shippingAddress == null)
                {
                    Console.WriteLine("Shipping address not found for the customer.");
                    return;
                }

                // Create a new order for the customer
                var newOrder = new Order
                {
                    CustomerId = customer.CustomerId,
                    ShippingAddressId = shippingAddress.AddressId,
                    Status = "Pending",
                    CreatedDate = DateTime.Now
                };
                context.Orders.Add(newOrder);

                try
                {
                    context.SaveChanges(); // Save to get the OrderID

                    Console.WriteLine($"Order created successfully with OrderID: {newOrder.OrderId}\n");

                    // Add an order item to the new order
                    int quantityOrdered = 2; // Assuming the customer orders 2 units

                    List<OrderItem> orderItems = new List<OrderItem>()
                    {
                        new OrderItem
                        {
                            OrderId = newOrder.OrderId,
                            ProductId = product1.ProductId,
                            Quantity = quantityOrdered,
                            UnitPrice = product1.Price,
                            TotalPrice = product1.Price * quantityOrdered,
                            CreatedDate = DateTime.Now
                        },
                        new OrderItem
                        {
                            OrderId = newOrder.OrderId,
                            ProductId = product2.ProductId,
                            Quantity = 1,
                            UnitPrice = product2.Price,
                            TotalPrice = product2.Price * 1,
                            CreatedDate = DateTime.Now
                        }
                    };

                    context.OrderItems.AddRange(orderItems);

                    // Update the order's total amount before discount
                    decimal totalAmountBeforeDiscount = orderItems.Sum(ord => ord.TotalPrice);

                    // Apply the discount using the stored function 'CalculateDiscount'
                    decimal? discount = context.Orders
                        .Where(o => o.OrderId == newOrder.OrderId)
                        .Select(o => EcommerceDbContext.CalculateDiscount(totalAmountBeforeDiscount))
                        .FirstOrDefault();

                    // Ensure discount is not null
                    discount ??= 0;

                    // Calculate the total amount after applying the discount
                    decimal totalAmountAfterDiscount = totalAmountBeforeDiscount - discount.Value;

                    // Update the order's total amount
                    newOrder.TotalAmount = totalAmountAfterDiscount;

                    // Save changes to persist updates
                    context.SaveChanges();
                    Console.WriteLine($"Order item added, discount applied, and order total amount updated.\n");

                    // Display discount details
                    Console.WriteLine($"Amount Before Discount: {totalAmountBeforeDiscount}, Discount Applied: {discount.Value}, Amount After Discount: {totalAmountAfterDiscount}\n");

                    // Process payment for the order
                    var payment = new Payment
                    {
                        OrderId = newOrder.OrderId,
                        Amount = Convert.ToDecimal(newOrder.TotalAmount), // Amount after discount
                        PaymentMethod = "Credit Card",
                        TransactionId = "TXN" + DateTime.Now.Ticks,
                        Status = "Processing",
                        Discount = discount,
                        CreatedDate = DateTime.Now
                    };
                    context.Payments.Add(payment);
                    context.SaveChanges();
                    Console.WriteLine("Payment processed for the new order with discounted amount.\n");

                    dbTransaction.Commit();
                    // *** Step 2: Use the View 'OrderDetailsView' ***
                }
                catch (Exception)
                {
                    dbTransaction.Rollback();
                }
                
                Console.WriteLine("Fetching order details using the 'OrderDetailsView'...\n");

                // Retrieve order details for the new order
                var orderDetails = context.OrderDetailsViews
                    .Where(od => od.OrderId == newOrder.OrderId)
                    .ToList();

                if (orderDetails.Any())
                {
                    foreach (var detail in orderDetails)
                    {
                        Console.WriteLine($"Order ID: {detail.OrderId}, Date: {detail.OrderDate}, Status: {detail.OrderStatus}");
                        Console.WriteLine($"Customer Name: {detail.CustomerName}, Shipping Address: {detail.ShippingAddress}");
                        Console.WriteLine($"Product Name: {detail.ProductName}, Quantity: {detail.Quantity}, Unit Price: {detail.UnitPrice}, Total Price: {detail.TotalPrice} \n");
                    }
                }
                else
                {
                    Console.WriteLine("No order details found for the new order.");
                }

                // *** Step 3: Use the Stored Procedure 'GetOrdersByCustomerID' ***

                Console.WriteLine("\nFetching orders for the customer using the stored procedure 'GetOrdersByCustomerID'...\n");

                // Define the parameter
                var customerIdParam = new SqlParameter("@CustomerID", customer.CustomerId);

                // Execute the stored procedure
                var orders = context.Orders
                    .FromSqlRaw("EXEC GetOrdersByCustomerID @CustomerID", customerIdParam)
                    .ToList();

                if (orders.Any())
                {
                    foreach (var order in orders)
                    {
                        Console.WriteLine($"Order ID: {order.OrderId}, Date: {order.OrderDate}, Status: {order.Status}, Total Amount: {order.TotalAmount}");
                    }
                }
                else
                {
                    Console.WriteLine("No orders found for the customer.");
                }
            }
            catch (DbUpdateException ex)
            {
                Console.WriteLine($"Database Error: {ex.InnerException?.Message ?? ex.Message}");
            }
            catch (Exception ex)
            {
                // Display any errors that occur during the operations
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}

We have successfully implemented how to use the EF Core Database First approach with an E-commerce database. We generated the required models by reverse-engineering the existing database and performing CRUD operations. Additionally, we use database views, stored procedures, and functions within our application.

Note: If the database schema changes, the models and DbContext will need to be updated using the scaffold command with the -Force option to regenerate the models, which will overwrite any customizations.

In this article, I explain the CRUD Operations in ASP.NET Core MVC using Entity Framework Core with Examples. I hope you enjoy this article on the Entity Framework Core Database First Approach.

1 thought on “Entity Framework Core Database First Approach”

Leave a Reply

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