static async Task Main()
        {
            using var context = new CarDataContext();

            /*await CleanDatabaseAsync(context);
             * await FillCarMakeAsync(context);
             * await FillCarModelsAsync(context);
             * await FillPersonsAsync(context);*/

            await QueryCarOwnerships(context);
            await QueryNotAssignedOwnerships(context);
        }
        private static async Task FillPersonsAsync(CarDataContext context)
        {
            // Note that we are jus reading primary keys of books, not entire
            // book records. Tip: Always read only those columns that you REALLY need.
            var modelIds = await context.CarModels.Select(m => m.CarModelId).ToArrayAsync();

            var personNames = JsonSerializer.Deserialize <IEnumerable <FirstnameLastname> >(
                await File.ReadAllTextAsync("Data/mockPeople.json"));

            List <Person> persons = new List <Person>();

            foreach (var personName in personNames)
            {
                var person = new Person {
                    Firstname = personName.Firstname, Lastname = personName.Lastname
                };
                persons.Add(person);
            }


            using var transaction = context.Database.BeginTransaction();

            var rand = new Random();

            foreach (var person in persons)
            {
                var dbPerson = new Person
                {
                    Firstname = person.Firstname,
                    Lastname  = person.Lastname
                };

                // Randomly assign each author one book.
                // Note that we can use the dbAuthor, although we have not yet written
                // it to the database. Also note that we are using the book ID as a
                // foreign key.
                var dbOwnership = new Ownership
                {
                    Person     = dbPerson,
                    CarModelId = modelIds[rand.Next(modelIds.Length)],
                    VehicleIdentificationNumber = RandomString()
                };

                // Note that we do NOT need to add dbAuthor. It is referenced by
                // dbBookAuthor, that is enough.
                context.Ownerships.Add(dbOwnership);
            }

            await context.SaveChangesAsync();

            await transaction.CommitAsync();
        }
        static async Task QueryNotAssignedOwnerships(CarDataContext context)
        {
            var carModels = (await context
                             .CarModels
                             .Include(m => m.Owners)
                             .Include(m => m.CarMake)
                             .Where(m => m.Owners.Count() <= 0)
                             .ToListAsync());

            carModels.Sort((a, b) => a.Model.CompareTo(b.Model));

            Console.WriteLine("Unassigned Ownerships: ");
            foreach (CarModel carModel in carModels)
            {
                Console.WriteLine(carModel.CarMake.Make + " " + carModel.Model);
            }
        }
        static async Task QueryCarOwnerships(CarDataContext context)
        {
            // Note Include method to query books and genre with a single query.
            var carModels = (await context
                             .CarModels
                             .Include(m => m.Owners)
                             .Include(m => m.CarMake)
                             .ToListAsync());

            carModels.Sort((a, b) => a.Model.CompareTo(b.Model));

            Console.WriteLine("Ownerships: ");
            foreach (CarModel carModel in carModels)
            {
                Console.WriteLine(carModel.CarMake.Make + " " + carModel.Model + ":\t" + carModel.Owners.Count);
            }
        }
        static async Task CleanDatabaseAsync(CarDataContext context)
        {
            // Note that we are using a DB transaction here. Either all records are
            // inserted or none of them (A in ACID).
            using var transaction = context.Database.BeginTransaction();

            // Note that we are using a "Raw" SQL statement here. With that, we can use
            // all features of the underlying database. We are not limited to what EFCore
            // can do.
            await context.Database.ExecuteSqlRawAsync("DELETE FROM CarMakes");

            await context.Database.ExecuteSqlRawAsync("DELETE FROM CarModels");

            await context.Database.ExecuteSqlRawAsync("DELETE FROM Ownerships");

            await context.Database.ExecuteSqlRawAsync("DELETE FROM Persons");

            await transaction.CommitAsync();
        }
        private static async Task FillCarMakeAsync(CarDataContext context)
        {
            var makeData = await File.ReadAllTextAsync("Data/mockCarMakes.json");

            var            makeStrings = JsonSerializer.Deserialize <IEnumerable <MakeHolder> >(makeData);
            List <CarMake> makes       = new List <CarMake>();

            foreach (var makeName in makeStrings)
            {
                var make = new CarMake {
                    Make = makeName.Make
                };
                makes.Add(make);
            }

            using var transaction = context.Database.BeginTransaction();

            // Note how we combine transaction with exception handling
            try
            {
                // Note that we add all genre data rows BEFORE calling SaveChanges.
                foreach (var m in makes)
                {
                    context.CarMakes.Add(m);
                }

                await context.SaveChangesAsync();

                // Commit transaction if all commands succeed, transaction will auto-rollback
                // when disposed if either commands fails
                await transaction.CommitAsync();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Something bad happened: {ex.Message}");

                // Note re-throw of exception
                throw;
            }
        }
        private static async Task FillCarModelsAsync(CarDataContext context)
        {
            // Demonstrate tracking queries here. Set a breakpoint up in
            // FillGenreAsync when genre rows are added. Afterwards, show that
            // the query returns THE SAME objects because of identical primary keys.
            var make = await context.CarMakes.ToArrayAsync();

            var modelStrings = JsonSerializer.Deserialize <IEnumerable <ModelHolder> >(
                await File.ReadAllTextAsync("Data/mockCarModels.json"));

            List <CarModel> models = new List <CarModel>();

            foreach (var modelName in modelStrings)
            {
                var model = new CarModel {
                    Model = modelName.Model
                };
                models.Add(model);
            }

            using var transaction = context.Database.BeginTransaction();

            var rand = new Random();

            foreach (var model in models)
            {
                var dbModel = new CarModel
                {
                    CarMakeId = make[rand.Next(make.Length)].CarMakeId,
                    Model     = model.Model
                };
                context.CarModels.Add(dbModel);
            }

            await context.SaveChangesAsync();

            await transaction.CommitAsync();
        }