Ошибка при добавлении новой сущности. Attaching an entity of type failed because another entity has the same primary key value

267
29 апреля 2017, 20:22

Привет всем, столкнулся с ошибкой при добавлении новой сущности в базу данных. Ниже привожу код сущностей/код метода где создается сущность/код метода ответственного за добавление.

Сущности:

public sealed class Lot : Entity
{
    [Required, DefaultValue(false)]
    public bool IsActive { get; set; }
    [Required, DefaultValue(false)]
    public bool IsDelete { get; set; }
    [Required, DefaultValue(false)]
    public bool InCredit { get; set; }
    [DefaultValue(1), Range(1, 10)]
    public int PhaseNumber { get; set; }
    [Required]
    public decimal DesiredPrice { get; set; }
    public Guid? AcceptedBetId { get; set; }
    [Required]
    public DateTime CreationDate { get; set; }
    public DateTime ExpirationDateTime { get; set; }
    [Required]
    public string City { get; set; }
    public User User { get; set; }
    public Car Car { get; set; }
    public ICollection<Bet> Bets { get; set; }
    public Lot()
    {
        Bets = new List<Bet>();
    }
}
public sealed class Bet : Entity
{
    [Required, Range(0.0, double.MaxValue)]
    public decimal Price { get; set; }
    [Required]
    public DateTime CreationDate { get; set; }
    [Range(0, int.MaxValue)]
    public int BetNumber { get; set; }
    public Lot Lot { get; set; }
    public User User { get; set; }
}

Участок кода где я создаю новую сущность, и вызываю метод сохранения:

Lot lot;
using (LotsManager lotsManager = new LotsManager())
{
     lot = await lotsManager.GetLotAsync(model.LotId);
}
User user;
using (UserManager userManager = new UserManager())
{
      user = await userManager.GetUserAsync(model.UserInfoId);
}
var bet = new Bet
{
      Id = Guid.NewGuid(),
      Price = model.Price,
      CreationDate = DateTime.Now,
      BetNumber = lastBetNumber + 1,
      Lot = lot,
      User = user
 };
 await _betsManager.SaveBetAsync(bet);

Метод который ответственен а добавление данных в БД.

protected async Task Save<T>(T entity) where T : Entity
    {
        using (var dbContextTransaction = _context.Database.BeginTransaction())
        {
            try
            {
                var dbEntity = await _context.Set<T>().SingleOrDefaultAsync(x => x.Id == entity.Id);
                _context.Set<T>().Attach(entity);
                _context.Entry(entity).State = dbEntity != null ? EntityState.Modified : EntityState.Added;
                await _context.SaveChangesAsync();
                dbContextTransaction?.Commit();
            }
            catch (DbEntityValidationException ex)
            {
                dbContextTransaction?.Rollback();
                var error = ex.EntityValidationErrors.First().ValidationErrors.First();
                throw new InvalidModelException(error.ErrorMessage);
            }
            catch (Exception ex)
            {
                dbContextTransaction?.Rollback();
                throw new InvalidDbOperationException(ex.Message);
            }
        }
    }

Собственно сом код ошибки. Она возникает на строке (_context.Set().Attach(entity)).

Attaching an entity of type 'WebCar.Domain.Entities.Lot' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

Я пробовал использовать разные приемы, но ничего не получилось

_context.Set<T>().Attach(entity);
_context.Entry(entity).State = EntityState.Unchanged;
or
_context.Entry(entity).State = EntityState.Unchanged;

Спасибо всем кто откликнется. Сам я джун, так что особо не судите.

Answer 1

Проблема кроется в методе:

protected async Task Save<T>(T entity) where T : Entity
{
    using (var dbContextTransaction = _context.Database.BeginTransaction())
    {
        try
        {
            var dbEntity = await _context
                           .Set<T>().SingleOrDefaultAsync(x => x.Id == entity.Id);
            _context.Set<T>().Attach(entity);
            _context.Entry(entity).State = 
                 dbEntity != null 
                 ? EntityState.Modified 
                 : EntityState.Added;
            await _context.SaveChangesAsync();
            dbContextTransaction?.Commit();
        }
        catch (DbEntityValidationException ex) { /* ... */ }
        catch (Exception ex) { /* ... */ }
    }
}
  1. У тебя есть объект entity, передаваемый через параметр в метод.
  2. Внутри метода ты снова пытаешься получить этот же объект в dbEntity
  3. У тебя получается два объекта, с одинаковыми primary key, которые нужно отслеживать, от этого и падает ошибка.

Как исправить:

  • При получении dbEntity использовать AsNoTracking, метод AsNoTracking() применяется к набору IQueryable, это значит что Entity Framework не будет отслеживать изменения полученных объектов.
  • Попытаться получить объект до открытия транзакции, просто открыв контекст и заполнив переменную внутри мода перед открытием транзакции, причем контекст в котором получаешь этот объект - потом закрыть, ведь тебе важен сам объект был или нет, чтобы поставить статус EntityState.Modified если был или EntityState.Added если не было.
READ ALSO
Проблема с кодировками. В Encoding.Convert есть баг?

Проблема с кодировками. В Encoding.Convert есть баг?

В общем, на работе стоит русская Windows 7, а дома английский Windows 10, так вот на работе с кодировками проблем нету, а дома какие-то кракозябры

236
Потеря данных при извлечении из БД MS SQL

Потеря данных при извлечении из БД MS SQL

Есть метод с запросом к БД:

250
Передача доп.параметров в System.Text.RegularExpressions.Regex.Replace

Передача доп.параметров в System.Text.RegularExpressions.Regex.Replace

Как передать в MatchEvaluator доппараметры

284
TwoWay Binding для Dictionary&lt;Enum, bool?&gt;

TwoWay Binding для Dictionary<Enum, bool?>

Есть несколько checkbox'ов с тремя состояниями: включено, исключено, не установлено

303