Entity Framework 4 - AddObject против Attach


132

Я недавно работал с Entity Framework 4 и немного не понимаю, когда использовать ObjectSet.Attach и ObjectSet.AddObject .

Из моего понимания:

  • Используйте "Прикрепить", если объект уже существует в системе
  • Используйте «AddObject» при создании новой сущности.

Итак, если я создаю нового человека , я делаю это.

var ctx = new MyEntities();
var newPerson = new Person { Name = "Joe Bloggs" };
ctx.Persons.AddObject(newPerson);
ctx.SaveChanges();

Если я изменяю существующего человека , я делаю следующее:

var ctx = new MyEntities();
var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" };
existingPerson.Name = "Joe Briggs";
ctx.SaveChanges();

Имейте в виду, это очень простой пример. На самом деле я использую Pure POCO (без генерации кода), шаблон репозитория (не работайте с ctx.Persons) и Unit of Work (не работайте с ctx.SaveChanges). Но «под прикрытием» в моей реализации происходит то, что описано выше.

Теперь мой вопрос - мне еще предстоит найти сценарий, в котором мне пришлось бы использовать Attach .

Что мне здесь не хватает? Когда нам нужно использовать Attach?

РЕДАКТИРОВАТЬ

Чтобы прояснить, я ищу примеры, когда использовать Attach over AddObject (или наоборот).

РЕДАКТИРОВАТЬ 2

Приведенный ниже ответ правильный (который я принял), но подумал, что добавлю еще один пример, в котором будет полезно Attach.

В моем приведенном выше примере для изменения существующего человека фактически выполняются два запроса.

Один для получения Person (.SingleOrDefault), а другой для выполнения ОБНОВЛЕНИЯ (.SaveChanges).

Если (по какой-то причине) я уже знал, что «Блог Джо» существует в системе, зачем делать дополнительный запрос, чтобы сначала получить его? Я мог сделать это:

var ctx = new MyEntities();
var existingPerson = new Person { Name = "Joe Bloggs" };
ctx.Persons.Attach(existingPerson);
ctx.SaveChanges();

Это приведет к выполнению только оператора UPDATE.


Attach также используется в MVC сейчас, когда модели возвращаются непосредственно в EF. Работает неплохо и экономит массу строк кода.
Петр Кула

Ответы:


162

ObjectContext.AddObject и ObjectSet.AddObject : AddObject метод для добавления нового созданных объектовкоторые не существуют в базе данных. Сущность получит автоматически сгенерированный временный ключ EntityKey, а для его EntityState будет установлено значение Added . Когда вызывается SaveChanges, для EF будет ясно, что эту сущность необходимо вставить в базу данных.

ObjectContext.Attach и ObjectSet.Attach :
с другой стороны, Attach используется для сущностей, которые уже существуют в базе данных. Вместо того, чтобы устанавливать для EntityState значение Added, Attach приводит к Unchanged EntityState, что означает, что оно не изменилось с момента присоединения к контексту. Предполагается, что присоединяемые объекты существуют в базе данных. Если вы изменяете объекты после того, как они были присоединены, при вызове SaveChanges значение EntityKey используется для обновления (или удаления) соответствующей строки путем нахождения соответствующего идентификатора в таблице db.

Кроме того, используя метод Attach, вы можете определить отношения между сущностями, которые уже существуют в ObjectContext, но имеютне было подключено автоматически. По сути, основная цель Attach - подключить сущности, которые уже прикреплены к ObjectContext и не являютсяновыми, поэтому вы не можете использовать Attach для присоединения сущностей, для которых EntityState добавлен. В этом случаевы должны использовать Add () .

Например, предположим, что у вашей сущности Person есть свойство навигации с именем Addresses, которое является коллекциейсущности Address . Допустим, вы прочитали оба объекта из контекста, но они не связаны друг с другом, и вы хотите сделать это таким:

var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" };
var myAddress = ctx.Addresses.First(a => a.PersonID != existingPerson.PersonID);
existingPerson.Addresses.Attach(myAddress);
// OR:
myAddress.PersonReference.Attach(existingPerson)
ctx.SaveChanges();

