0
votes

I just started with asp.net core MVC by developing a Car Rental application and I'm using EF Core for working with the database. The problems comes when I'm trying to update a record from database. It gives me the folloing exception:

InvalidOperationException: The instance of entity type 'Car' cannot be tracked because another instance with the same key value for {'CarID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Here's my code for updating a car:

Repository:

public Car GetCarByID(int carId)
{
    return _context.Cars.Where(c=> c.CarID == carId).Include(c=>c.Category)
                                                    .Include(c=>c.Type)
                                                   .Include(c=>c.CarLocations).ThenInclude(c=>c.Location)
                                                   .FirstOrDefault();
}
 public void UpdateCar(Car car)
 {
     _context.Update(car);
 }

Controller car update code:

 [HttpGet]
        public async Task<IActionResult> Edit(int id)
        {
            await SeedCarView();

            var carToEdit = _carsRepo.GetCarByID(id);

            if (carToEdit == null)
            {
                return NotFound();
            }

            return View(carToEdit);
        }
[HttpPost]
        public async Task<IActionResult> Edit(Car car)
        {
            await SeedCarView();

            if (ModelState.IsValid)
            {
                /*get selections from dropdown form*/
                string categSelection = Request.Form["Category_Name"].ToString();
                string bodySelection = Request.Form["TypeName"].ToString();
                var selectedLocations = Request.Form["AreChecked"].ToList();

                var selectedCategory = await _categoriesRepo.GetCategoryByIDAsync(categSelection);
                var selectedBodyType = await _typesRepo.GetBodyTypeByNameAsync(bodySelection);

                car.Category = selectedCategory;
                /*Increment the number of cars in this category*/
                car.Category.NumberOfCars = car.Category.Cars.Count + 1;
                car.Type = selectedBodyType;

                foreach (var sel in selectedLocations)
                {
                    CarLocation carLocation = new CarLocation
                    {
                        Car = car
                    };

                    var location = _locationsRepo.GetLocationByID(Int32.Parse(sel));
                    carLocation.Location = location;

                    car.CarLocations.Add(carLocation);
                }

                _carsRepo.UpdateCar(car);
                await _carsRepo.SaveAsync();

                return RedirectToAction("Listing");
            }

            return View(car);
        }

private async Task SeedCarView()
        {
            var categories = await _categoriesRepo.GetCategoriesAsync();
            var bodyTypes = await _typesRepo.GetBodyTypesAsync();
            var locations = await _locationsRepo.GetLocationsAsync();

            List<string> categorieNames = new List<string>();
            List<string> typeNames = new List<string>();

            /*get the existing categories name and body types name*/
            foreach (var item in categories)
                categorieNames.Add(item.Category_Name);
            foreach (var item in bodyTypes)
                typeNames.Add(item.Name);

            ViewBag.Category_Names = categorieNames;
            ViewBag.TypeNames = typeNames;
            ViewBag.LocationNames = locations;
        }

Here's the Service Configuration methode:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddDbContext<CarRentalContext>(item => item.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddScoped<ICarsRepository, CarsRepository>();
            services.AddScoped<ICarCategoryRepository, CarCategoryRepository>();
            services.AddScoped<ICarTypeRepository, CarTypeRepository>();
            services.AddScoped<ILocationsRepository, LocationsRepository>();
        }

Exception at Update methode

Please help me to fix this issue. I'm stuck at this for more than 2 days and I don't understand where's the problem.

Update:

I've changed my Update methode in Repository with the following code:

 public void UpdateCar(Car car)
        {
            var existingCar = GetCarByID(car.CarID);
            _context.Entry(existingCar).CurrentValues.SetValues(car);

            //_context.Update(car);
        }

and the code in the POST action:

    foreach (var sel in selectedLocations)
                {
                    var location = _locationsRepo.GetLocationByID(Int32.Parse(sel));

                    CarLocation carLocation = new CarLocation
                    {
                        CarID = car.CarID,
                        LocationID = location.Location_ID,
                        Car = car,
                        Location = location
                    };

                    car.CarLocations.Add(carLocation);
                }

but now only the car instance is updated, without childs like CarCategory, Locations and CarType.

1
The way you did it you should not use context.Update(), you just need to update whatever you need and then do SaveChanges(). Reason being - when you getting data using EF, by default it is tracking the instances for changes - that's exactly what it is saying to you, when you do .Update(), all it does - it internally adds object for tracking into EF, but you already have same object tracked cause you got it already without NoTracking option - Nikita Chayka
I tried to get the instance with AsNoTracking() but it gives me the same exception. - John
Currently this is definately the problem i see, so could you please either remove your call to Update and do SaveChanges() or do AsNoTracking and update your code with exact error again. Other possibility here- how exactly you creating context? Is it PerRequest mode in dependency cotainer? Or you creating it somehow else ? Please provide here the details - Nikita Chayka
When I'm removing the Update call, nothing happens, the record is not updated and if I add the AsNoTracking I'm getting the same exception. I will add the service configuration in the question imediatly - John
Hm, interesting, can you just for experiment then comment out this line - car.CarLocations.Add(carLocation). - Nikita Chayka

1 Answers

0
votes

I solved the the exception by creating a new instance of the Car, cleaning its old data and adding the changed properties.

public async Task<IActionResult> Edit(Car car)
        {
            await SeedCarView();

            if (ModelState.IsValid)
            {
                /*get selections from dropdown form*/
                string categSelection = Request.Form["Category_Name"].ToString();
                string bodySelection = Request.Form["TypeName"].ToString();
                var selectedLocations = Request.Form["AreChecked"].ToList();

                var selectedCategory = await _categoriesRepo.GetCategoryByIDAsync(categSelection);
                var selectedBodyType = await _typesRepo.GetBodyTypeByNameAsync(bodySelection);

                var carToBeUpdated = _carsRepo.GetCarByID(car.CarID);
                carToBeUpdated.CarLocations.Clear();

                foreach (var sel in selectedLocations)
                {
                    var location = _locationsRepo.GetLocationByID(Int32.Parse(sel));

                    CarLocation carLocation = new CarLocation
                    {
                        CarID= car.CarID,
                        LocationID = location.Location_ID,
                        Car = car,
                        Location = location
                    };

                    carToBeUpdated.CarLocations.Add(carLocation);
                }

                carToBeUpdated.Category = selectedCategory;
                carToBeUpdated.CategoryName = categSelection;
                /*Increment the number of cars in this category*/
                carToBeUpdated.Category.NumberOfCars = carToBeUpdated.Category.Cars.Count + 1;
                carToBeUpdated.Type = selectedBodyType;
                carToBeUpdated.TypeID = selectedBodyType.TypeID;
                carToBeUpdated.Acceleration = car.Acceleration;
                carToBeUpdated.ClimateControll = car.ClimateControll;
                carToBeUpdated.Fabrication_Date = car.Fabrication_Date;
                carToBeUpdated.FuelType = car.FuelType;
                carToBeUpdated.Model = car.Model;
                carToBeUpdated.PictureURL = car.PictureURL;
                carToBeUpdated.TransmisionType = car.TransmisionType;
                carToBeUpdated.PricePerDay = car.PricePerDay;

                //_carsRepo.UpdateCar(carToBeUpdated);
                await _carsRepo.SaveAsync();

                return RedirectToAction("Listing");
            }

            return View(car);
        }