Skip to content
  • iImagine
  • Register
  • Log In

Web Development School

Learning made easy.

  • Books
    • Beginning Web Development with ASP.Net Core & Client-Side Technologies
      • TOC
      • Part 1
        • Chapter 1: Static HTML – Designing the landing page
      • Part 2
        • Chapter 2: ASP.Net Core – Let’s talk Dynamic
        • Chapter 3: Introduction to ASP.Net Core MVC
          [ASP.Net Core v9]
      • Part 4
        • Chapter 7: Using Server Side & Client Side technologies together
          [ASP.Net Core v7 & Angular 15]
  • Environment Setup
    • Installing Angular
    • Installing Visual Studio 2022
    • Installing SQL Server 2022 Express
    • Installing Postman
    • Installing Git for Windows
  • Blog
  • iImagine WebSolutions
  • Events
  • Learning Videos
  • Toggle search form

Seed the Database: Loading test data

Now that we have a database that reflects our Data Model, we need to fill it with some data.

In this module we are going to, “Seed”, the database with test data. And, configure the application to only seed the database if we are running the application in the development environment, and there are no existing records in the database. So let’s get started!

Table Of Contents
  1. Seed the Database
    • Run the program to initialize the database
      • Complete Seed Scenario testing
  2. The (One or Zero)-to-Many DB Relationship
  3. Code Review
    • Program.cs
      • DI (Dependency Injection) in ASP.Net Core
        • Problems DI solves
          • Tight Coupling
          • Testing
        • Service Lifetimes
      • The C# using keyword
        • Garbage Collection: managing memory in C#
        • Alternative C# Using Declaration
    • The SeedData class
      • static members in C#
      • Operators in C#
        • Assignment Operators
        • Arithmetic Operators
        • Comparison Operators
        • Logical Operators
          • Binary Logical Operators
          • Conditional Logical Operators
          • Short Circuiting
      • Truth tables
        • AND truth table
        • OR truth table
        • Unknown/Null (Uncertainty) truth table
        • AND Uncertainty truth table
        • OR Uncertainty truth table
      • Add the VehicleType Records
      • Add the Vehicle Records
  4. What's Next

Seed the Database

Create a class file named SeedData.cs in the FredsCars/Models folder and fill it with the contents below.

FredsCars/Models/SeedData.cs

using FredsCars.Data;

namespace FredsCars.Models
{
    public class SeedData
    {
        public static void Initialize(FredsCarsDbContext context)
        {
            // if data exists in both tables
            //   return - kickout.
            if (context.Vehicles.Any() && context.VehicleTypes.Any())
            {
                return;
            }

            // Add VehicleType records
            //   if no VehicleType data exists yet.
            if (!context.VehicleTypes.Any())
            {
                context.VehicleTypes.AddRange(
                    new VehicleType
                    {
                        Name = "Car"
                    },
                    new VehicleType
                    {
                        Name = "Truck"
                    },
                    new VehicleType
                    {
                        Name = "Jeep"
                    }
                );
                context.SaveChanges();
            }

            // Capture newly generated VehicleType Ids
            //   generated by SQL Server when the records
            //   are inserted.
            var carTypeId = context.VehicleTypes
                    .FirstOrDefault(vt => vt.Name == "Car")!.Id;
            var truckTypeId = context.VehicleTypes
                .FirstOrDefault(vt => vt.Name == "Truck")!.Id;
            var jeepTypeId = context.VehicleTypes
                .FirstOrDefault(vt => vt.Name == "Jeep")!.Id;

            // Add Vehicle records
            //   if no Vehicle data exists yet.
            if (!context.Vehicles.Any())
            {
                context.Vehicles.AddRange(
                    // Cars
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2021",
                        Make = "Dodge",
                        Model = "Challenger",
                        Color = "Frostbite",
                        Price = 64164,
                        VIN = "2C3CDZFJ8MH631199",
                        VehicleTypeId = carTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.Used,
                        Year = "2020",
                        Make = "Ford",
                        Model = "Escape",
                        Color = "Oxford White",
                        Price = 22999,
                        VIN = "1FMCU0F63LUC25826",
                        VehicleTypeId = carTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2021",
                        Make = "Dodge",
                        Model = "Durange",
                        Color = "Black",
                        Price = 50557,
                        VIN = "1C4RDJDG5MC837730",
                        VehicleTypeId = carTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2021",
                        Make = "Nissan",
                        Model = "Niro",
                        Color = "Blue",
                        Price = 24960,
                        VIN = "2XYZT67JTF24AZG856",
                        VehicleTypeId = carTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2021",
                        Make = "Kia",
                        Model = "Stinger",
                        Color = "Gray",
                        Price = 36090,
                        VIN = "6FG146B89624AZ7952",
                        VehicleTypeId = carTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2021",
                        Make = "Kia",
                        Model = "Stinger",
                        Color = "Gray",
                        Price = 36090,
                        VIN = "6FG146B89624AZ7952",
                        VehicleTypeId = carTypeId
                    },
                    // Trucks
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2022",
                        Make = "Ram",
                        Model = "Crew Cab",
                        Color = "Black",
                        Price = 68400,
                        VIN = "3C6UR5DL8NG157035",
                        VehicleTypeId = truckTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.Used,
                        Year = "2017",
                        Make = "Ram",
                        Model = "Crew Cab",
                        Color = "Red",
                        Price = 33000,
                        VIN = "1C6RR7PT0HS814596",
                        VehicleTypeId = truckTypeId
                    },
                    // Jeeps
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2022",
                        Make = "Jeep",
                        Model = "Compass",
                        Color = "White",
                        Price = 34980,
                        VIN = "3C4NJDFB5NT114024",
                        VehicleTypeId = jeepTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2022",
                        Make = "Jeep",
                        Model = "Compass",
                        Color = "Red",
                        Price = 39275,
                        VIN = "3C4NJDCB1NT118172",
                        VehicleTypeId = jeepTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2022",
                        Make = "Jeep",
                        Model = "Grand Cherokee",
                        Color = "Pearlcoat",
                        Price = 53575,
                        VIN = "1C4RJKBG5M8201121",
                        VehicleTypeId = jeepTypeId
                    },
                    new Vehicle
                    {
                        Status = Status.New,
                        Year = "2021",
                        Make = "Jeep",
                        Model = "Wrangler Sport S",
                        Color = "Green",
                        Price = 40940,
                        VIN = "1C4GJXAN0MW856433",
                        VehicleTypeId = jeepTypeId
                    }
                );
                context.SaveChanges();
            }
        }
    }
}

The code above checks if both tables have data and if so, kicks out, or returns to the calling code in Program.cs (skipping the seeding process).

Otherwise, it checks if there are any records in the VehicleType table, and if not fills the table with data, and captures the VehicleTypeIds to use for Vehicle foreign key data. Finally, it checks if there are records in the Vehicle table, and if not fills the table with data. We will look at this code more closely once we get everything running.

