Estrutura de entidade 4 - AddObject vs Attach

132

Eu tenho trabalhado com o Entity Framework 4 recentemente e estou um pouco confuso sobre quando usar ObjectSet.Attach e ObjectSet.AddObject .

Pelo meu entendimento:

  • Use "Anexar" quando uma entidade já existir no sistema
  • Use "AddObject" ao criar uma nova entidade

Então, se estou criando uma nova Pessoa , faço isso.

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

Se estou modificando uma Pessoa existente , faço o seguinte:

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

Lembre-se, este é um exemplo muito simples . Na realidade, estou usando Pure POCO (sem geração de código), padrão de repositório (não lida com ctx.Persons) e unidade de trabalho (não lida com ctx.SaveChanges). Mas "debaixo das cobertas", o acima é o que acontece na minha implementação.

Agora, minha pergunta - ainda estou para encontrar um cenário em que tive que usar o Attach .

O que estou perdendo aqui? Quando precisamos usar o Anexar?

EDITAR

Apenas para esclarecer, estou procurando exemplos de quando usar Anexar sobre AddObject (ou vice-versa).

EDIT 2

A resposta abaixo está correta (que eu aceitei), mas pensei em adicionar outro exemplo em que o Attach seria útil.

No meu exemplo acima, para modificar uma Pessoa existente , duas consultas estão realmente sendo executadas.

Um para recuperar a Pessoa (.SingleOrDefault) e outro para executar a UPDATE (.SaveChanges).

Se (por algum motivo), eu já sabia que "Joe Bloggs" existia no sistema, por que fazer uma consulta extra para obtê-lo primeiro? Eu poderia fazer isso:

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

Isso resultará em apenas uma instrução UPDATE sendo executada.

RPM1984
fonte
Anexar também é usado no MVC nos dias de hoje ao Colocar os modelos de volta diretamente no EF. Funciona muito bem e economiza uma tonelada de linhas de código.
Piotr Kula

Respostas:

162

ObjectContext.AddObject e ObjectSet.AddObject :
Ométodo AddObject é para adicionar objetos recém-criados que não existem no banco de dados. A entidade receberá uma EntityKey temporária gerada automaticamentee seu EntityState será definido como Adicionado . Quando SaveChanges é chamado, fica claro para o EF que essa entidade precisa ser inserida no banco de dados.

ObjectContext.Attach e ObjectSet.Attach :
por outro lado, Attach é usado para entidades que já existem no banco de dados. Em vez de definir EntityState como Adicionado, Anexar resulta em umEntityState inalterado , o que significa que não foi alterado desde que foi anexado ao contexto. Os objetos que você está anexando são assumidos como existentes no banco de dados. Se você modificar os objetos depois que eles foram anexados, quando você chamar SaveChanges, o valor da EntityKey será usado para atualizar (ou excluir) a linha apropriada, localizando seu ID correspondente na tabela db.

Além disso, usando o método Attach, você pode definir relacionamentos entre entidades que já existem no ObjectContext, mas que possuemnão foi conectado automaticamente. Basicamente, o principal objetivo do Attach é conectar entidades que já estão anexadas ao ObjectContext e não sãonovas, portanto você não pode usar o Attach para anexar entidades cujo EntityState foi adicionado. Você precisa usar Add () neste caso.

Por exemplo, vamos supor que sua entidade Pessoa tenha uma propriedade de navegação denominada Endereços, que é uma coleção daentidade Endereço . Digamos que você tenha lido os dois Objetos a partir do contexto, mas eles não são relacionados um ao outro e você deseja fazê-lo:

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();
Morteza Manavi
fonte
Obrigado pela resposta, eu entendo a definição dos dois (também conhecido como dois primeiros parágrafos). Mas não entendo um cenário em que PRECISO usar o Attach. Seu último parágrafo realmente não faz sentido para mim (lê basicamente como uma combinação dos dois primeiros parágrafos), você pode me dar um exemplo de onde eu usaria "Anexar" no meu cenário acima? É isso mesmo que estou procurando - exemplos, não definições. Realmente aprecio seu tempo. :)
RPM1984
1
Sem problemas, adicionei um trecho de código para esclarecer o último parágrafo, pois você pode ver que temos dois objetos não relacionados e o Attach nos ajuda a relacioná-los. O outro exemplo seria usar Anexar () para Anexar uma volta "entidade separada" para o contexto (Há várias razões que você pode querer uma entidade separada para ser para trás Anexado ao contexto)
Morteza Manavi
1
Sim, eu peguei agora. Acabei de assistir a um vídeo do TechEd no EF4 (de Julie Lerman), que mostrou um exemplo. Você pode ter uma entidade que NÃO recuperou de uma consulta (ou seja, está desconectada), mas sabe que ela existe, portanto, você usa o Attach para executar uma ATUALIZAÇÃO nessa entidade. Faz sentido, embora eu ainda lute para imaginar um cenário em que você teria uma entidade "desconectada". Obrigado pela ajuda.
RPM1984
1
Ótimo. Posso pedir que você compartilhe o link do vídeo com outros colegas desenvolvedores que possam ler este post?
Morteza Manavi 13/10/10
3
O link acima compartilhado pelo RPM1984 está quebrado, agora é redirecionado para channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DEV205 . Desfrute
Empilhado
31

Esta é uma resposta tardia, mas pode ajudar outras pessoas a encontrar isso.

Basicamente, uma entidade "desconectada" pode acontecer quando você manipula uma entidade fora do escopo "using".

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
}

Se você inserir outro escopo "using", a variável "e" será desconectada porque pertence ao escopo anterior "using" e, como o escopo anterior "using" é destruído, o "e" será desconectado.

É assim que eu entendo.

TchiYuan
fonte
3
O exemplo de Tchi é um exemplo excelente e simples - sim, a variável Employee deve ser declarada fora. tente e.Address.Street fora do escopo e veja um pop-up de exceção de referência nula. Se você anexar, o aplicativo não precisará retornar ao banco de dados para o funcionário no segundo escopo.
7113 Steve
9

Esta é uma citação do Programming Entity Framework: DbContext

Chamar Remove em uma entidade que não é rastreada pelo contexto fará com que uma InvalidOperationException seja lançada. O Entity Framework lança essa exceção porque não está claro se a entidade que você está tentando remover é uma entidade existente que deve ser marcada para exclusão ou uma nova entidade que deve ser ignorada. Por esse motivo, não podemos usar apenas Remover para marcar uma entidade desconectada como Excluída; precisamos anexá-lo primeiro .

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();
    }
}

O método TestDeleteDestination simula um aplicativo cliente que busca um destino existente do servidor e passa-o para o método DeleteDestination no servidor. O método DeleteDestination usa o método Attach para informar ao contexto que é um destino existente. Em seguida, o método Remove é usado para registrar o destino existente para exclusão

Teoman shipahi
fonte
-8

Que tal se referir apenas à chave primária em vez de anexar?

ou seja:

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();
Dan
fonte