.net 无法跟踪实体类型的示例,因为已跟踪具有相同键的此类型的另一个示例,该示例ID = {“2”}

cbwuti44  于 2023-06-25  发布在  .NET
关注(0)|答案(1)|浏览(176)

这是我的代码,当我想更新我的featureDisa实体我得到这个错误,我不知道为什么会发生这种情况。有一点是我不能使用AsNoTracking(),因为我需要记录一些东西

var updateFeatureDisas = await _featureDisaRepository.
                GetAll()
                 .Where(a => featureDisaIdInsert.Contains(a.DisadvantageId) &&
                 featureIdInsert.Contains(a.FeatureId)
                 && featureParentIdInsert.Contains(a.ParentFeatureId)).ToListAsync();
    
            var sqInsertFeatureDisa = insertTagDisa.Where(a => updateFeatureDisas.All(d => d.Id != a.Id)).ToList();
    
    
    
            if (updateFeatureDisas.Any())
            {
                var sqUpdateFeatureDisa = insertTagDisa.Where(a => updateFeatureDisas.
                    All(d => d.Id.Equals(a.Id, StringComparison.Ordinal))).ToList();
                var updateFeatureDisa = _mapper.Map<List<FeatureDisa>>(sqUpdateFeatureDisa);
                _featureDisaRepository.UpdateRangeWithOutSaveChangeAsync(updateFeatureDisa);
            }

这是我的UpdateWithOutSaveChangeAsync

public void UpdateWithOutSaveChangeAsync(TEntity entity)
{
    DbContext.Update(entity);
}
46scxncf

46scxncf1#

EF更改跟踪与引用一起工作。从DbContext加载实体时,默认情况下EF会跟踪该引用。如果您稍后创建具有相同ID的实体的另一个副本,并告诉DbContext Update该行,EF将首先检查其跟踪列表,如果发现已经在跟踪具有相同ID的实体的另一个副本,则抛出此错误。
这段代码:

var updateFeatureDisa = _mapper.Map<List<FeatureDisa>>(sqUpdateFeatureDisa);

是你的问题Automapper正在构造新的FeatureDisa实体示例,您正试图告诉EF更新行,但您已经读取并跟踪了实体。
解决方案1:告诉EF在加载特征时不要跟踪引用。如果您的Repository方法返回IQueryable,并且在查询尚未在Repository中具体化的情况下正确执行了此操作,那么:

var updateFeatureDisas = await _featureDisaRepository.GetAll()
    .AsNoTracking() // <- Just add this...
    .Where(a => featureDisaIdInsert.Contains(a.DisadvantageId) 
        && featureIdInsert.Contains(a.FeatureId)
        && featureParentIdInsert.Contains(a.ParentFeatureId))
    .ToListAsync();

这个*应该起作用,但有一个很大的警告要注意。当处理注入的DbContext时,这一个语句将加载所请求的FeatureDisas而不将它们添加到跟踪缓存中,但是在该DbContext的生命周期范围内的其他代码/调用(即请求)可以加载并跟踪一个或多个FeatureDisa实体。如果有人不小心并意识到这种依赖性,这肯定会有间歇性异常或破坏性更改的风险。
选项1a:为了确保上面的代码是“安全的”,我们需要确保在调用“Update”之前DbContext没有跟踪任何示例。如果是的话,让它分离。最好在UpdateWithoutSaveChangesAsync方法中这样做,但是作为泛型方法,它将无法工作,因为我们需要按ID查找跟踪缓存中的任何现有项。

public void UpdateWithOutSaveChangeAsync(TEntity entity)
{
    var existingEntity = DbContext.DbSet<TEntity>().Local.FirstOrDefault(x => x.Id == entity.Id); // Doesn't work due to Generic implementation
    if (existingEntity != null)
        dbContext.Entry(existingEntity).State = EntityState.Detached;
    DbContext.Update(entity);
}

不幸的是,代码不能工作,因为我们不能在泛型中这样做,除非我们有一个公共的基类型来访问我们可以强制转换到的ID。因此,在调用Update之前,必须在FeatureDisaRepository. UpdateRangeWithOutSaveChangeAsync()方法中完成:
假设UpdateRangeWithoutSaveChangeAsync本身是一个泛型实现:类似于:

public async Task UpdateRangeWithOutSaveChangeAsync(List<FeatureDisa> featureDisas)
 {
      var featureDisaIds = featureDisas.Select(x => x.Id).ToList();
      var trackedFeatureDisas = DbContext.FeatureDisas.Local
           .Where(x => featureDisaIds.Contains(x.Id))
           .ToList();

      foreach(var trackedFeatureDisa in trackedFeatureDisas)
          DbContext.Entry(trackedFeatureDisa).State = EntityState.Detached;

      return base.UpdateRangeWithOutSaveChangeAsync(featureDisas); // hand off to the Generic method to do the work.
}

这基本上是从我的头上跳出来的,取决于您的存储库是如何实现的,由于泛型模式,它可能会变得更加混乱。(出于这样的原因,我真的不推荐使用通用存储库/w EF)
选项2:幸运的是,有一个比Automapper可以提供的所有选项更好的选项。Automapper可以在引用之间复制值,因此保留原始读取调用加载跟踪的引用,而不是:

var updateFeatureDisa = _mapper.Map<List<FeatureDisa>>(sqUpdateFeatureDisa);

使用automapper的copy mapping方法调用:

foreach(var updated in sqUpdateFeatureDisa)
{
    var existingFeatureDisa = updateFeatureDisas.FirstOrDefault(x => x.Id = updated.Id);
    if (existingFeatureDisa == null) continue; // What else to do if we don't have a matching existing row?
    _mapper.Map(updated, existingFeatureDisa); // copy values from dto to tracked entity.
}

不需要在存储库中调用"Update"方法,剩下的就是等待DbContext.SaveChanges()调用,修改后的FeatureDisa示例将被持久化。请注意,在这个选项中,我们***不想***将AsNoTracking()添加到初始查询中以获取updateFeatureDisa集合。我们想要跟踪参考,这样我们就可以让EF做它的事情。
Automapper关于复制Mapper. map调用的文档在过去相当缺乏。它对于像这样的更新操作非常有用。
您可以添加到此选项的唯一其他详细信息是,在从导入DTO到FeatureDisaMap设置Automapper配置时,将其配置为仅复制您打算允许更改的值。目前,您正在使用Map器从DTO构建新的FeatureDisa,但由于我们正在更新跟踪的加载记录,因此您可以更悲观地保护更新,仅允许您打算更新的值,以保护数据免受潜在篡改数据的影响。这意味着您的更新DTO可以简化为仅反映可以/应该更改的值,因为您不再需要将构建完整FeatureDisa记录所需的所有字段传递给Update

相关问题