Next, modify Program.cs with the code below.

FredsCars\Program.cs

... existing code ...
var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseStaticFiles();

/*** Add endpoints for contoller actions and
       the default route ***/
app.MapDefaultControllerRoute();

/*** Seed the database ***/
if (builder.Environment.IsDevelopment())
{
    using (var scope = app.Services.CreateScope())
    {
        var context = scope.ServiceProvider
            .GetRequiredService<FredsCarsDbContext>();
        SeedData.Initialize(context);
    }
}

app.Run();

The code above calls the Seeding process in Program.cs after all the services have been added and the HTTP request pipeline has been configured but, before the application is run. We will also revisit this code once we have the seeding process working.

Run the program to initialize the database

Before we run the program, let’s check the state of the tables.

Right click on the Vehicle table and select View Data. Next right click on the VehicleType table and also select View Data. With the VehicleType [Data] and Vehicle [Data] windows open we can visually inspect that the tables are empty and no records exist yet.

Next, open a console window, navigate the command prompt to the FredsCars project and run the application with the following command. If the application is currently running, click Ctrl-C in the console window to stop it and run the following command to restart it.

 dotnet run --launch-profile "https"

Now refresh the data tables by clicking the green refresh icon in the upper left of both table [data] tabs and we can see that the Vehicle and VehicleType tables have been seeded with our test data. Great.

Success! But, we are not done yet. Remember we coded the SeedData.Initialize method for three scenarios.

  1. No data. Both tables are empty. We have already tested this scenario.
  2. VehicleType table has data but Vehicle table is empty.
  3. Vehicle table has data but VehicleType table is empty.

Complete Seed Scenario testing

Let’s go on to scenario two. We can set this up by deleting all of the existing records from the Vehicle table.

Select all of the records in the Vehicle table and hit the Delete button on your keyboard.

Restart the application.

Ctrl-C
dotnet run --launch-profile "https"

Now, when I inspect the table data again, I can see the Vehicle records were inserted (which we wanted) but, no new records were added to the VehicleType table (which we also wanted).

This is really what the second seeding scenario is really all about; to make sure no duplicate entries end up in the VehicleType table if the Vehicle table is empty and needs to be filled. Otherwise we could end up with six VehicleType entries with three duplicates.

However, if we look closer at the Vehicle data, it did enter the twelve test records. But, the Id field now currently starts with 13 rather then 1. This is not a very big deal and definitely not a show stopper. But, let’s see if we can figure out a way to make the Id start back at 1 every time.

Modify SeedData.cs with the following code.

FredsCars/Models/SeedData.cs

... existing code ...
// Add Vehicle records
//   if no Vehicle data exists yet.
if (!context.Vehicles.Any())
{
    // Reset Identity seed value to 1
    context.Database
        .ExecuteSqlRaw("DBCC CHECKIDENT ('Vehicle', RESEED, 0)");

    context.Vehicles.AddRange(
... existing code ...

The nice thing about Entity Framework Core is that it will let you call raw SQL commands if needed. That is what we are doing with this new line of code.
We use the Database property of the DbContext to call the ExecuteSqlRaw command and insert an SQL command as a string for the parameter. The string contains an SQL DBCC (Database Console Command).

The DBCC used here is CHECKIDENT.

We pass three parameters to the DBCC command:

  1. Vehicle: the table to target.
  2. RESEED: The action to take on the target value. We want to reset the starting value for the Id (IDENTITY) column.
  3. 0: The index value to to use for the starting IDENTITY column. We use 0 so the first row’s Id will be 1. If we had used 1, the first row’s Id would be 2. And, so on.

Delete all of the Vehicle table’s records again and restart the application.

Ctrl-C
dotnet run --launch-profile "https"

Now, let’s reinspect the table data.

Perfect. The Vehicle table was refilled with 12 records and the first Id field starts with 1 rather than 13.

Let’s move on to the final test scenario for Seeding the database.


Scenario 3: Vehicle table has data but VehicleType table is empty.

Let’s start off by making the same modification to SeedData.cs for the VehicleType table that we made for the Vehicle table.

FredsCars\Models\SeedData.cs

... existing code ...
// if data exists in both tables
//   return - kickout.
if (context.Vehicles.Any() && context.VehicleTypes.Any())
{
    return;
}

// Add VehicleType records
//   if no VehicleType data exists yet.
if (!context.VehicleTypes.Any())
{
    // Reset Identity seed value to 1
    context.Database
        .ExecuteSqlRaw("DBCC CHECKIDENT ('VehicleType', RESEED, 0)");

    context.VehicleTypes.AddRange(
        new VehicleType
        {
            Name = "Car"
        },
... existing code ...

In the above code we are repeating the same IDENTITY reseeding process with ExecuteSqlRaw and the DBCC for the VehicleType table we used on the Vehicle table if we need to enter only VehicleType data.

Delete all of the records from the VehicleType table.

Restart the application.

Ctrl-C
dotnet run --launch-profile "https"

The VehicleType table should now be filled with three records. Furthermore the first record starts with an Id IDENTITY column of 1 (not 4). And the Vehicle table still has only 12 records. So, we did not fill it with 12 more duplicate records. Good.


The Seeding process is complete and we now have data to work with to start developing the features of our application.

And, we have created a well rounded, well tested seeding routine. No matter what state our tables are in, when we restart our application anytime during the development process, we will be back to the original state of our test data. However, if we have added any records to the Vehicle or VehicleType tabels during development and not deleted all the records from that table, the new records will remain.

The (One or Zero)-to-Many DB Relationship

In the last module we looked at Database Relationships and we said that because a VehcileType can belong to many Vehicles and each Vehicle can have only one VehicleType, they are said to have a one-to-many relationship.

Earlier in this module I defined the three seeding test scenarios. Scenario number 3 was that the Vehicle table has data but the VehicleType table is empty. There is a slight caveat here I should talk about before moving on.

If you delete the three VehicleType records and then refresh the Vehicle table, you will see that all of the Vehicle records have also been deleted automatically as a result of deleting the VehicleType records. So the way the project stands now, scenario three will never exist.

This is because when Entity Framework created the database tables, it assumed that if you delete a VehicleType record, it should also delete all of the Vehicle records that reference the deleted VehicleType through the VehicleTypeId foreign key.

We can see this if we open the Vehicle design tab from SSOX and study the SQL used to create the table. When it sets up the foreign key constraint it tacks on the DELETE CASCADE option which deletes rows in a child table when a corresponding row in the parent table is deleted. This feature maintains referential integrity in the database.

This is a true one-to-many relationship. Each Vehicle must have 1 VehicleType or it cannot exist.

If we wanted a Vehicle to have the option of not having a VehicleTypeId defined, this type of relationship would be a (one-or-zero)-to-many relationship.

If we need to we can override the default one-to-many relationship in the DbContext‘s OnModelCreating method where we overrode the default table names in the last module.

Modify FredsCarsDbContext.cs with the code below.

FredsCars/Data/FredsCarsDbContext.cs

... existing code ...        
public DbSet<Vehicle> Vehicles => Set<Vehicle>();
        public DbSet<VehicleType> VehicleTypes => Set<VehicleType>();

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // specify table names
            modelBuilder.Entity<Vehicle>().ToTable("Vehicle");
            modelBuilder.Entity<VehicleType>().ToTable("VehicleType");

            // modify relationships
            // 0 or 1 VehicleType to many Vehicles
            modelBuilder.Entity<VehicleType>()
                .HasMany(vt => vt.Vehicles)
                .WithOne(v => v.VehicleType)
                .IsRequired(false);
        }
    }
}

We won’t actually implement the (one-or-zero)-to-many relationship for now. But if we find the one-to-many relationship tripping us up in the future, we can have the code ready to override the default behavior. Let’s comment out the new code but have it ready to go if needed. Modify FredsCarsDbContext.cs with the code shown below.

FredsCars/Data/FredsCarsDbContext.cs

... existing code ...        
public DbSet<Vehicle> Vehicles => Set<Vehicle>();
        public DbSet<VehicleType> VehicleTypes => Set<VehicleType>();

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // specify table names
            modelBuilder.Entity<Vehicle>().ToTable("Vehicle");
            modelBuilder.Entity<VehicleType>().ToTable("VehicleType");

            // modify relationships
            // 0 or 1 VehicleType to many Vehicles
            //modelBuilder.Entity<VehicleType>()
            //    .HasMany(vt => vt.Vehicles)
            //    .WithOne(v => v.VehicleType)
            //    .IsRequired(false);
        }
    }
}

