This repositoru contains an experiment in seeing whether when pulling in an
entity through a Include call and Clearing a set on it of associated
entities will work with the EF change tracker.
This is similar to my other experiment where I was exploring options of
clearing an entity's set of associated entities without using Include.
The model of this experiment is as follows:
Userhas an associated entityCarCarhas a set of associated entitiesTripTrip
In our experiment, we are looking to see if we pull in Car by knowing its
owner User and clear its Trips, whether EF Core change tracker will
understand this.
The code for that might look like this:
void Main() {
using (var appDbContext = new AppDbContext()) {
var user appDbContext.Users.Include(u => u.Car).ThenInclude(c => c.Trips).SingleOrDefault(u => u.Id == 1);
user.Car.Trips.Clear();
appDbContext.SaveChanges();
}
}Will this code result in the car's trips being cleared?
Let's start by creating the application: dotnet new console. Next we install
the EF Core NuGet package as well as the SQL Server provider, because we will
use LocalDB for the demo. We also add Netwonsoft JSON for pretty-printing
entities.
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Newtonsoft.JsonUncheck other package sources in Visual Studio > Tools > Options > NuGet … in case you are experiencing issues installing the packages from the official package repository.
With the application created, let's create the database:
sqllocaldb create ef_core_include_clear -s. We call it this way so that the
database name matches the generated application namespace name, which itself
is derived from the repository directory name. This way we can use nameof in
the connection string. -s starts the database up after creation.
The application database context class will be really simple:
using Microsoft.EntityFrameworkCore;
public class AppDbContext: DbContext
{
// TODO: Add the DbSet properties here
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer($@"Server=(localdb)\{nameof(ef_core_include_clear)};Database={nameof(ef_core_include_clear)};");
}
}We can try connecting to the database and for good measure we'll make it so that the database is dropped and recreated before each demo run so that the demo is consistent across runs.
static void Main()
{
using (var appDbContext = new AppDbContext())
{
appDbContext.Database.EnsureDeleted();
appDbContext.Database.EnsureCreated();
Console.WriteLine("The database has been reset.");
}
}We can test that the database connect and the application runs without crashing
by issuing the dotnet run command.
Next up we will wire up the model classes and for each of them add a respective
DbSet property to the application database context class.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public Car Car { get; set; }
public int CarId { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Model { get; set; }
public string Make { get; set; }
public ICollection<Trip> Trips { get; set; }
}
public class Trip
{
public int Id { get; set; }
public DateTime DateAndTime { get; set; }
public int DistanceInKilometers { get; set; }
public Car Car { get; set; }
public int CarId { get; set; }
}We also add some seed data in the Main method and add another using block
for the app DB context (so that we get a fresh instance of the change tracker)
in which we print the database contents after a successful save for inspection.
The easiest way to print the database state is to serialize it as JSON. We will
serialize the whole AppDbContext class and we will mark it as
[JsonObject(MemberSerialization.OptIn)] and the DbSet properties as
[JsonProperty] so that no base class properties get serialized - we just care
about data.
Now's the time for the transaction which is the core of this experiment. We'll duplicate the database-printing block and slide this one in between the other two so that we print the database before and after the change.
static void Main()
{
// …pre
using (var appDbContext = new AppDbContext())
{
var user = appDbContext.Users.Include(u => u.Car).ThenInclude(c => c.Trips).SingleOrDefault(u => u.Id == 1);
user.Car.Trips.Clear();
appDbContext.SaveChanges();
}
// …post
}And the code works! Like in the other experiment, we still need to make the
relationships known, we cannot avoid the Include without using SQL directly
(which strips us of the ability to use the in memory database), but this is
confirmed to work.