Спасибо за ответ, я понимаю определение этих двух (или первых двух абзацев). Но я не понимаю сценария, в котором мне НУЖНО использовать Attach. Ваш последний абзац действительно не имеет для меня смысла (читается в основном как комбинация первых двух абзацев), вы можете привести мне пример того, где я бы использовал «Присоединить» в моем вышеупомянутом сценарии? Это действительно то, что я ищу - примеры, а не определения. Хотя действительно цените ваше время. :)
RPM1984

1
Нет проблем, я добавил фрагмент кода, чтобы прояснить последний абзац, поскольку вы можете видеть, что у нас есть 2 несвязанных объекта, и Attach помогает нам связать их друг с другом. Другой пример - использование метода Attach () для прикрепления «Отсоединенной сущности» обратно к контексту (существуют различные причины, по которым вы можете захотеть, чтобы Отсоединенная сущность была прикреплена обратно к контексту)
Мортеза Манави

1
Ага, теперь я понял. Я только что посмотрел видеоролик TechEd на EF4 (Джули Лерман), в котором показан пример. У вас может быть сущность, которую вы НЕ извлекали из запроса (т.е. она разобщена), но вы знаете, что она существует, поэтому вы используете Attach для выполнения ОБНОВЛЕНИЯ этой сущности. Имеет смысл, хотя я все еще изо всех сил пытаюсь представить сценарий, в котором у вас будет «отключенная» сущность. Спасибо за вашу помощь.
RPM1984

1
Отлично. Могу я попросить вас поделиться ссылкой на видео для других разработчиков, которые могут прочитать этот пост?
Мортеза Манави,

3
Ссылка, указанная выше для RPM1984, не работает, теперь она перенаправлена ​​на channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DEV205 . Enjoy
сложено

31

Это запоздалый ответ, но он может помочь другим, которые его найдут.

По сути, «отключенная» сущность может произойти, когда вы манипулируете сущностью вне области «использования».

Employee e = null;

using (var ctx = new MyModelContainer())
{
     e = ctx.Employees.SingleOrDefault(emp => emp .....);
}

using (var ctx2 = new MyModelContainer())
{
     e; // This entity instance is disconnected from ctx2
}

Если вы войдете в другую область «using», то переменная «e» будет отключена, потому что она принадлежит предыдущей области «using», а поскольку предыдущая область «using» уничтожена, то «e» отключается.

Вот как я это понимаю.


3
Пример Тчи - отличный и простой пример - да, переменная Employee должна быть объявлена ​​снаружи. попробуйте e.Address.Street за пределами области видимости и увидите всплывающее окно исключения нулевой ссылки. Если вы прикрепите, то приложению не придется возвращаться в БД для сотрудника во второй области.
Стив

9

Это цитата из Programming Entity Framework: DbContext

Вызов Remove для объекта, который не отслеживается контекстом, вызовет выброс InvalidOperationException. Entity Framework выдает это исключение, поскольку неясно, является ли сущность, которую вы пытаетесь удалить, существующей сущностью, которую следует пометить для удаления, или новой сущностью, которую следует просто игнорировать. По этой причине мы не можем использовать только Remove, чтобы пометить отключенный объект как удаленный; нам нужно сначала прикрепить его .

private static void TestDeleteDestination()
{
    Destination canyon;
    using (var context = new BreakAwayContext())
    {
        canyon = (from d in context.Destinations
        where d.Name == "Grand Canyon"
        select d).Single();
    }
    DeleteDestination(canyon);
}
private static void DeleteDestination(Destination destination)
{
    using (var context = new BreakAwayContext())
    {
        context.Destinations.Attach(destination);
        context.Destinations.Remove(destination);
        context.SaveChanges();
    }
}

Метод TestDeleteDestination имитирует клиентское приложение, которое выбирает существующее назначение с сервера и затем передает его методу DeleteDestination на сервере. Метод DeleteDestination использует метод Attach, чтобы дать контексту знать, что это существующий пункт назначения. Затем метод Remove используется для регистрации существующего пункта назначения для удаления.


-8

А как насчет ссылки только на первичный ключ вместо присоединения?

то есть:

var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" };
var myAddress = ctx.Addresses.First(a => a.PersonID != existingPerson.PersonID);
existingPerson.AddressId = myAddress.Id // not -> existingPerson.Addresses.Attach(myAddress);
// OR:
myAddress.Person.Id = existingPerson.Id // not -> myAddress.PersonReference.Attach(existingPerson);
ctx.SaveChanges();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.