Praveen Rai
Praveen Rai

Reputation: 845

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 ?

Upvotes: 2

Views: 1041

Answers (1)

Nkosi
Nkosi

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

Related Questions