Code Review

Now that we have completed “Seeding” the database, let’s take a closer look at the code we used to accomplish this step.

Program.cs

Let’s start by looking at the calling code in Program.cs

The calling code is contained in a C# if block condition.

if (builder.Environment.IsDevelopment())
{
   // Calling code
}

We only want to seed the database in the development environment. And, that is what the above check does using the IsDevelopment method of the WebApplicationBuilder‘s Environment property.

In the “Environment in ASP.Net Core” section of the last module (module 10, Install EF Core & Create the Database), we learned all about Environment; that it is a first class concept in ASP.Net Core, and that it comes with three environments out of the box: Development, Staging, and Production.

The IsDevelopment method checks if the current host environment name is Development and returns a bool value that will be true if we are indeed running in Development and false otherwise. If true, the code within the C# if block is executed. Otherwise, the application will skip the calling code and move on to the app.run() statement to run the application.


Next, if we are indeed running in Development, a C# using block is used to create a variable named scope of type IServiceScope by calling the CreateScope method of the WebApplication object’s Services property.

using (var scope = app.Services.CreateScope())
{
    var context = scope.ServiceProvider
        .GetRequiredService<FredsCarsDbContext>();
    SeedData.Initialize(context);
}

The first line within the using block declares a variable named context. We fill the context variable with an instance of the FredsCarsDbContext object by calling the generic GetRequiredService method where type of T is FredsCarsDbContext. We access the GetRequiredService from the scope variable’s ServiceProvider property which returns an IServiceProvider.

var context = scope.ServiceProvider
    .GetRequiredService<FredsCarsDbContext>();

The end result from the line above is that we pull an instance of the FredsCarsDbContext object as a service from the application’s ASP.Net Core DI (Dependency Injection) system, or Service Container.

The final line in the using block calls the static Initialize method of our SeedData class and passes to it as a parameter the context variable holding the service instance of FredsCarsDbContext we just pulled from DI.

SeedData.Initialize(context);

There is a lot going on in this little bit of code. And, there are a few concepts here we can learn about C#. So, let’s take the opportunity to dig in to some of these ideas before we move on to the actual SeedData.Initialize method.

DI (Dependency Injection) in ASP.Net Core

Services, like FredsCarsDbContext, are classes we define that contain features needed in multiple parts of the program. Some good examples are database access and logging.

We defined FredsCarsDbContext as a service earlier on in Program.cs in the Add Services section with the following code.

builder.Services.AddDbContext<FredsCarsDbContext>(opts =>
{
    opts.UseSqlServer(
        builder.Configuration["ConnectionStrings:FredsCarsMvcConnection"]
    );
});

So, now we can use this service anywhere in the application we need it by “injecting it” into the component requesting it.

Once we get into creating the features for the application using controllers and views, we will see examples of “constructor injection“. Here in Program.cs, we are not working in a component with a constructor so we had to use the GetRequiredService<T>() method directly from the DI service provider to grab an instance of FredsCarsDbContext.

Problems DI solves
Tight Coupling

The biggest problem DI solves is Tight Coupling. Say instead of registering FredsCarsDbContext in the DI system and pulling it from the Service Provider in the using block we are now studying, we directly instantiate it every time we need it with the C# new keyword. (The new keyword in C# is used to create object instances of classes).

// using (var scope = app.Services.CreateScope())
// {
    // var context = scope.ServiceProvider
    //    .GetRequiredService<FredsCarsDbContext>();

    var optionsBuilder = new DbContextOptionsBuilder<FredsCarsDbContext>();
    optionsBuilder.UseSqlServer(
        builder.Configuration["ConnectionStrings:FredsCarsMvcConnection"]
    );
    var context = new FredsCarsDbContext(
        optionsBuilder.Options
    );

    SeedData.Initialize(context);
// }

As you can see there can be a lot of build up and configuration when instantiating an object manually instead of relying on the DI system where the service is configured and registered one time. In the above example it took seven lines to replace our two lines of DI to grab an instance of FredsCarsDbContext.

NOTE: One of the original goals of .Net was to minimize the amount of code developers needed to write.

Also, manually instantiating services with the new keyword tightly couples the components making it hard to change or replace implementations. One of the most common tasks we need to replace an implementation of a service is in unit testing.


Testing

Another advantage to DI is that it makes it easier to test components like controllers by swapping out implementations of services the component depends on which are called dependencies.

Again, once we get into creating features using controllers (and views) and unit testing these features we will see being able to easily swap out implementations of services makes it easier to test components. For instance, if we create a unit test for a controller that depends on the FredsCarsDbContext service and the test fails, how do we know if the error is coming from the controller component or the FredsCarsDbContext? To solve this problem we will mock instances of the dbContext to inject into the components we are testing.

DI is a complicated subject for a lot of people to get their heads around. But, we will see plenty of examples of all these concepts in action in later modules. Stay tuned!

If you typed in the above examples, modify the code back to our current state.

/FredsCars/Program.cs

... existing code ...
/*** Add endpoints for contoller actions and
       the default route ***/
app.MapDefaultControllerRoute();

/*** Seed the database ***/
if (builder.Environment.IsDevelopment())
{
    using (var scope = app.Services.CreateScope())
    {
        var context = scope.ServiceProvider
            .GetRequiredService<FredsCarsDbContext>();
        SeedData.Initialize(context);
    }
}

