¿Es posible verificar si un objeto ya está adjunto a un contexto de datos en Entity Framework?

86

Recibo el siguiente error al intentar adjuntar un objeto que ya está adjunto a un contexto dado a través de context.AttachTo(...):

Ya existe un objeto con la misma clave en ObjectStateManager. ObjectStateManager no puede rastrear varios objetos con la misma clave.

¿Hay alguna forma de lograr algo en la línea de:

context.IsAttachedTo(...)

¡Salud!

Editar:

El método de extensión que describió Jason está cerca, pero no funciona para mi situación.

Estoy tratando de trabajar un poco usando el método descrito en la respuesta a otra pregunta:

¿Cómo elimino una o más filas de mi tabla usando Linq to Entities * sin * recuperar las filas primero?

Mi código se parece un poco a esto:

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

Esto funciona bien, excepto cuando hago algo más para ese usuario donde uso el mismo método e intento adjuntar un Userobjeto ficticio . Esto falla porque he adjuntado previamente ese objeto de usuario ficticio. ¿Cómo puedo verificar esto?

Joshcomley
fuente

Respuestas:

57

Esto es lo que terminé con, que funciona muy bien:

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

Puede llamarlo de la siguiente manera:

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

Esto funciona muy bien porque es como, context.AttachTo(...)excepto que puedes usar el truco de identificación que mencioné anteriormente cada vez. Terminas con el objeto previamente adjunto o con tu propio objeto adjunto. Llamar CreateEntityKeyal contexto asegura que sea agradable y genérico y que funcionará incluso con claves compuestas sin más codificación (¡porque EF ya puede hacer eso por nosotros!).

Joshcomley
fuente
Estaba teniendo un problema similar, y esto acaba de resolver el mío: ¡brillante, salud! +1
RPM1984
4
Sería incluso mejor cuando el parámetro de cadena se reemplaza por una función de selección para la colección a la que pertenece la entidad.
Jasper
1
¿Alguna idea de por qué a (T)entry.Entityveces devuelve nulo?
Tr1stan
1
No puedo averiguar en qué debería configurar mi entitySetName. Sigo recibiendo una excepción. Estoy realmente frustrado porque todo lo que quiero hacer es eliminar un usuario con el que no quiero tener que lidiar con tantas tonterías ocultas que hacen explotar mi aplicación.
Julie
1
si Tya lo está IEntityWithKey, ¿no puede simplemente usar su entity.EntityKeypropiedad en lugar de reconstruirlo o necesita adivinar / proporcionar el EntitySetName?
drzaus
54

Un enfoque más simple es:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
fuente
1
Esto funcionó para mí, sólo tenía que usar "EntityState.Detached" en lugar de simplemente "desprendido" ...
Marcelo Myara
22
Hmm probé Su solución, pero para mí isDetached es cierto, pero sigue siendo el mismo error cuando intento Adjuntar la entrada al contexto
Prokurors
Excelente appraoch. Incluso funcionó con mi repositorio genérico.
ATHER
5
@Prokurors Hace poco supe la razón cheque de Mosh no siempre es suficiente para evitar el error, es que .Include()'las propiedades de navegación ed también se intentaron unir cuando se llama .Attacho establece el EntityStatea EntityState.Unchanged- y van a entrar en conflicto si alguna de las entidades se refieren a la misma entidad. No he descubierto cómo adjuntar solo la entidad base, por lo que tuve que rediseñar un poco el proyecto, para usar contextos separados para cada "transacción comercial", como fue diseñado. Tomaré nota de esto para proyectos futuros.
Pregunte B.
18

Pruebe este método de extensión (esto no ha sido probado y es 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 la situación que describe en su edición, es posible que deba usar la siguiente sobrecarga que acepta EntityKeyun objeto en lugar de un:

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 un EntityKeyen su situación, use lo siguiente como guía:

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

Puede obtener el EntityKeyde una instancia existente de Userutilizando la propiedad User.EntityKey(desde la interfaz IEntityWithKey).

jason
fuente
Esto es muy bueno, pero no me funciona en mi situación ... Actualizaré la pregunta con detalles. ps, quieres bool no booleano y estático, ¡pero aparte de ese método de extensión bastante impresionante!
Joshcomley
@joshcomley: Creo que puedes abordar usando una sobrecarga de TryGetObjectStateEntryque acepta un en EntityKeylugar de un object. He editado en consecuencia. Avísame si esto no ayuda y volveremos a la mesa de dibujo.
Jason
Acabo de ver esto: estaba trabajando en una solución descrita en una respuesta que acabo de publicar. +1 por tu ayuda y consejos !!
Joshcomley
Cualquier idea de por qué recibo un error, los detalles están aquí stackoverflow.com/questions/6653050/…
Joper
6

Usando la clave de entidad del objeto que está intentando verificar:

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

Amabilidad,

Dan

Daniel Elliott
fuente
0

Esto no responde directamente a la pregunta de OP, pero así es como resolví la mía.

Esto es para aquellos que están usando en DbContextlugar de ObjectContext.

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

Método DbSet.Find :

Busca una entidad con los valores de clave principal dados. Si existe una entidad con los valores de clave primaria dados en el contexto, se devuelve inmediatamente sin realizar una solicitud a la tienda. De lo contrario, se realiza una solicitud a la tienda de una entidad con los valores de clave primaria dados y esta entidad, si se encuentra, se adjunta al contexto y se devuelve. Si no se encuentra ninguna entidad en el contexto o en la tienda, se devuelve un valor nulo.

Básicamente, devuelve el objeto adjunto del dado, primaryKeypor lo que solo necesita aplicar los cambios en el objeto devuelto para mantener la instancia correcta.

Lawrence
fuente