Entity Framework: не удалось присоединить объект типа ‘Country’, поскольку другой объект того же типа уже имеет то же значение первичного ключа

#c# #sql #.net #sql-server #entity-framework

#c# #sql #.net #sql-server #entity-framework

Вопрос:

Я пытаюсь упростить гораздо более сложную модель данных, но для ясности давайте предположим, что у меня есть две таблицы БД: tb_Item и tb_Country с FK в tb_Item таблице, относящейся tb_Country.CountryId к и tb_Item.MarketId .

В коде у меня есть Item Country классы and , где Country является членом Item , потому что мне нужно получить доступ к некоторым Country свойствам в бизнес-логике (хотя это не должно иметь отношения к этому вопросу).

 public class Item 
{
    public long ItemId { get; set; }
    public int? MarketId { get; set; }
    public string Name { get; set; }
    public virtual Country Country { get; set; }
}

public class Country
{
    public int CountryId { get; set; }
    public string Name { get; set; }
}
  

Когда я извлекаю записи элементов из базы данных для отображения в пользовательском интерфейсе, я делаю что-то вроде этого (опять же, упрощенный):

 using (var ctx = GetContext())
{
    var items = ctx.Items.Include(x => x.Country).AsQueryable();
}

var matching = Items.Where(d => d.AsOfDate == items.Where(d2 => 
d2.Country.IsoCountryCode == d.Country.IsoCountryCode amp;amp; 
d2.AsOfDate <= asOfDate).Max(to2 => to2.AsOfDate));
  

Затем, когда я вношу какие-либо изменения через пользовательский интерфейс и сохраняю, вызывается этот Save метод:

 public void SaveItems(IList<Item> items)
{
    _repo.ExecuteInTransaction(ctx =>
    {
        foreach (var item in items)
        {
            var existingItems = _repo.FindItems(
                item.AsOfDate.Date,
                item.MarketId)
            var existing = existingItems.FirstOrDefault(a => a.AsOfDate.Date == item.AsOfDate.Date);
            var createNewForAmend = existing == null amp;amp; item.ItemId > 0;
            if (createNewForAmend)
            {
                existing = _repo.GetItemById(item.ItemId);
                existing.ItemId = 0;
                existing.AutomaticTouchDisabled = true;
            }
            _repo.SaveItem(ctx, item);
            if (!createNewForAmend) continue;
            _repo.SaveItem(ctx, existing);
        }
    });
}
  

Где FindItems :

 public IList<Item> FindItems(DateTime asOfDate, int? countryId)
{
    using (var ctx = GetContext())
    {
        var rawItems = ctx.Items.Include(x => x.Country).AsQueryable();

        var items = rawItems.Where(d => d.AsOfDate == rawItems.Where(d2 =>
                                                                d2.Country.IsoCountryCode == d.Country.IsoCountryCode amp;amp; 
                                                                d2.AsOfDate <= asOfDate).Max(to2 => to2.AsOfDate));

        if (countryId.HasValue)
            items = items.Where(d => d.Country.CountryId == countryId.Value);

        return items.ToList();
    }
}
  

GetItemById is:

 public Item GetItemById(long id)
{
    using (var ctx = GetContext())
    {
        return ctx.Items.SingleOrDefault(c => c.ItemId == id);
    }
}
  

…и SaveItem является:

 public void SaveItem(Context ctx, Item item)
{
    ctx.Items.Attach(item);
    ctx.Entry(item).State = EntityState.Modified;
    ctx.Items.AddOrUpdate(item);
    ctx.SaveChanges();
}
  

В момент, когда ctx.SaveChanges() выполняется, и я сохраняю запись элемента с CountryId уже существующим в Country таблице, я получаю страшную ошибку EF:

System.ServiceModel .Исключение FaultException: ‘Прикрепление объекта типа ‘MyService.Data.Модель.Country’ не удалось, потому что другой объект того же типа уже имеет то же значение первичного ключа. Это может произойти при использовании метода «Attach» или при установке состояния объекта на «Неизмененный» или «Измененный», если какие-либо объекты в графе имеют конфликтующие значения ключа. Это может быть связано с тем, что некоторые объекты являются новыми и еще не получили значения ключей, сгенерированные базой данных. В этом случае используйте метод «Add» или «Added» состояние объекта для отслеживания графика, а затем установите для состояния не новых объектов значение «Неизмененный» или «Измененный» в зависимости от обстоятельств. ‘

Мой вопрос в том, что я не ожидаю, что в tb_Item таблице будут проблемы с ограничением уникальности для повторяющихся стран (я ожидаю, что для элементов в страну будет много: одно отношение). Это почти так, как если бы код пытался сохранить дублирующуюся запись страны в tb_Country таблицу при сохранении записи в tb_Item таблицу.

Я уверен, что делаю что-то глупое, я просто не могу понять, что это такое … пробовал предложения в других вопросах, связанных с этим: попытка отсоединить Country модель, использование AsNoTracking() при извлечении записей, не выполнение .Include(x => x.Country) при извлечении записей элементов и т. Д., Но ничего не работает.

Комментарии:

1. Может быть несколько способов объяснить, почему это происходит, но я вижу одну очевидную вещь — почему вы вызываете AddOrUpdate после того, как вы уже подключили объект? Это просто попытается присоединить его снова и должно вызвать исключение, которое вы видите

2. Удаление либо . Прикрепить или . Строки AddOrUpdate не имеют никакого значения, та же ошибка. Если я удалю . Присоедините и строки .State, тогда я вижу ошибку SQL Server: System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Violation of UNIQUE KEY constraint 'UQ_tb_Country_Code'. Cannot insert duplicate key in object 'dbo.tb_Country'.

3. Согласно сообщению об ошибке, вы должны, чтобы это происходило не при сохранении, а при попытке прикрепить объект item к контексту. Без дополнительного кода трудно сказать, почему именно это происходит, но суть должна заключаться в том, что у вас уже есть страна с (например) идентификатором 1, прикрепленным где-то (возможно, из-за вызова include). Затем у вас есть другая ссылка на объект, также с идентификатором 1, и вы также пытаетесь присоединить ее, вызывая ошибку. Быстрое и грязное исправление заключается в добавлении FK в модель и установке только идентификатора страны. В противном случае проверьте свои вызовы code / EF и использование экземпляра DbContext.

4. @Jejuni, под «добавлением FK в модель» вы подразумеваете украшение элемента. Свойство Country с [ForeignKey("MarketId")] атрибутом? Пробовал это раньше, но безуспешно.

5. Допустим, у меня есть 3 элемента, и 2 из них имеют одну и ту же дочернюю страну (с идентификатором 1). Я не ожидаю, что это будет проблемой, поскольку многие элементы могут иметь одну и ту же страну / одна страна может принадлежать многим элементам. Я бы не ожидал, что это сработает. Если я выполняю простую вставку элемента в необработанный SQL, я могу добавить столько строк элементов, сколько захочу, с тем же идентификатором страны.