Entity framework SaveChanges () в другом DbContext вставляет ненужные строки

моя модель супермаркета содержит класс StockItem и класс Alert, который содержит поле StockItem:

public class StockItem
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int CurrentQuantity { get; set; }
    public int MinQuantity { get; set; }
}

public class Alert
{
    public int ID { get; set; }
    public int Message{ get; set; }
    public virtual StockItem StockItem { get; set; }
}

У меня есть функция, которая извлекает все StockItems с одним DbContext:

using (var db = new MyDbContext())
{
     return db.StockItems.ToList();
}

И еще одна функция, которая обрабатывает эти элементы и добавляет новые оповещения в другой DbContext:

foreach (var item in items)
{
     if (item.CurrentQuantity < item.MinQuantity)
     {
        using (var db = new MyDbContext())
        {
            db.Alerts.Add(new Alert(){StockItem = item, Message = "Low Quantity"});
            db.SaveChanges();
        }
     }
}

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


person Sean    schedule 24.05.2011    source источник


Ответы (2)


Я думаю, вам сначала нужно Attach купить товар на складе. Попробуй это:

foreach (var item in items)
{
     if (item.CurrentQuantity < item.MinQuantity)
     {
        using (var db = new MyDbContext())
        {
            db.StockItems.Attach(item);
            db.Alerts.Add(new Alert {StockItem = item, Message = "Low Quantity"});
            db.SaveChanges();
        }
     }
}
person benwasd    schedule 24.05.2011
comment
Понятно. но теперь мне нужно выполнить присоединение во многих случаях db.SaveChanges (). Разве Entity Framework не может автоматически узнать, согласно идентификатору StockItem, что он уже находится в БД? - person Sean; 24.05.2011
comment
если вы создаете новый контекст, ef не может узнать его, не запрашивая базу данных ... поэтому, если вы не хотите повторно подключаться, вы должны изменить жизненный цикл контекста (не удалять после каждой операции и кешировать контекст) - person benwasd; 24.05.2011
comment
@Sean: Никакой EF не сделает для тебя ничего подобного. Вы обязаны сообщать EF, что нового, обновленного, удаленного или неизменного. Вызывая Add для предупреждений, вы присоединяете все сущности к графу объектов Alert и помечаете их как вставленные. - person Ladislav Mrnka; 24.05.2011
comment
Я сделал, как было предложено, и сохранил один статический экземпляр DbContext, используя его в методе выборки, а затем в методе, который создает оповещения. все работало отлично, но когда я вызвал эти методы с помощью WCF, дублирование появилось снова. При отладке казалось, что они все еще используют тот же DbContext. Пытался также настроить WCF для работы PerSession, и это тоже не помогло. - person Sean; 24.05.2011

using (var db = new MyDbContext())
{
   var items = db.StockItems.ToList();
   foreach (var item in items)
   {
      if (item.CurrentQuantity < item.MinQuantity)
      {
         db.Alerts.Add(new Alert {StockItem = item, 
            Message = "Low Quantity"});
         db.SaveChanges();
      }
   }        
}

В этом случае прикреплять файлы не нужно. EF может отслеживать изменения только в своем собственном жизненном цикле, в первом случае, когда вы это делаете,

using (var db = new MyDbContext())
{
     return db.StockItems.ToList();
}

Вы удаляете MyDbContext, поэтому EF делает все стандартные элементы независимыми (отдельные элементы), и когда вы добавляете их в другой контекст, контекст предполагает, что это новый элемент, и вставляет его.

Лучшим способом будет поддерживать Context на протяжении всех изменений, которые вы хотите внести. Также обратите внимание, что сохранение контекста в течение более длительного времени не означает, что вы будете постоянно поддерживать соединение с базой данных. EF будет автоматически открывать и закрывать соединение с базой данных только тогда, когда вы выполняете запрос и вызываете сохранение изменений.

В противном случае вам придется прикрепить, как предложил Бен.

person Akash Kava    schedule 24.05.2011
comment
Я сделал, как вы предложили, и сохранил один статический экземпляр DbContext и использовал его в методе выборки, а затем в методе создания предупреждений. все работало отлично, но когда я вызвал эти методы с помощью WCF, дублирование появилось снова. При отладке казалось, что они все еще используют тот же DbContext. Пытался также настроить WCF для работы PerSession, и это тоже не помогло. - person Sean; 24.05.2011
comment
В WCF ваш статический экземпляр не будет работать, вместо этого вы должны использовать HttpContext.Current.Session для хранения вашего DbContext, это сохранит ваш контекст для сеанса, но есть одна проблема: у вас должен быть какой-то пинг, где ваш сеанс останется открытым. В противном случае вы можете попробовать переключиться на RIA Services, это неплохо работает. - person Akash Kava; 24.05.2011