Reputation: 845
I have an URL field in my table for each of the courses. And i am using it as route parameter. I did this to make Urls user-friendly and as per my understanding this might also help me in SEO ( Please correct me if i am wrong ). With such as setup, i am unable to figure-out how do i create Edit / Delete actions.
Course.cs : The model of the course
public partial class Course
{
public int Id { get; set; }
public string Title { get; set; }
// This is set as Unique Key in the table.
public string Url { get; set; }
public string InnerHtml { get; set; }
}
CourseController.cs : The controller and Edit action for our reference.
[HttpPost("Edit/{courseUrl}")]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrator")]
public async Task<IActionResult> Edit(string courseUrl, [Bind("Id,Title,Url,InnerHtml")] Course course)
{
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(course);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CourseExists(course.Url))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(course);
}
The problem : I am getting the following error on this action
InvalidOperationException: The instance of entity type 'Course' cannot be tracked because another instance with the same key value for {'Id'} 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.
The WorkAround : Commenting the below code in the action, get's the application working. But, the below code is to check if the model being edited is contained in DB.
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id)
{
return NotFound();
}
What's the correct way to handle this scenario ?
Upvotes: 2
Views: 1041
Reputation: 247098
As the error message explains, there is already a model loaded from the search which is being track by the ORM. You need to copy desired properties over to the tracked model if you intend to save it.
//...code removed for brevity
var OriginalCourse = await _context.Courses.SingleOrDefaultAsync(m => m.Url == courseUrl);
if (OriginalCourse.Id != course.Id) {
return NotFound();
}
if (ModelState.IsValid) {
try {
Populate(OriginalCourse, course);
_context.Update(OriginalCourse);
await _context.SaveChangesAsync();
} catch (DbUpdateConcurrencyException) {
if (!CourseExists(course.Url)) {
return NotFound();
} else {
throw;
}
}
return RedirectToAction(nameof(Index));
}
//...code removed for brevity
Where Populate
could look like this
void Populate(Course original, Cource source) {
original.Title = source.Title;
original.Url = source.Url;
original.InnerHtml = source.InnerHtml;
}
Another option would be to not load up an instance by not selecting/returning an item from the context
//...code removed for brevity
var exists = await _context.Courses.AnyAsync(m => m.Url == courseUrl);
if (!exists) {
return NotFound();
}
//...code removed for brevity
and then update the provided course
Upvotes: 2