实体类型 Model 的实例无法被跟踪,因为已经有另一个具有相同键值 {'Id'} 的实例正在被跟踪。

4

我有一个问题,当我在数据库中更新时,出现了以下异常:

实体类型“ExpenseReport”的实例无法跟踪,因为已经跟踪了另一个具有相同键值({'Id'})的实例。在附加现有实体时,请确保只附加了一个具有给定键值的实体实例。考虑使用'DbContextOptionsBuilder.EnableSensitiveDataLogging'查看冲突的键值。

这是我的更新方法:

      public async Task UpdateExpenseReportForm(Guid ExpenseReportId)
        {
            var totalValue =   _uow.GetReadRepository<ExpenseItem>().FindByCondition(x => x.ExpenseReportId.Equals(ExpenseReportId)).Sum(x => x.Value);

            var expenseReprot = await _uow.GetReadRepository<ExpenseReport>().FindByCondition(x => x.Id.Equals(ExpenseReportId)).FirstOrDefaultAsync().ConfigureAwait(false);
            expenseReprot.TotalValue = totalValue - expenseReprot.AdvanceValue;
            _uow.GetWriteRepository<ExpenseReport>().Update(expenseReprot);
            await _uow.CommitAsync();

        }

一个重要的细节是,在这个方法中 _uow.GetReadRepository <ExpenseReport> (),我已经使用了 AsNoTracking 来不映射它。
这些是获取和更新“存储库动态”的方法。
  public void Update(T entity)
        {
            _dbSet.Update(entity);
        }

 public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression)
        {
            return _dbSet.Where(expression).AsNoTracking();
        }
1个回答

5

由于错误信息指示实体已经被从先前的查询中进行跟踪,因此您不需要调用_dbSet.Update

尝试通过从FindByCondition方法中移除"AsNoTracking"语句,并在"Update"方法中简单地调用保存来解决问题:

public void Update(T entity)
{
    _dbContext.SaveChanges();
}

public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression)
{
    return _dbSet.Where(expression);
}

这里有一个很好的通用仓储模式实现,您可能希望重复使用:

public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    /// <summary>
    /// The context object for the database
    /// </summary>
    private DbContext _context;

    /// <summary>
    /// The IObjectSet that represents the current entity.
    /// </summary>
    private DbSet<TEntity> _dbSet;

    /// <summary>
    /// Initializes a new instance of the GenericRepository class
    /// </summary>
    /// <param name="context">The Entity Framework ObjectContext</param>
    public GenericRepository(DbContext context)
    {
        _context = context;
        _dbSet = _context.Set<TEntity>();
    }

    /// <summary>
    /// Gets all records as an IQueryable
    /// </summary>
    /// <returns>An IQueryable object containing the results of the query</returns>
    public IQueryable<TEntity> GetQuery()
    {
        return _dbSet;
    }

    /// <summary>
    /// Gets all records as an IQueryable and disables entity tracking
    /// </summary>
    /// <returns>An IQueryable object containing the results of the query</returns>
    public IQueryable<TEntity> AsNoTracking()
    {
        return _dbSet.AsNoTracking<TEntity>();
    }

    /// <summary>
    /// Gets all records as an IEnumerable
    /// </summary>
    /// <returns>An IEnumerable object containing the results of the query</returns>
    public IEnumerable<TEntity> GetAll()
    {
        return GetQuery().AsEnumerable();
    }

    /// <summary>
    /// Finds a record with the specified criteria
    /// </summary>
    /// <param name="predicate">Criteria to match on</param>
    /// <returns>A collection containing the results of the query</returns>
    public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Where<TEntity>(predicate);
    }

    public Task<TEntity> FindAsync(params object[] keyValues)
    {
        return _dbSet.FindAsync(keyValues);
    }

    /// <summary>
    /// Gets a single record by the specified criteria (usually the unique identifier)
    /// </summary>
    /// <param name="predicate">Criteria to match on</param>
    /// <returns>A single record that matches the specified criteria</returns>
    public TEntity Single(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Single<TEntity>(predicate);
    }

    /// <summary>
    /// The first record matching the specified criteria
    /// </summary>
    /// <param name="predicate">Criteria to match on</param>
    /// <returns>A single record containing the first record matching the specified criteria</returns>
    public TEntity First(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.First<TEntity>(predicate);
    }

    /// <summary>
    /// The first record matching the specified criteria or null if not found
    /// </summary>
    /// <param name="predicate">Criteria to match on</param>
    /// <returns>A single record containing the first record matching the specified criteria or a null object if nothing was found</returns>
    public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.FirstOrDefault<TEntity>(predicate);
    }

    /// <summary>
    /// Deletes the specified entitiy
    /// </summary>
    /// <param name="entity">Entity to delete</param>
    /// <exception cref="ArgumentNullException"> if <paramref name="entity"/> is null</exception>
    public void Delete(TEntity entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }
        _dbSet.Remove(entity);
    }

    /// <summary>
    /// Adds the specified entity
    /// </summary>
    /// <param name="entity">Entity to add</param>
    /// <exception cref="ArgumentNullException"> if <paramref name="entity"/> is null</exception>
    public void Add(TEntity entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }
        _dbSet.Add(entity);
    }


    /// <summary>
    /// Attaches the specified entity
    /// </summary>
    /// <param name="entity">Entity to attach</param>
    public void Attach(TEntity entity)
    {
        _dbSet.Attach(entity);
    }

    /// <summary>
    /// Detaches the specified entity
    /// </summary>
    /// <param name="entity">Entity to attach</param>
    public void Detach(TEntity entity)
    {
        _context.Entry(entity).State = EntityState.Detached;
    }

    public void MarkModified(TEntity entity)
    {
        _context.Entry(entity).State = EntityState.Modified;
    }

    public DbEntityEntry<TEntity> GetEntry(TEntity entity)
    {
        return _context.Entry(entity);
    }

    /// <summary>
    /// Saves all context changes
    /// </summary>
    public void SaveChanges()
    {
        _context.SaveChanges();
    }

    /// <summary>
    /// Releases all resources used by the WarrantManagement.DataExtract.Dal.ReportDataBase
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases all resources used by the WarrantManagement.DataExtract.Dal.ReportDataBase
    /// </summary>
    /// <param name="disposing">A boolean value indicating whether or not to dispose managed resources</param>
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_context != null)
            {

                _context.Dispose();

                _context = null;

            }
        }
    }
}

