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!
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.
- No data. Both tables are empty. We have already tested this scenario.
- VehicleType table has data but Vehicle table is empty.
- 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:
- Vehicle: the table to target.
- RESEED: The action to take on the target value. We want to reset the starting value for the Id (IDENTITY) column.
- 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 theIDisposable
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.
Operator | Example | Same As | |
+= | x += 3 | x = x + 3 | Addition Compound Assignment operator |
-= | x -= 3 | x = x – 3 | Subtraction Compound Assignment operator |
*= | x *= 3 | x = x * 3 | Multiplication Compound Assignment operator |
/= | x /= 3 | x = x / 3 | Division Compound Assignment operator |
%= | x %= 3 | x = 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.
Operator | Name | Description | Example |
+ | Addition | Adds together two values | x + y |
– | Subtraction | Subtracts one value from another | x – y |
* | Multiplication | Multiplies two values | x * y |
/ | Division | Divides one value by another | x / y |
% | Modulus | Returns the division remainder | x % y |
++ | Increment | Increases the value of a variable by 1 | x++ |
— | Decrement | Decreases the value of a variable by 1 | x– |
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.
Operator | Name | Example |
== | Equal to | x == y |
!= | Not equal | x != y |
> | Greater than | x > y |
< | Less than | x < y |
>= | Greater than or equal to | x >= y |
<= | Less than or equal to | x <= 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.
NAME | OPERATOR | DESCRIPTION | EXAMPLE |
AND | & | Returns True if both statements are true | x < 5 & x < 10 |
OR | | | Returns True if one of the statements is true | x < 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.
NAME | OPERATOR | DESCRIPTION | EXAMPLE |
AND | && | Returns True if both statements are true | x < 5 && x < 10 |
OR | || | Returns True if one of the statements is true | x < 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
P | Q | P AND Q |
---|---|---|
TRUE | TRUE | TRUE |
TRUE | FALSE | FALSE |
FALSE | TRUE | FALSE |
FALSE | FALSE | FALSE |
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
P | Q | P OR Q |
---|---|---|
TRUE | TRUE | TRUE |
TRUE | FALSE | TRUE |
FALSE | TRUE | TRUE |
FALSE | FALSE | FALSE |
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
P | Q | P AND Q |
---|---|---|
TRUE | UNCERTAIN | UNCERTAIN |
UNCERTAIN | TRUE | UNCERTAIN |
FALSE | UNCERTAIN | FALSE |
UNCERTAIN | FALSE | FALSE |
UNCERTAIN | UNCERTAIN | UNCERTAIN |
OR Uncertainty truth table
P | Q | P OR Q |
---|---|---|
TRUE | UNCERTAIN | TRUE |
UNCERTAIN | TRUE | TRUE |
FALSE | UNCERTAIN | UNCERTAIN |
UNCERTAIN | FALSE | UNCERTAIN |
UNCERTAIN | UNCERTAIN | UNCERTAIN |
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.