É possível verificar se um objeto já está anexado a um contexto de dados no Entity Framework?

86

Estou recebendo o seguinte erro ao tentar anexar um objeto que já está anexado a um determinado contexto por meio de context.AttachTo(...):

Já existe um objeto com a mesma chave no ObjectStateManager. O ObjectStateManager não pode rastrear vários objetos com a mesma chave.

Existe uma maneira de alcançar algo nos moldes de:

context.IsAttachedTo(...)

Felicidades!

Editar:

O método de extensão descrito por Jason está próximo, mas não funciona na minha situação.

Estou tentando fazer algum trabalho usando o método descrito na resposta a outra pergunta:

Como excluo uma ou mais linhas de minha tabela usando Linq to Entities * sem * recuperar as linhas primeiro?

Meu código se parece um pouco com este:

var user = new User() { Id = 1 };
context.AttachTo("Users", user);
comment.User = user;
context.SaveChanges();

Isso funciona bem, exceto quando eu faço outra coisa para aquele usuário em que uso o mesmo método e tento anexar um Userobjeto fictício . Isso falha porque eu anexei anteriormente esse objeto de usuário fictício. Como posso verificar isso?

Joshcomley
fonte

Respostas:

57

Aqui está o que eu terminei, o que funciona muito bem:

public static void AttachToOrGet<T>(this ObjectContext context, string entitySetName, ref T entity)
    where T : IEntityWithKey
{
    ObjectStateEntry entry;
    // Track whether we need to perform an attach
    bool attach = false;
    if (
        context.ObjectStateManager.TryGetObjectStateEntry
            (
                context.CreateEntityKey(entitySetName, entity),
                out entry
            )
        )
    {
        // Re-attach if necessary
        attach = entry.State == EntityState.Detached;
        // Get the discovered entity to the ref
        entity = (T)entry.Entity;
    }
    else
    {
        // Attach for the first time
        attach = true;
    }
    if (attach)
        context.AttachTo(entitySetName, entity);
}

Você pode chamá-lo da seguinte maneira:

User user = new User() { Id = 1 };
II.AttachToOrGet<Users>("Users", ref user);

Isso funciona muito bem porque é exatamente igual, context.AttachTo(...)exceto que você pode usar o truque de identificação que citei acima todas as vezes. Você acaba com o objeto previamente anexado ou seu próprio objeto sendo anexado. Chamar CreateEntityKeyo contexto garante que ele seja bom e genérico e funcione mesmo com chaves compostas sem codificação adicional (porque EF já pode fazer isso por nós!).

Joshcomley
fonte
Eu estava tendo um problema parecido, e isso resolveu o meu - brilhante, saúde! +1
RPM1984 de
4
Seria ainda melhor quando o parâmetro string é substituído por uma função de seletor para a coleção à qual a entidade pertence.
Jasper,
1
Alguma ideia de por que (T)entry.Entityàs vezes retorna nulo?
Tr1stan,
1
Não consigo descobrir como devo definir o meu entitySetName. Eu continuo recebendo uma exceção. Estou realmente frustrado porque tudo o que quero fazer é excluir um usuário que não quero ter que lidar com tantas tolices ocultas explodindo meu aplicativo.
Julie
1
se Tjá estiver IEntityWithKey, você não pode simplesmente usar sua entity.EntityKeypropriedade em vez de reconstruí-la ou precisar adivinhar / fornecer o EntitySetName?
drzaus
54

Uma abordagem mais simples é:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
fonte
1
Isso funcionou para mim, só tive que usar "EntityState.Detached" em vez de apenas "detached" ...
Marcelo Myara 31/10/2013
22
Hmm tentei sua solução, mas para mim isDetached é verdadeiro, mas ainda o mesmo erro quando tento anexar a entrada ao contexto
Prokurors
Excelente abordagem. Até funcionou com meu Repositório Genérico.
ATHER
5
@Prokurors Recentemente descobri que a razão pela qual a verificação de Mosh nem sempre é suficiente para evitar o erro, é que .Include()as propriedades de navegação 'ed também tentam ser anexadas quando você chama .Attachou as define EntityStatecomo EntityState.Unchanged- e elas entrarão em conflito se qualquer uma das entidades se referir ao mesma entidade. Não descobri como anexar apenas a entidade base, então tive que redesenhar um pouco o projeto, para usar contextos separados para cada "transação comercial", como foi projetado. Vou anotar isso para projetos futuros.
Aske B.
18

Experimente este método de extensão (não testado e improvisado):

public static bool IsAttachedTo(this ObjectContext context, object entity) {
    if(entity == null) {
        throw new ArgumentNullException("entity");
    }
    ObjectStateEntry entry;
    if(context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) {
        return (entry.State != EntityState.Detached);
    }
    return false;
}

Dada a situação que você descreve em sua edição, pode ser necessário usar a seguinte sobrecarga que aceita um em EntityKeyvez de um objeto:

public static bool IsAttachedTo(this ObjectContext, EntityKey key) {
    if(key == null) {
        throw new ArgumentNullException("key");
    }
    ObjectStateEntry entry;
    if(context.ObjectStateManager.TryGetObjectStateEntry(key, out entry)) {
        return (entry.State != EntityState.Detached);
    }
    return false;
}

Para construir um EntityKeyem sua situação, use o seguinte como diretriz:

EntityKey key = new EntityKey("MyEntities.User", "Id", 1);

Você pode obter o EntityKeyde uma instância existente de Userusando a propriedade User.EntityKey(da interface IEntityWithKey).

Jason
fonte
Isso é muito bom, mas não funciona para mim na minha situação ... Vou atualizar a pergunta com detalhes. ps você quer bool não booleano e estático, mas diferente daquele método de extensão incrível!
joshcomley
@joshcomley: Acho que você pode usar uma sobrecarga de TryGetObjectStateEntryque aceita um em EntityKeyvez de um object. Eu editei em conformidade. Avise-me se isso não ajudar e voltaremos à prancheta.
Jason
Acabei de ver isso - estava trabalhando em uma solução descrita em uma resposta que acabei de postar. 1 por sua ajuda e dicas !!
joshcomley
Qualquer ideia de por que estou recebendo um erro, os detalhes estão aqui stackoverflow.com/questions/6653050/…
Joper
6

Usando a chave de entidade do objeto que você está tentando verificar:

var entry = context.ObjectStateManager.GetObjectStateEntry("EntityKey");
if (entry.State == EntityState.Detached)
{
  // Do Something
}

Bondade,

Dan

Daniel Elliott
fonte
0

Isso não responde diretamente à pergunta dos OPs, mas foi assim que resolvi a minha.

Isso é para aqueles que estão usando em DbContextvez de ObjectContext.

    public TEntity Retrieve(object primaryKey)
    {
        return DbSet.Find(primaryKey);
    }

Método DbSet.Find :

Encontra uma entidade com os valores de chave primária fornecidos. Se uma entidade com os valores de chave primária fornecidos existir no contexto, ela será retornada imediatamente, sem fazer uma solicitação à loja. Caso contrário, uma solicitação é feita à loja para uma entidade com os valores de chave primária fornecidos e essa entidade, se encontrada, é anexada ao contexto e retornada. Se nenhuma entidade for encontrada no contexto ou no armazenamento, será retornado nulo.

Basicamente, ele retorna o objeto anexado do dado, primaryKeyentão você só precisa aplicar as alterações no objeto retornado para manter a instância correta.

Lawrence
fonte