SportStore with database

Objectives

  • Learn how to use entities
  • Learn how to define a persistence layer
  • Learn how to use Entity Framework
  • Learn how to work with migrations
  • Learn how to work with seeds

Goal

In this exercise we are going to add a database to our e-commerce website.

Changes since chapter 8

This repository contains the solution code from the previous chapter. The classes OrderItem and CartItem were removed because the project was a little over engineered. These classes only existed for easier comparison of these lines but made the database configuration more difficult.

The Order class also got an extra property for the Customer.

Exercise

  1. Add the following connection string to the appsettings.Development.json of the Server project
"ConnectionStrings": {
  "SportStore": "Server=(localdb)\\mssqllocaldb;Database=SportStore;Trusted_Connection=True;"
}
  1. Create a new Class Library in the src folder called Persistence and add it to the solution, remove the dummy Class1.cs
  2. Add the following dependencies to the Persistence project:
    • Microsoft.EntityFrameworkCore.Design
    • Microsoft.EntityFrameworkCore.SqlServer
  3. Add the following dependencies to the Server project:
    • Microsoft.EntityFrameworkCore.SqlServer
    • Microsoft.EntityFrameworkCore.Tools
  4. Add a reference from the Persistence to the Domain project.
  5. Add a reference from the Services to the Persistence project.
    • Since the Server project uses the Services project, we get a child dependency on the Persistence project
  6. Create a DbContext named SportStoreDbContext in a Data folder in the Persistence project
    • This class only contains a constructor which calls the base class' constructor
  7. Initialize a connection in the StartUp of the Server
  8. Add a private default constructor for every class in the Domain. Note not every domain class should be persisted, how do you know?
  9. Read through this documentation: Persist value objects as owned entity types and make every ValueObject an owned entity type.
    • Create seperate IEntityTypeConfiguration subclasses per class that needs modification, see the documentation for more info.
    • For readonly properties, you need to specify the property in the model builder or Entity Framework won't add it to the database. An alternative is to add a private setter but this is bad practice because it's not obvious why the setter is private and it's not clear at first sight which properties are stored in the database.
    • Set a precision of 12 and a scale of 10 for the Value of class Money
  10. Add a DbSet for each entity that can be fetched
  11. Create a migration name InitialCreate which contains all data to create the empty database
    • If the CLI tells you that the ef command does not exist, install the tool: dotnet tool install --global dotnet-ef --version 5.0.12
  12. Create a class named SportStoreDataInitializer and paste the following code:
public class SportStoreDataInitializer
{
    private readonly SportStoreDbContext _dbContext;

    public SportStoreDataInitializer(SportStoreDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void SeedData()
    {
        _dbContext.Database.EnsureDeleted();
        if (_dbContext.Database.EnsureCreated())
        {
            SeedProducts();
        }
    }

    private void SeedProducts()
    {
        var products = new ProductFaker()
            .RuleFor(p => p.Id, () => 0) // Remove the id, database column is auto generated
            .RuleFor(p => p.Category, new CategoryFaker().RuleFor(c => c.Id, 0))
            .Generate(100);
        _dbContext.Products.AddRange(products);
        _dbContext.SaveChanges();
    }
}
  1. Make this class a scoped service and inject an instance in the Configure method, finally call the Seed method
  2. Check if Entity Framework mapped every entity correctly. If not, create or update the IEntityTypeConfiguration for that specific entity, create a new migration, make sure the server executed the new migration and check again. Repeat this step until the database model is correct.
    • The only optional fields are Description (Product) and DeliveryDate (Order)
    • The Total in Order should not be persisted
  3. Implement a new ProductService which interacts with the database
  4. Use this new service in the Server (instead of the FakeProductService)
  5. Make sure everything works

Solution

A possible solution can be found here.