Edit Action in ASP.Net Core controller using another field

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 ?

1 answer

  • answered 2018-04-17 04:11 Nkosi

    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