app.Run();
... existing code ...
Service Lifetimes

In the using block we are currently inspecting within Program.cs, we grab FredsCarsDbContext from the DI Service Container using the GetRequiredService<T> method (where type of T is FredsCarsDbContext)

using (var scope = app.Services.CreateScope())
{
    var context = scope.ServiceProvider
        .GetRequiredService<FredsCarsDbContext>();
    SeedData.Initialize(context);
}

Here, GetRequiredService<T> is a method of the IServiceScope typed scope variable’s ServiceProvider property.

But, the builder (of type WebApplicationBuilder) and app (of type WebApplication) variables also have a GetRequiredService<T> method. So we could change how we grab the FredsCarsDbContext service with the modification in Program.cs below.
Do not make the following modification in your own project.

using (var scope = app.Services.CreateScope())
{
    var context = app.Services.GetRequiredService<FredsCarsDbContext>();
    SeedData.Initialize(context);
}

So, what is the difference between these ways of using the GetRequiredService<T> method?

var context = scope.ServiceProvider
     .GetRequiredService<FredsCarsDbContext>();
var context = app.Services.GetRequiredService<FredsCarsDbContext>();

The answer lies in the fact that ASP.Net Core services can have different lifetimes. There are three lifetimes in ASP.Net Core as follows.

Singleton:
Method used to create a singleton service is:
AddSingleton<T, U>

One instance shared by all dependencies for the lifetime of the application.


Transient:
Method used to create a transient service is:
AddTransient<T, U>

A new instance of the service is created each time it is requested to resolve a dependency.


Scoped:
Method used to create a scoped service is:
AddScoped<T, U>

One instance is created per request (or per scope) to resolve all dependencies in the request.


In each of the three methods above, Type U (the concrete class) is used to resolve Type T (the Interface).

In later modules we will build repositories using the repository pattern as a way to separate out data access from our controllers making the application easier to maintain and scale. Also again, this will make each layer and component of the application easier to unit test.

Below is an example of registering a repository service with a service lifetime of Scoped in the Add Services section of Program.cs

builder.Services.AddScoped<IVehicleRepository, VehicleRepository>

In the above line of code we are registering the VehicleRepository service to resolve any dependencies on an IVehicleRepository
using the AddScoped method signature shown once again below.

AddScoped<T, U>

VehicleRepository is Type U resolving
IVehicleRepository (Type T).


Now that we understand a little about service lifetimes, hover your mouse over the AddDbContext<FredsCarsDbContext>() registration method in Program.cs

In the image above we can see that the AddDbContext<T> method registers a DbContext service with a service lifetime of Scoped.

Ok, let’s circle back around now. If in our using block I go back to the following line using the GetRequiredService<T>() method of the app.Services property:

using (var scope = app.Services.CreateScope())
{
    var context = app.Services.GetRequiredService<FredsCarsDbContext>();
    
    SeedData.Initialize(context);
}

And hover over the Services property in the Visual Studio:

Then you can see the Services property off of the app (WebApplication object) variable returns the application’s configured services. But, no service lifetime has been defined. And, if no service lifetime is defined, the default lifetime is transient. So, since the AddDbContext<T> method expects a service lifetime of scoped, we need to create a scope manually. Otherwise we will get an error that the service lifetime is transient when a scoped lifetime is expected. And that is what we do in the using block.

/*** Seed the database ***/
if (builder.Environment.IsDevelopment())
{
    using (var scope = app.Services.CreateScope())
    {
        var context = scope.ServiceProvider
            .GetRequiredService<FredsCarsDbContext>();
        SeedData.Initialize(context);
    }
}

Now if we hover over the ServiceProvider property of the newly created scope in the IDE, we can see that it is able to resolve dependencies from the scope.

The C# using keyword

Well, we spent a good amount of time in the section above talking about service lifetimes and studying the code in the using block.

But, what exactly is a using block anyway? What does the using reserved word mean in C# anyway?


In C#, a using block is a construct that ensures that objects implementing the IDisposable interface are properly disposed of (from memory) once they are no longer needed. It is primarily used for managing resources like file handles, database connections, or network streams that need to be explicitly released to avoid resource leaks.


The syntax of the using block is as follows:

using (var resource = new SomeDisposableResource())
{
    // Use the resource
}
// The resource is automatically disposed of here, even if an exception occurs.

In our using block’s expression (the assignment statement that goes between the parenthesis) the CreateScope method returns an object of type IServiceScope.

using (var scope = app.Services.CreateScope())

Hover over the CreateScope method and in the popup click on the IServiceScope link.

A new tab pops open in Visual Studio and we are taken to a definition for the ISeviceScope interface.

public interface IServiceScope : IDisposable
{
    /// <summary>
    /// Gets the <see cref="System.IServiceProvider"/> used to resolve dependencies from the scope.
    /// </summary>
    IServiceProvider ServiceProvider { get; }
}

Note: Interface names are prefixed with an ‘I’ by convention as in: IMyInterface.

In the code definition for IServiceScope above, we can see that it implements the IDisposable interface.


We have seen the colon character before when we looked at inheritance where it means Type A inherits from Type B.

// MyClassA inherits from MyClassB
public class MyClassA : MyClassB

Here, rather then inherits, it means implements as in:

// MyInterfaceB implements MyInterfaceA
public interface MyInterfaceB : MyInterfaceA

IDisposable is an interface in the .Net framework that is used to release unmanaged resources from memory and perform cleanup operations for objects when they are no longer needed via its Dispose method.


All of this means that within our using block, once the call to SeedData.Initialize(context) is made, the Initialize(context) method finishes executing and returns control back to the calling using block, and we then exit the using block, the IDisposable.Dispose method will be called to destroy the scope variable of type IServiceScope freeing up that piece of memory.

Garbage Collection: managing memory in C#

In older programming languages like C++, programmers had to manually keep track of objects and remove them from memory when no longer needed. If they forgot to remove objects these were called memory leaks. And if there were too many memory leaks the computer would run out of memory and the application would crash.

