我正在构建一个电子商务,更确切地说是购物车,并尝试在删除购物车时实现添加/删除项目的会话。我目前正在使用 ASP.NET Core MVC 和 EF Core 7.0.10。我做了一些关于实体跟踪器、如何正确删除、更新项目 ecc 的研究。但尽管如此,我在实体跟踪器方面遇到了这个问题。这是以下错误:
InvalidOperationException:无法跟踪实体类型“ShoppingCart”的实例,因为已跟踪具有相同键值 {'Id'} 的另一个实例。
我读到未跟踪的实体是优化的更好原因。在此代码中,我已经应用了 asNoTracking() 方法,因此不会跟踪它们。下面我展示了控制器的操作方法中使用的存储库代码。
public IEnumerable<T> GetAll(
Expression<Func<T, bool>>? filter = null,
string? includeProperties = null
)
{
IQueryable<T> query = _dbSet;
// Here i apply a X filter
if (filter != null)
{
query = query.Where(filter);
}
// Here i can populate properties
if (!string.IsNullOrEmpty(includeProperties))
{
foreach (
var property in includeProperties
.Split(
new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries)
)
{
query = query.Include(property);
}
}
return query.ToList();
}
GetAll() 方法与 Get() 方法相同(显然也带有 asNoTracking())
我还读到,要删除与内容分离的实体,我必须首先将分离的实体附加到上下文,然后将该实体标记为“已删除”:以下代码:
_dbSet.Attach(entity).State = EntityState.Deleted;
我已经在其他代码上尝试过这个“策略”,它有效,但在这种情况下不起作用。我不明白原因。我尝试使用 _context 而不是使用 _unitOfWork 编写所有操作,并且它有效。怎么可能?使用 _context 和 _unitOfWork 有什么区别?
这是ShoppingCart控制器的操作方法。
public IActionResult Remove(int cartId)
{
ShoppingCart? cart = _unitOfWork.ShoppingCartRepo.Get(x => x.Id == cartId);
if (cart == null) return NotFound("Cart not found in Remove(int cartId)");
// Before saving i set new value for the session
HttpContext.Session.SetInt32(
key: StaticDetails.SessionCart,
value: _unitOfWork.ShoppingCartRepo.GetAll
(filter: x => x.ApplicationUserId == cart.ApplicationUserId)
.Count() - 1
);
_unitOfWork.ShoppingCartRepo.Remove(cart);
_unitOfWork.Save();
return RedirectToAction(nameof(Index));
}
这是其存储库中remove()的实现。
public void Remove(T entity, bool entityStateDeleted = true)
{
if (entityStateDeleted)
{
_dbSet.Attach(entity).State = EntityState.Deleted;
}
else
{
_dbSet.Remove(entity);
}
}
GetAll() 方法与 Get() 方法相同(显然也带有 AsNoTracking())
方法
Get
返回一个未被跟踪的对象。如果我重新表述该方法Remove(int cartId)
,例如:
IActionResult Remove(int cartId) // For the exemple, cartId is 42
{
ShoppingCart? cart = _dbSet.AsNoTracking().FirstOrDefault(x => x.Id == cartId);
// The shopping cart 42 is loaded in memomy, but not tracked
List<ShoppingCart> userShoppingCarts = _dbSet.Where(x => x.ApplicationUserId == cart.ApplicationUserId).ToList();
// All user shopping cart are loaded in memory with tracking.
// Then the shoshopping cart 42 is traked, but from a other object than that is referenced by the variable cart
ShoppingCart cartDuplicated = userShoppingCarts.First(c => c.Id == cart.Id);
Object.ReferenceEqual(cart, cartDuplicated); // False
int userShoppingCartsCount = userShoppingCarts.ToList();
// The error come from :
_dbSet.Attach(cart)
// Throw the error :
// InvalidOperationException: The instance of entity type 'ShoppingCart' cannot be tracked because
// another instance with the same key value for {'Id'} is already being tracked.
// Translated :
// cart cannot be tracked because cartDuplicated with the id 42 is already being tracked.
...
}
解决方案是反转
AsNoTracking
的位置。
将
AsNoTracking
从 Get
移至 GetAll
:
public IEnumerable<T> GetAll(...)
{
IQueryable<T> query = _dbSet.AsNoTacking();
...
}
然后您只需删除:
public void Remove(T entity)
{
_dbSet.Remove(entity);
}
备注:统计使用购物车时,所有用户购物车都会加载到内存中。实在是效率太低了。我建议你做类似的事情:
var count = _dbSet.Where(x => x.ApplicationUserId == cart.ApplicationUserId).Count();
因此,只有计数值被加载到内存中。为此,您可以在存储库中添加
Count
方法。