EFCore Update using UnitOfWork Repository and Serv

2019-08-01 22:55发布

问题:

Following the N-tier architecture, I'm currently having issues while trying to update an entity. In my Controller, if I set the properties manually, the update works perfectly, if I set the properties mapping the ViewModel into the entity it generate the exception "...cannot track multiple objects with the same key". How can I solve this?

This is my UnitOfWork:

public class UnitOfWork : IUnitOfWork
{
    private readonly CoreContext _context;
    private IGenericRepository<Currency> currencyRepository;

    private static string DataConnectionString => new DatabaseConfiguration().GetDataConnectionString();

    public UnitOfWork(CoreContext context)
    {
        var optionsBuilder = new DbContextOptionsBuilder<CoreContext>();
        optionsBuilder.UseSqlServer(DataConnectionString);
        _context = new CoreContext(optionsBuilder.Options);

    }

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

    public async Task CommitAsync()
    {
        await _context.SaveChangesAsync();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        this.disposed = true;
    }

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


    public IGenericRepository<Currency> CurrencyRepository
    {
        get { return this.currencyRepository ?? (this.currencyRepository = new GenericRepository<Currency>(_context)); }
    }

}


public interface IUnitOfWork : IDisposable
{
    int Commit();
    Task CommitAsync();
    IGenericRepository<Currency> CurrencyRepository { get; }

}

This is my Generic Repository + CurrencyRepository:

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    internal CoreContext _context;
    internal DbSet<T> _entities;

    public GenericRepository(CoreContext context)
    {
        _context = context;
        _entities = context.Set<T>();
    }

    public virtual void Update(T entityToUpdate)
    {
        _context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public IEnumerable<T> GetBy(Expression<Func<T, bool>> predicate)
    {
        IEnumerable<T> query = _entities.Where(predicate).AsEnumerable();
        return query;
    }

}


public interface IGenericRepository<T> where T : class
{
    void Update(T entity);
    IEnumerable<T> GetBy(Expression<Func<T, bool>> predicate);

}

public class CurrencyRepository : GenericRepository<Currency>, ICurrencyRepository
{        
    public CurrencyRepository(CoreContext context)
    : base(context) {
    }
}

This is my Service:

public class CurrencyService : ICurrencyService
{
    private readonly IUnitOfWork _unitOfWork;

    public void UpdateCurrency(Currency currency)
    {
        _unitOfWork.CurrencyRepository.Update(currency);
    }

    public Currency GetCurrencyById(int Id)
    {
        return _unitOfWork.CurrencyRepository.GetBy(x => x.CurrencyId == Id).Single();

    }

    public int SaveChanges()
    {
        return _unitOfWork.Commit();
    }

}

public interface ICurrencyService 
{
    Currency GetCurrencyById(int Id);
    void UpdateCurrency(Currency currency);

    int SaveChanges();
}

And finally, This is my Controller:

public class CurrencyController : Controller
{
    private readonly ICurrencyService _currencyService;
    private readonly IMapper _mapper;

    public CurrencyController(ICurrencyService currencyService, IMapper mapper)
        : base()
    {
        _currencyService = currencyService;
        _mapper = mapper;
    }


    [HttpPost]
    public ActionResult UpdateCurrency([DataSourceRequest] DataSourceRequest dsRequest, CurrencyViewModel currency)
    {
        if (currency != null && ModelState.IsValid)
        {
            var currencyToUpdate = _currencyService.GetCurrencyById(currency.CurrencyId);

            if (currencyToUpdate != null)
            {
                //UPDATE NOT WORKING
                //currencyToUpdate = _mapper.Map<CurrencyViewModel, Currency>(currency);

                //UPDATE WORKING
                currencyToUpdate.Description = currency.Description;
                currencyToUpdate.Code= currency.Code;
                currencyToUpdate.Symbol = currency.Symbol;

                _currencyService.UpdateCurrency(currencyToUpdate);
                _currencyService.SaveChanges();
            }
        }

        return Json(ModelState.ToDataSourceResult());
    }

回答1:

It doesn't work in your case because you are using IMapper.Map overload which creates a new instance of the currency object. Because of the fact that your GetCurrencyById track your entity after successful fetch, you'll end up with the exception since you'll have to instances with the same key (One tracked already in DbContext, and new one which mapper created).

There are two things you can do to prevent it:

  1. Use AsNoTracking() method when fetching from database
  2. Use IMapper.Map overload which takes both destination and source instances of currency:

    _mapper.Map(currency, currencyToUpdate);