这是接口:

public interface IRepository<TEntity> : IDisposable where TEntity : class
{
    IQueryable<TEntity> GetQuery();
    IEnumerable<TEntity> GetAll();
    IQueryable<TEntity> AsNoTracking();
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
    TEntity Single(Expression<Func<TEntity, bool>> predicate);
    TEntity First(Expression<Func<TEntity, bool>> predicate);
    TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);
    void Add(TEntity entity);
    void Delete(TEntity entity);
    void Attach(TEntity entity);
    void Detach(TEntity entity);
    void MarkModified(TEntity entity);
    void SaveChanges();
}

请注意,只有在实体未被跟踪时才需要调用 "Attach" 或 "MarkModified",在大多数情况下,您可以简单地查询一个已被跟踪的实体,修改一些属性,然后调用 "SaveChanges"。
您还可以将存储库与工作单元组合使用,以便更好地控制事务等内容。以下是一个示例:
public class UnitOfWork : IUnitOfWork
{
    private readonly YouDatabaseContext _context = new YouDatabaseContext();
    private DbContextTransaction _dbContextTransaction;
    private GenericRepository<ExpenseReport> _expenseReportRepository;
    private GenericRepository<ExpenseItem> _expenseItemRepository;

    public GenericRepository<ExpenseReport> ExpenseReportRepository
    {
        get
        {
            if (_expenseReportRepository == null)
            {
                _expenseReportRepository = new GenericRepository<ExpenseReport>(_context);
            }
            return _expenseReportRepository;
        }

        set
        {
            _expenseReportRepository = value;
        }
    }
    
    public GenericRepository<ExpenseItem> ExpenseItemRepository
    {
        get
        {
            if (_expenseItemRepository == null)
            {
                _expenseItemRepository = new GenericRepository<ExpenseItem>(_context);
            }
            return _expenseItemRepository;
        }

        set
        {
            _expenseItemRepository = value;
        }
    }

    public void BeginTransaction()
    {
        _dbContextTransaction = _context.Database.BeginTransaction();
    }

    public void BeginTransaction(IsolationLevel isolationLevel)
    {
        _dbContextTransaction = _context.Database.BeginTransaction(isolationLevel);
    }

    public int Save()
    {
        return _context.SaveChanges();
    }

    public Task<int> SaveAsync()
    {
        return _context.SaveChangesAsync();
    }

    public void Commit()
    {
        if (_dbContextTransaction!=null)
        {
            _dbContextTransaction.Commit();
        }
    }

    public void RollBack()
    {
        if (_dbContextTransaction != null)
        {
            _dbContextTransaction.Rollback();
        }
    }

    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
                _dbContextTransaction?.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

接口如下:

public interface IUnitOfWork : IDisposable
{
    void BeginTransaction();
    void BeginTransaction(IsolationLevel isolationLevel);
    int Save();
}

工作得非常完美。但我不明白为什么(AsNoTracking)不起作用。它不会使这个映射发生吗? - Eliemerson Fonseca
那应该可以工作,也许你之前有其他查询或外键的问题?我还添加了与仓储库一起使用的工作单元实现,如果您感兴趣,我也可以发布它。 - Isma
是的,我有兴趣。 - Eliemerson Fonseca
我已将其添加到问题中。谢谢!祝编码愉快。 - Isma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接