2013-04-23 3 views
0

У меня есть три класса: Fish (который содержит два свойства типа Chips и MushyPeas соответственно), MushyPeas (который содержит свойство типа Chips) и Chips (который имеет свойство Name).Присоединение объектов, полученных из нескольких источников

Я бег следующего куска гипотетического кода:

int chipsId; 
using (var db = new FishContext()) 
{ 
    var creationChips = new Chips() { Name = "A portion of chips" }; 
    db.Chips.Add(creationChips); 
    db.SaveChanges(); 
    chipsId = creationChips.ChipsId; 
} 

Chips retrievedChips1; 
using (var db = new FishContext()) 
{ 
    retrievedChips1 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0]; 
} 

Chips retrievedChips2; 
using (var db = new FishContext()) 
{ 
    retrievedChips2 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0]; 
} 

using (var db = new FishContext()) 
{ 
    db.Chips.Attach(retrievedChips1); 
    db.Chips.Attach(retrievedChips2); 

    var mushyPeas = new MushyPeas() { Chips = retrievedChips2 }; 

    var fish = new Fish() { Chips = retrievedChips1, MushyPeas = mushyPeas }; 
    db.Fish.Add(fish); 
    db.ChangeTracker.DetectChanges(); 
    db.SaveChanges(); 
} 

Это смоделировать ситуацию, в моем реальном приложении, в котором EF объекты (которые на самом деле может представлять собой ту же запись базы данных) будут загружены из множество различных DbContexts, а затем добавлено в дерево объектов в другом DbContext.

Если я не вызываю две строки db.Chips.Attach, тогда новые объекты Chips создаются, когда объект Fish сохраняется в базе данных и назначаются новые идентификаторы.

Вызов db.Chips.Attach разрешает эту проблему для одного из полученных объектов, но второй вызов Attach не выполняется с исключением: «Объект с тем же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с помощью тот же ключ. "

Каков наилучший способ достичь того, чего я хочу достичь здесь?

+0

FYI, если вы используете только один DbContext для более длительного срока службы, нет никакого вреда, потому что DbContext не поддерживает соединение с БД все время, только для запросов и сохранения изменений он открывает и закрывает соединение с базой данных.Наличие множественного контекста просто увеличивает сложность. –

ответ

0

Вы можете запросить Local коллекцию, чтобы проверить, если объект с тем же ключом уже подключен, и если да, то используйте прилагаемую объект:

using (var db = new FishContext()) 
{ 
    var attachedChips1 = db.Chips.Local 
     .SingleOrDefault(c => c.ChipsId == retrievedChips1.ChipsId); 
    if (attachedChips1 == null) 
    { 
     db.Chips.Attach(retrievedChips1); 
     attachedChips1 = retrievedChips1; 
    } 

    var attachedChips2 = db.Chips.Local 
     .SingleOrDefault(c => c.ChipsId == retrievedChips2.ChipsId); 
    if (attachedChips2 == null) 
    { 
     db.Chips.Attach(retrievedChips2); 
     attachedChips2 = retrievedChips2; 
    } 

    var mushyPeas = new MushyPeas() { Chips = attachedChips2 }; 

    var fish = new Fish() { Chips = attachedChips1, MushyPeas = mushyPeas }; 

    //... 
} 

(Первая проверка не имеет смысла в этом простом например, потому что новый контекст пуст, при этом ничего не связано с ним. Но вы получаете идею ...)

Однако в случае, если вы также хотите обновить связанные объекты (например, установив состояние на Modified после есть) было бы проблемой, если retrievedChips1 и retrievedChips2 есть (exc ept значение ключа) разные значения свойств. Вы должны как-то решить, что это «правильный». Но это была бы логика бизнеса. Вам просто нужно передать один из них в EF и только один. В вашем сценарии не имеет значения, какой из них вы используете, потому что вы только создаете отношения, и для этого EF будет заботиться только о ключевом значении.

Сторона примечания: вместо ...ToList()[0] более естественным способом будет ...First() (или Single() в этом случае, потому что вы запрашиваете ключ).

+0

Это не сработает, потому что проблема не в том, что attachChips1 или attachChips2 уже привязаны к контексту, это связано с тем, что attachChips2 ссылается на сущность, которая также присоединяетChips1, которая уже была рекурсивно присоединена во время первого вызова 'Attach'. Такая же проблема будет существовать с вашим решением. –

+0

@ w.brian: он загружает 'retrievedChips1' и' retrievedChips2' без свойств навигации, поэтому нет других объектов, на которые ссылаются, и мой код выше разрешит проблему в примере вопроса. Для более общего случая вы правы, исключение может быть вызвано связанными объектами, и реальный источник проблемы может быть гораздо более скрытым. – Slauma

+0

Это надуманный пример за его оригинальное сообщение. Код в его примере не подведет по той же причине, что и вы. –

1

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

Исключение "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key" обычно вводит в заблуждение, поскольку объект, который вы пытаетесь подключить, на самом деле не привязан к контексту данных. Что происходит, когда вы присоединяете объект, он рекурсивно присоединяет любые объекты, к которым он ссылается. Итак, если вы привязываете объект к контексту данных, а затем присоединяете другой объект, который ссылается на какой-либо объект, который был связан с неявным образом, вы получите эту ошибку. Решение довольно простое:

using (var db = new FishContext()) 
{ 
    var chips1 = db.Chips.Find(retrievedChips1.Id); 
    var chips2 = db.Chips.Find(retrievedChips2.Id); 

    var mushyPeas = new MushyPeas() { Chips = chips2 }; 

    var fish = new Fish() { Chips = chips1, MushyPeas = mushyPeas }; 
    db.Fish.Add(fish); 
    db.ChangeTracker.DetectChanges(); 
    db.SaveChanges(); 
} 

Это гарантирует, что оба объекта будут прикреплены к контексту данных без каких-либо проблем ObjectStateManager.

+1

+1, но есть дополнительный SELECT, запущенный в базу данных. –

+0

Это действительно является недостатком этого решения. –

Смежные вопросы