The instance of entity type Model cannot be tracke

2020-07-22 19:52发布

I have a problem, when will I do update in my database, I have this exception.

The instance of entity type 'ExpenseReport' 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. tracked already

This is my method to do an update.

      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();

        }

An important detail is that in this method _uow.GetReadRepository <ExpenseReport> () I'm already using AsNoTracking to not map it

These are methods that do get and update"repository dynamic"

  public void Update(T entity)
        {
            _dbSet.Update(entity);
        }

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

1条回答
▲ chillily
2楼-- · 2020-07-22 20:15

You don't need to call _dbSet.Update because as the error message indicates the entity is already being tracked from your previous query.

Try by removing the statement "AsNoTracking" from the FindByCondition method and simply call save in the "Update" method:

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

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

Here is a nice generic implementation of the repository pattern that you may want to reuse:

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 IEnumberable
    /// </summary>
    /// <returns>An IEnumberable 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;

            }
        }
    }
}

Here is the interface:

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();
}

Note that you only need to call "Attach" or "MarkModified" if the entity is not being tracked, in most scenarios you can simply make a query, modify some properties of a tracked entity and then call SaveChanges.

You can also combine the repositories with a unit of work so you can have more control over transactions, etc... here is an example:

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);
    }
}

And the interface:

public interface IUnitOfWork : IDisposable
{
    void BeginTransaction();
    void BeginTransaction(IsolationLevel isolationLevel);
    int Save();
}
查看更多
登录 后发表回答