In .Net (and in C# since it is a programming language for .Net) memory is managed for us through a process called Garbage Collection.

So, we don’t have to keep track of every object we use in .Net and remove them from memory ourselves. .Net takes care of that for us. This is what makes C# a managed language as opposed to C++ which is an unmanaged language.


What all of this means is that anytime you see an object in .Net that implements IDisposable, that probably means it is an unmanaged resource and should be created in a using block so it will be disposed of properly.

If you would like to know more about the garbage collection process you can read about here.


Alternative C# Using Declaration

In C# version 8 and later we can use the alternative Using declaration rather then a Using block.

The syntax for a Using declaration is as follows.

using var resource = new SomeDisposableResource();
// Use the resource
// The resource is disposed of when it goes out of scope.

This approach avoids the need for an explicit block, making the code a little cleaner. The resource is still disposed of when the containing scope (e.g., a method or block) ends.

So, we could replace this code:

using (var scope = app.Services.CreateScope())
{
    var context = scope.ServiceProvider
        .GetRequiredService<FredsCarsDbContext>();
    SeedData.Initialize(context);
}

with this:

using var scope = app.Services.CreateScope();
var context = scope.ServiceProvider
        .GetRequiredService<FredsCarsDbContext>();
SeedData.Initialize(context);

Let’s do that now. But, make sure to go back through the Database Seeding scenario testing we did earlier in this module for our three scenarios.


At this point we call the Initialize method of our SeedData class and pass to it the context variable which contains an object instance of the FredsCarsDbContext service.

I think we have covered the calling code to seed the database pretty well in Program.cs. Next, let’s walk through the code in the actual SeedData class.

The SeedData class

The SeedData class is a public class with one public static method called Initialize.


static members in C#

For now, just know that a static method is a method that belongs to the class itself rather than an object instance of the class. This means you can call a static method without creating an instance of the class.

If the Initialize method was not marked as static, than back in Program.cs we would have to first create an object instance of the SeedData class and then call the Initialize method from it like this:

// seedData of type SeedData is assigned a new instance of SeedData.
SeedData seedData = new SeedData();
seedData.Initialize(context);

We would not be able to call the Initialize method directly off of the SeedData type name without first instantiating it as in the below line.

// Can only do this if Initialize method is static.
SeedData.Initialize(context);

You can also have static fields as well as static methods in a class. A common static field to have is a counter to keep track of how many object instances of the class currently exist in memory.

Below is an example of a class with a static counter field.

public class StaticFieldExample
{
    // shared counter field between all 
    //   object instances of StaticFieldExample class
    private static int _counter;
    
    public void IncrementCounter()
    {
        // Add one to the counter using the
        //   increment operator (++)
        _counter++;
    }

    public void DecrementCounter()
    {
        // Subtract one from the counter using the
        //   decrement operator (--)
        _counter--;
    }

    public int NumberOfObjectInstancesAlive()
    {
        // return number of StaticFieldExample objects in memory.        
        return _counter;
    }

    public bool ObjectInstancesExist()
    {
        if (_counter == 0)
        {
            return false;
        }
        return true;
    }
}

And finally a class itself can be marked as static. This means that the class itself cannot be instantiated. All of its fields, properties, and methods must be static.


Below is the signature of the Initialize method.

public static void Initialize(FredsCarsDbContext context)

It receives as a parameter an instance of the FredsCarsDbContext service passed to it by a call from Program.cs in the last line of the using block we just studied in detail earlier in this module.

The first statement in the Initialize method is an if block that checks whether both the Vehicle and VehicleType tables have data.

// if data exists in both tables
//   return - kickout.
if (context.Vehicles.Any() && context.VehicleTypes.Any())
{
    return;
}

An if block in C# contains an expression between its parenthesis that returns a bool (Boolean) value of either true or false.

if (expression){
   // code to execute in the block
}

If the expression is true then all of the code within the if block is executed, otherwise the if block is skipped and the program continues on with execution of the method.

The expression in our if block itself contains two sub Boolean expressions both of which must be true for the if block to execute.

context.Vehicles.Any() && context.VehicleTypes.Any()

The first sub expression on the left asks if the Vehicles DbSet property of the dbContext contains any Vehicle elements using the LINQ to Entities method, Any().

context.Vehicles.Any()

The expression on the left is followed by the C# logical AND (&&) operator.

The second sub expression following the && (AND) operator checks if the VehicleTypes DbSet property of the dbContext contains any VehicleType elements also using the LINQ to Entities method, Any().

context.VehicleTypes.Any()

These two sub expressions, each of which return a Boolean value, tied together by the AND operator, form one single compound expression which itself returns a Boolean value of either true or false.

So this expression in the if block:

context.Vehicles.Any() && context.VehicleTypes.Any()

means:

if there are any Vehicle records and there are any VehicleType records

In our logic, if there are Vehicle elements in the Vehicles DbSet, and there are VehicleType elements in the VehicleTypes DbSet, then the C# return statement is executed, control is passed back to the calling code in Program.cs, the Initialize method never executing the remaining code to actually seed the database.

If, however, either DbSet Vehicles or VehicleTypes do not contain elements, then the return statement in the if block is never executed and execution will continue on with the SeedData.Initialize method.


Operators in C#

Since we are talking about Boolean expressions being used to make decisions and control the flow of execution in if blocks, this is a good place to learn about C# operators. (Boolean expressions are also used to to make decisions on execution flow in other programming structures as well such as looping structures; For Loop, While Loop, Do While Loop, etc ).

There are four types of operators in C#; Assignment, Arithmetic, Comparison, and Logical.

Assignment Operators

Let’s start with the Assignment operators. The most common type of assignment operator is the Basic Assignment operator. An example of a basic assignment operator in use follows in the code line below.

int x = 10;

At first look you might think the above code line is read “x of type int equals 10.” But, that would be incorrect. The equals operator which we will see under comparison operators actually looks like this: “==”.
The “=” operator in the above code line is actually the basic assignment operator. The line is read as, “x of type int is assigned 10.”
So now, x equals 10.


A second type of assignment operator are Compound Assignment Operators. An example follows in the code below.

int x = 10; // basic assignment operator
x += 5 // compound assignment operator

Above, the compound addition assignment operator (+=) is used:
x += 5
The new compound statement: x += 5
is shorthand for: x = x + 5.
And they are both read as, “x is assigned x + 5.”
So, if x currently equals 10, then the statement executes as:
x = 10 + 5
So, x will now be 15.


In addition to the compound addition assignment operator, there are also several other types for subtraction, multiplication, division, and more. Below is a list the most common compound assignment operators.

OperatorExampleSame As
+=x += 3x = x + 3 Addition Compound Assignment operator
-=x -= 3x = x – 3 Subtraction
Compound Assignment operator
*=x *= 3x = x * 3 Multiplication
Compound Assignment operator
/=x /= 3x = x / 3 Division
Compound Assignment operator
%=x %= 3x = x % 3 modulus Compound Assignment operator
Arithmetic Operators

Arithmetic operators are used to perform common mathematical operations on variables and values. The example below adds together two values using the Addition operator.

int x = 100 + 50;

The above example declares a variable named x as an int value type. It is read as, “x is assigned 100 + 50”. So, now x equals 150.

Operators such as Arithmetic operators have operands on both sides.
Here the addition (+) sign is the operator. The values 100 and 50 are the operands.


Although the + operator is often used to add together two values, as in the example above, it can also be used to add together a variable and a value, or a variable and another variable as in the examples below.

int sum1 = 100 + 50;        // 150 (100 + 50)
int sum2 = sum1 + 250;      // 400 (150 + 250)
int sum3 = sum2 + sum2;     // 800 (400 + 400)

There are two special Arithmetic operators called the Increment operator (++) and the Decrement Operator (--).

Here is an example of the increment operator.

int x = 10;
x++; // x == 11;

Above, the increment arithmetic operator (++) is used:
x++
The increment operator increments the variable’s value by 1.
The statement: x++
is shorthand for: x = x + 1.
And it is read as, “x is assigned x + 1.”
So, if x currently equals 10, then the statement executes as:
x = 10 + 1
So, x will now be 11.

Here is an example of the decrement operator.

int x = 10;
x--; // x == 9;

Above, the decrement arithmetic operator (--) is used:
x--
The decrement operator decrements the variable’s value by 1.
The statement: x--
is shorthand for: x = x – 1.
And it is read as, “x is assigned x – 1.”
So, if x currently equals 10, then the statement executes as:
x = 10 - 1
So, x will now be 9.


Below is a list of the Arithmetic Operators.

OperatorNameDescriptionExample
+AdditionAdds together two valuesx + y
–SubtractionSubtracts one value from anotherx – y
*MultiplicationMultiplies two valuesx * y
/DivisionDivides one value by anotherx / y
%ModulusReturns the division remainderx % y
++IncrementIncreases the value of a variable by 1x++
—DecrementDecreases the value of a variable by 1x–
Comparison Operators

Comparison operators are used to compare two values (or variables).

This is important in programming, because it helps us to make decisions about what code to execute, what code to skip, and what branch to take when there are multiple possible paths as in ‘if-then-else' blocks and switch blocks. (A switch block is a programming construct that allows you to execute different code sections based on the value of a variable.)

The return value of a comparison is either True or False. These values are known as Boolean values.

Let’s look at an example of using a Comparison operator.

int x = 5;
int y = 3;
Console.WriteLine(x > y); // returns True because 5 is greater than 3

In the code above, we use the Greater than comparison operator to check if x is greater than y and if so write true out to the console and if not write false to the console.

And this is how the expression gets evaluated.

x = 5; y = 3;
x > y;  // variable comparison
5 > 3;  // converts to literal comparison
true    // result

Below is a list of Comparison operators.

OperatorNameExample
==Equal tox == y
!=Not equalx != y
> Greater thanx > y
< Less thanx < y
>=Greater than or equal tox >= y
<=Less than or equal tox <= y
Logical Operators

In the section above, we just learned that Comparison Operators are used to compare values and return a Boolean result. And, we can use that Boolean result to make decisions on execution flow. For instance,

int x = 5;
int y = 3;
if (x > 5)
{
   // execute this code
} 
else {
  // execute some other code
}

Logical operators in C# allow us to build on this and define more complex conditions to control application flow. As an example, look at the code below.

int x = 8;
x < 5 & x < 10;  // returns false   

In the code above x is declared as 8.
The next line has two logical Boolean expressions.
On the left side we have the expression: x < 5.
The expression on the left is followed by the bitwise AND (&) operator.
On the right side of the AND operator we have the expression: x < 10.
The whole second line is read as:
if x is less then 5 AND x is less then 10.

This expression could be used to control execution flow as in the following code.

int x = 8;
if (x < 5 & x < 10)
{
   // run this code
}
else{
   // run some other code
}

Expressions can become pretty complex. Sometimes wrapping expressions in parenthesis can help clarify exactly what we mean in the expression.

int x = 10;
int y = 15;
(x < y & x < 12) & (x == 12)

In the above example, first we perform all of the logic in the parenthesis set on the left side of the AND (&) operator:
x < y == true
x < 12 = true
true & true == true

Then we perform all of the logic in the parenthesis set on the right side of the AND (&) operator:
x == 12
false

Then we combine the two results.
true & false
false // final result


Binary Logical Operators

In the above example we used the binary AND (&) logical operator. Binary logical operators can perform logical operations on binary numbers as well as on value types like int. But that is beyond the scope of this book and we will not need to dig that deep for what we are doing in ASP.Net Core.
Below is a list of the Binary Logical Operators.

NAMEOPERATORDESCRIPTIONEXAMPLE
AND&Returns True if both statements are truex < 5 & x < 10
OR|Returns True if one of the statements is truex < 5 | x < 4
XOR^True if one statement is true (but not both)x < 5 ^ x > 7
Conditional Logical Operators

Conditional logical operators are like binary logical operators but they have two symbols instead of one. Plus they contain a feature called short circuiting.

Let’s look at an example using a conditional operator.

int x = 8;
if (x < 5 && x < 10)
{
   // run this code
}
else{
   // run some other code
}

The above example uses the conditional AND (&&) logical operator.
In the first line, x is declared as 8.
In the second line the double ampersand (&&) is the conditional AND operator used in the if block’s compound Boolean expression.
The left side of the expression, x < 5, is false.

Short Circuiting

This is where the short circuiting feature of the double ampersand (&&) AND operator kicks in.
Since the left side of the AND (&&) operator, x < 5, is false, the right side of the operator, x < 10 is never checked. Since the outcome of the compound expression is already known to be false after checking the left side, there is no need to evaluate the right side, and the proper branch of the if-then-else block can be chosen to execute improving overall performance.
This is the short circuiting feature in action.


The conditional OR (||) operator works in a similar fashion as the conditional AND (&&) operator when it comes to short circuiting. Let’s look at an example.

int x = 8;
if (x < 5 || x < 10)
{
   // run this code
}
else{
   // run some other code
}

In the above code, x is again declared to be 8.
The next line uses the conditional OR (||) operator in its compound Boolean expression.
The left side of the OR operator, x < 5, is true.
Since an OR statement only needs one of its operands to be true, there is no need to evaluate the right side operand. The outcome of true is already known and the first path in the if-then-else block can execute.

Below is a list of Conditional Logical Operators.

NAMEOPERATORDESCRIPTIONEXAMPLE
AND&&Returns True if both statements are truex < 5 && x < 10
OR||Returns True if one of the statements is truex < 5 || x < 4
NOT!Reverse the result, returns False if the result is true!(x < 5 && x < 10)

Truth tables

In the above section, Operators in C#, we learned how to use all of the C# operators to create simple to more complex Boolean expressions that return either true or false as a result. And, we can use the result to make decisions on which branches of code to execute.

Truth Tables can be used to sum up all possible outcomes of true/false input operands on both sides of an AND or OR operator. In mathematics these inputs are usually called p and q.

AND truth table

The AND operators produce the following set of possible outcomes:

P AND Q

PQP AND Q
TRUETRUETRUE
TRUEFALSEFALSE
FALSETRUEFALSE
FALSEFALSEFALSE

As you can see from the truth table, it is only if both inputs are true that the result will equate to true. If one or other or both of the inputs in the conjunction are false, then the conjunction equates to false.

OR truth table

The OR operators produce the following set of possible outcomes:

P OR Q

PQP OR Q
TRUETRUETRUE
TRUEFALSETRUE
FALSETRUETRUE
FALSEFALSEFALSE

As you can see, if one or other of the inputs is true, the expression will equate to true. It is only if both conditions are false that the entire collection equates to false.

Unknown/Null (Uncertainty) truth table

There will be times when one side (operand) of an AND or OR operator will be unknown or null (no value).

The following truth tables show how uncertainty works:

AND Uncertainty truth table
PQP AND Q
TRUEUNCERTAINUNCERTAIN
UNCERTAINTRUEUNCERTAIN
FALSEUNCERTAINFALSE
UNCERTAINFALSEFALSE
UNCERTAINUNCERTAINUNCERTAIN
OR Uncertainty truth table
PQP OR Q
TRUEUNCERTAINTRUE
UNCERTAINTRUETRUE
FALSEUNCERTAINUNCERTAIN
UNCERTAINFALSEUNCERTAIN
UNCERTAINUNCERTAINUNCERTAIN

Add the VehicleType Records

So far we have covered the following amount of code in the SeedData class.

using FredsCars.Data;
using Microsoft.EntityFrameworkCore;

namespace FredsCars.Models
{
    public class SeedData
    {
        public static void Initialize(FredsCarsDbContext context)
        {
            // if data exists in both tables
            //   return - kickout.
            if (context.Vehicles.Any() && context.VehicleTypes.Any())
            {
                return;
            }
... existing code ...

Up until now the work we have done has been mainly setup and configuration. This is one of the first places we are really starting to code. And, since the three main blocks of code in the Initialize method depend on if block decisions on whether or not to execute, we took the scenic route and learned all about Boolean expressions which return either true or false, all about C# operators, and how we use the two together to make decisions in our code.

Now that we have that out of the way, I can move much quicker when talking about an if block or looping block signature. Let’s move on to the second block of code in the Initialize method where we add the VehicleType records to the database.

if (!context.VehicleTypes.Any())
{
    // Reset Identity seed value to 1
    context.Database
        .ExecuteSqlRaw("DBCC CHECKIDENT ('VehicleType', RESEED, 0)");

    context.VehicleTypes.AddRange(
        new VehicleType
        {
            Name = "Car"
        },
        new VehicleType
        {
            Name = "Truck"
        },
        new VehicleType
        {
            Name = "Jeep"
        }
    );
    context.SaveChanges();
}

In the code above we check if there are any VehicleType elements in the VehicleTypes DbSet using an if block expression.

if (!context.VehicleTypes.Any())

Notice the Logical NOT (!) operator before the conditional expression. The Any() LINQ to Entities method returns a C# bool value of true if there are VehicleType entities in the DbSet. We don’t want to fill the DbSet if it already contains entities. So, the expression would evaluate to:
NOT true
You can think of the expression as
if not any vehicle types

If the VehicleTypes DbSet contains no elements, then the Any() method returns false. So, NOT false would evaluate to true. So the if block code executes and we fill the VehicleTypes DbSet with VehicleType entity elements.

So, if not any vehicle types is true, then execution enters into the if block to execute and first makes sure to reseed the Database to 1.

context.Database
    .ExecuteSqlRaw("DBCC CHECKIDENT ('VehicleType', RESEED, 0)");

Next the AddRange method of the VehicleTypes DbSet property is called. The AddRange method takes as a parameter a params VehicleType Array (params VehicleType[]). Just think of this as a list of VehicleTypes for now.

The List of VehicleTypes to use as a parameter to the AddRange method is created by using the C# new operator to instantiate three VehicleType entities for Car, Truck, and Jeep.

new VehicleType
{
    Name = "Car"
},
new VehicleType
{
    Name = "Truck"
},
new VehicleType
{
    Name = "Jeep"
}

And finally, we use the SaveChanges method of the DbContext to save the newly created VehicleType entities to the Database VehicleType table.

context.SaveChanges();

Notice when we created the VehicleType entities, we only specified the name property values.

new VehicleType
{
    Name = "Car"
},

We did not specify the Id properties as in the following.

new VehicleType
{
    Id = 1,
    Name = "Car"
}

That is because when we insert new records into an SQL Server database, SQL Server creates the Ids for us. So, next we need to capture the Ids of the new VehicleType records. And that is what the next 3 sequential statements do.

NOTE: Sequential statements are one of the three basic programming structures; sequential, if-then[-else], and looping. Object Oriented Programming (OOP) is just using these three programming structures to create custom Types such as classes.

var carTypeId = context.VehicleTypes
        .FirstOrDefault(vt => vt.Name == "Car")!.Id;
var truckTypeId = context.VehicleTypes
    .FirstOrDefault(vt => vt.Name == "Truck")!.Id;
var jeepTypeId = context.VehicleTypes
    .FirstOrDefault(vt => vt.Name == "Jeep")!.Id;

In the above code we use the FirstOrDefault LINQ-to-Entities method of the VehicleTypes DbSet property to find the Id of each VehicleType and assign it to a variable in order to capture it. (We need these Ids to use a little later as foreign keys in our Vehicle elements.)

Let’s look at the first line as an example which captures the Car VehicleType Id.

var carTypeId = context.VehicleTypes
        .FirstOrDefault(vt => vt.Name == "Car")!.Id;

The FirstOrDefault method is a LINQ to Entities method that returns the first member of a sequence that satisfies a specified condition, in our case the sequence being the list of vehicles in the DbSet. It takes in a Lambda expression as a parameter and uses it as a predicate (or a set of criteria to search the DbSet by). For now just think of a Lambda expression as an anonymous function that is easy to pass to another method. The Lambda expression looks like this:

vt => vt.Name == "Car"

If the Lambda expression was written as a normal function it might look like this.

int getVehicleTypeById(VehicleType vt)
{
   var allVehicleTypes = context.VehicleTypes.ToList(); 
   int id;
   foreach (var vt in allVehicleTypes)
   {
      if (vt.Name == "Car")
      {
         id = vt.Id; 
      }
   }
   return id;
}

We can see that passing the Lambda expression is clearly more readable and eloquent then trying to write out a whole function and pass it as a parameter. Although there are quite a few ways to define functions in C# to pass around to other functions and methods (for instance setting up a Func delegate), using Lambdas is the most efficient way to work with Entity Framework and LINQ to Entities.

The next thing to note is that after the closing parenthesis of the Lambda expression is an exclamation mark. This is the null forgiveness operator.

var carTypeId = context.VehicleTypes
        .FirstOrDefault(vt => vt.Name == "Car")!.Id;

If take away the exclamation mark, Visual Studio will give us a warning that we are trying to grab the Id of a VehicleType object that possibly does not exist.

The null forgiveness operator is us telling .Net, “Don’t worry about it”. We are sure that the result of the Lambda criteria for the FirstOrDefault method will not return null. We are positive there is a VehilceType in the DbSet with a Name property value of “Car”.

Finally, we access the Id of the returned VehicleType element from the FirstOrDefault method using the C# dot (.) operator.

var carTypeId = context.VehicleTypes
        .FirstOrDefault(vt => vt.Name == "Car")!.Id;

And that Id value gets assigned to the carTypeId variable.

Add the Vehicle Records

Adding the Vehicle records to the database is very similar to to what we just saw with creating VehicleType elements and inserting them into the database as VehicleType records.

First, we check if there are any Vehicles in the database.

if (!context.Vehicles.Any())

NOTE: Technically, we are checking if there are any Vehicle elements in the Vehicles DbSet of the DbContext, not the database itself. Entity Framework takes a snapshot of the database when we create an instance of the DbContext and tracks all the records as entities in memory and any changes made to the entities such as updates, deletions, and creations. All of these changes are updated to the database when we call the SaveChanges method of a DbSet such as the Vehicles DbSet. A DbContext in Entity Framework (EF) “keep records in memory” by maintaining an in-memory cache of entities that it tracks during its lifetime. In ASP.Net Core, that lifetime is the duration of an HTTP request, or in this case the Scope of the DI Service Container we created back in Program.cs to get the FredsCarsDbContext service from the service container.

Next, if the Vehicles DbSet is empty, we make sure to reseed the Vehicles table’s Id IDENTITY column to 1.

context.Database
    .ExecuteSqlRaw("DBCC CHECKIDENT ('Vehicle', RESEED, 0)");

Then, we create a list of Vehicle entity objects adding them to the Vehicles DbSet, and insert them as records to the database using the SaveChanges method of the Vehicles DbSet:

... existing code ...
context.Vehicles.AddRange(
    // Cars
    new Vehicle
    {
        Status = Status.New,
        Year = "2021",
        Make = "Dodge",
        Model = "Challenger",
        Color = "Frostbite",
        Price = 64164,
        VIN = "2C3CDZFJ8MH631199",
        VehicleTypeId = carTypeId
    },
    ... existing code ...
    new Vehicle
    {
        Status = Status.New,
        Year = "2021",
        Make = "Jeep",
        Model = "Wrangler Sport S",
        Color = "Green",
        Price = 40940,
        VIN = "1C4GJXAN0MW856433",
        VehicleTypeId = jeepTypeId
    }
);
context.SaveChanges();
... existing code ...

What’s Next

In this module we added test data to the application by seeding the database.


Along the way we continued to expand on our knowledge in C#, talked some more about Dependency Injection, and learned about service lifetimes and how C# manages memory for us with garbage collection.

We also spent some time talking about using blocks, if-blocks, C# operators and truth tables in order to help us understand how Boolean expressions can help us control execution flow of an application.


At this point we have completed most of the configuration and setup of our MVC application. In the next module we are going to fetch a list of Vehicles from the database and show them in the results area of the landing page.

< Prev
Next >

Leave a ReplyCancel reply

Chapter 1: Static HTML – Designing the landing page.

  • Static HTML – Designing the landing page.
  • Let’s get started!
  • Mock your site with HTML
  • Make CSS easy with Bootstrap
  • Mock your content
  • Introducing JavaScript
  • JavaScript Code Improvements
  • Results Data
  • Images and the HTML Image Element.
  • Revisiting Reusability for CSS and JavaScript
  • Reuse for HTML: PART 1
  • Reuse for HTML: PART 2
  • Details Page – Using a Bootstrap Component
  • Creating Links
  • Chapter One Conclusion

Chapter 2: ASP.Net Core – Let’s talk Dynamic

  • Introduction to ASP.Net Core
  • What is .Net?
  • What is ASP.Net
  • Introduction to Entity Framework Core

Chapter 3: ASP.Net MVC Core – Models, Views, and Controllers [ASP.Net Core v9]

  • Introduction to ASP.Net Core MVC
  • Create the project: ASP.Net Core MVC
  • Explore the ASP.Net Core Empty Web Project Template
  • Configure the Application for MVC
  • Create a Controller: Home Controller
  • Create a View: Index View for the Home Controller
  • Install Bootstrap using Libman
  • Create the Layout template
  • Create the Model
  • Install EF Core & Create the Database
  • Seed the Database: Loading test data
  • DI (Dependency Injection): Display a List of Vehicles
  • Repository Pattern: The Vehicles Repo
  • Unit Test 1: Home Controller Can Use Vehicle Repository
  • Unit Test 2: Vehicle Repository Can Return List
  • Add the ImagePath Migration and Thumbnail images to results
  • Pagination: Create a Custom Tag Helper
  • Sorting
  • Category Filter
  • Partial View: Break out the vehicle results
  • View Component: Create dynamic category buttons
  • Create the Details page
  • Create the Create Page

Chapter 7: Using Server Side & Client Side technologies together. [ASP.Net Core v7 & Angular v15]

  • Intro to Full Stack Development
  • Fred’s Cars – Full Stack Development
  • Prepare the environment
  • Create the Visual Studio Solution
  • Add the ASP.Net Core Web API project
  • Add the Angular Project
  • Wire it up!
  • WeatherForecast: Understanding the basics
  • Vehicles API Controller: Mock Data
  • Vehicles Angular Component: Consuming Data
  • Routing and Navigation
  • Using a Component Library: Angular Material
  • Our first Angular Material Component: MatToolbar
  • Configuring for Saas: CSS with superpowers
  • Create the Header & Footer components
  • Displaying Results with MatTable
  • Loading: Using a Progress Spinner
  • MatTable: Client-Side Paging and Sorting
  • MatSidenav: Create a Search Sidebar
  • MatCheckbox: Category Search UI
  • Adding an image to the welcome page
  • Create the database with Entity Framework Core migrations
  • MatPaginator & PageEvent: Custom Server-Side Paging
  • Unit Testing: Custom Server-Side Paging
  • Repository Pattern: VehicleRepository
  • Unit Test: Paging in the Vehicles controller
  • Server-Side Sorting
  • Unit Tests: Sorting
  • Filter (Quick Search)
  • Unit Tests: Filter feature
  • Advanced Search: Categories
  • Unit Tests: Search by category
  • Progress Spinner: Final Fix

TOC

  • What were WebForms?
  • Enter MVC
    • Understanding MVC
    • Advantages of MVC
  • ASP.Net Core MVC – A total rewrite
  • ASP.Net Core 2 MVC – Here come Razor Pages
    • Understanding Razor Pages
  • ASP.Net Core 3 – Dropping the MVC reference
    • Understanding Blazor
  • Dropping the MVC reference
  • Hello .Net 5!
  • What’s Next? – Here comes .Net 6.

Recent Posts

  • Angular Commands Cheat Sheet
  • Installing Git for Windows
  • Installing Postman
  • Installing SQL Server 2022 Express
  • Installing Visual Studio 2022

Recent Comments

No comments to show.

Archives

  • November 2023
  • October 2023
  • June 2023
  • October 2021

Categories

  • Angular
  • ASP.Net
  • Environment Setup
  • See All
  • SQL Server
  • Visual Studio
  • Web API & Rest Services

WordPress Theme Editor

Copyright © 2025 Web Development School.

Powered by PressBook Blog WordPress theme