I have this bookmark that has a relation with a category, so every time I update an existing bookmark, a new category should be created or updated if it already exists. When I update an existing bookmark I get this error:
The instance of entity type 'Bookmark' 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.
My Entity
looks like this:
public class Bookmark
{
[Key]
public int ID { get; set; }
[StringLength(maximumLength: 500)]
public string URL { get; set; }
public string ShortDescription { get; set; }
public int? CategoryId { get; set; }
public virtual Category Category { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime CreateDate { get; set; }
}
My Update
Controller looks like this:
public async Task<IActionResult> Edit(BookmarkViewModel request)
{
var bookmark = _mapper.Map<Bookmark>(request);
var exists = await _bookmarkService.GetBookmark(bookmark.ID);
if(exists == null)
return new StatusCodeResult(Microsoft.AspNetCore.Http.StatusCodes.Status404NotFound);
if (ModelState.IsValid)
{
if (request.CategoryName != null)
{
var category = _categoryService.CreateCategory(new Category
{
Name = request.CategoryName
});
bookmark.Category = category;
bookmark.CategoryId = category.ID;
}
await _bookmarkService.UpdateBookmark(bookmark);
return RedirectToAction("Index");
}
return View(bookmark);
}
My CreateCategory
service looks like this:
public Category CreateCategory(Category category)
{
category.UserId = _identityService.GetUserId();
var existing = _ReadLaterDataContext.Categories
.FirstOrDefault(x => x.Name.Equals(category.Name) && x.UserId.Equals(category.UserId));
if (existing != null)
return existing;
_ReadLaterDataContext.Add(category);
_ReadLaterDataContext.SaveChanges();
return category;
}
And my UpdateBookmark
service looks like this:
public async Task UpdateBookmark(Bookmark bookmark)
{
bookmark.CreateDate = DateTime.Now;
UpdateExistingCategory(bookmark);
_ReadLaterDataContext.Update(bookmark);
await _ReadLaterDataContext.SaveChangesAsync();
}
My GetBookmark()
method
public async Task<Bookmark> GetBookmark(int Id)
{
return await _ReadLaterDataContext.Bookmark.Where(x => x.ID == Id && x.UserId.Equals(_identityService.GetUserId())).Include(x => x.Category).FirstOrDefaultAsync();
}
The problem is here:
await _bookmarkService.UpdateBookmark(bookmark);
This is causing an Identity Resolution violation. Here's an excerpt from the link(with added emphasis):
A DbContext can only track one entity instance with any given primary key value. This means multiple instances of an entity with the same key value must be resolved to a single instance.
The change tracker begins to track the Bookmark
soon after the execution of this statement:
var exists = await _bookmarkService.GetBookmark(bookmark.ID);
Change tracking is a feature of EF Core wherein any changes made to an entity is tracked after it is fetched from the database. All these changes will be applied as soon as you call context.SaveChanges()
.
Now, if EF Core sees any other Bookmark
instance other than the instance in exists
with the same primary key, it throws an error. In your case:
var bookmark = _mapper.Map<Bookmark>(request);
In the above statement, bookmark
is another instance of Bookmark
with the same primary key( Bookmark.ID
). This ID is being set from the post request during model binding. So, in order to get around this, you have to either disable change tracking using AsNoTracking()
:
public async Task<Bookmark> GetBookmark(int Id)
=> await _ReadLaterDataContext.Bookmark
.AsNoTracking()
.Where(x => x.ID == Id
&& x.UserId.Equals(_identityService.GetUserId()))
.Include(x => x.Category)
.FirstOrDefaultAsync();
Or, the better option would be to make changes in the same instance that was fetched from the database(in your case, exists
). You are also assigning the newly created category
to bookmark.Category
without checking if the Bookmark
already contains another category. Check if the bookmark already contains a category, then change the name of this category if it exists and create a new one only if there is no existing category.
It also appears that you do not need the mapped bookmark, so I've completely gotten rid of it and renamed exists
to bookmark
. I've assumed that the request
contains a property ID
. Use the property that contains the bookmark ID:
public async Task<IActionResult> Edit(BookmarkViewModel request)
{
var bookmark = await _bookmarkService.GetBookmark(request.ID);
if (bookmark == null)
return new StatusCodeResult(Microsoft.AspNetCore.Http.StatusCodes.Status404NotFound);
if (ModelState.IsValid)
{
if (request.CategoryName is not null)
{
if (bookmark.Category is not null)
{
bookmark.Category.Name = request.CategoryName;
}
else
{
var category = _categoryService.CreateCategory(new Category
{
Name = request.CategoryName
});
bookmark.Category = category;
bookmark.CategoryId = category.ID;
}
}
await _bookmarkService.UpdateBookmark(bookmark);
return RedirectToAction("Index");
}
return View(request);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.