Actualizar fila si existe otra lógica de inserción con Entity Framework

179

¿Alguien tiene sugerencias sobre la forma más eficiente de implementar la lógica de "fila de actualización si existe otra inserción" usando Entity Framework?

Jonathan Wood
fuente
2
Esto es algo que debe hacerse a nivel de motor de base de datos, en un procedimiento almacenado. De lo contrario, tendrá que ajustar la detección / actualización / inserción en una transacción.
Stephen Chung
1
@Stephen: Esto, de hecho, es lo que terminé haciendo. Gracias.
Jonathan Wood
Jonathan, tu pregunta es muy útil para mí. ¿Por qué cambiaste a un procedimiento almacenado?
anar khalilov
2
@Anar: Fue más fácil y espero mucho más eficiente.
Jonathan Wood
¿Tiene que escribir un procedimiento almacenado para cada tabla?
tofutim

Respuestas:

174

Si está trabajando con un objeto adjunto (objeto cargado desde la misma instancia del contexto) simplemente puede usar:

if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
    context.MyEntities.AddObject(myEntity);
}

// Attached object tracks modifications automatically

context.SaveChanges();

Si puede usar algún conocimiento sobre la clave del objeto, puede usar algo como esto:

if (myEntity.Id != 0)
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

Si no puede decidir la existencia del objeto por su Id, debe ejecutar la consulta de búsqueda:

var id = myEntity.Id;
if (context.MyEntities.Any(e => e.Id == id))
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();
Ladislav Mrnka
fuente
Gracias. Parece lo que necesito. ¿Puedo hacerte una pregunta que me ha estado molestando por un tiempo? Normalmente, pongo mi contexto en un usingbloque corto . ¿Está bien dejar el contexto en la memoria por un tiempo? Por ejemplo, durante la vida de un formulario de Windows? Normalmente intento limpiar los objetos de la base de datos para asegurar una carga mínima en la base de datos. ¿No hay problema esperando para destruir mi contexto EF?
Jonathan Wood el
Mira esto: stackoverflow.com/questions/3653009/… el contexto del objeto debe ser lo más breve posible, pero en el caso de winforms o wpf, esto puede significar que el contexto dura tanto como el presentador. La pregunta vinculada contiene un enlace al artículo de msdn sobre el uso de la sesión nhibernate en winforms. Se puede usar el mismo enfoque para el contexto.
Ladislav Mrnka
1
Pero, ¿qué pasa si necesito hacer esto con una lista de objetos ... en mi base de datos hay una lista de filas con la misma identificación y quiero reemplazarla si existe o insertarla si no ... ¿cómo lo hago? ¡Gracias!
Phoenix_uy
1
Esta respuesta se ve increíble, pero me encuentro con este problema en la actualización: ya existe un objeto con la misma clave en ObjectStateManager. ObjectStateManager no puede rastrear varios objetos con la misma clave.
John Zumbrum
1
Parece que estaba teniendo un pequeño problema con la recuperación del objeto existente para recuperar su clave antes de realizar la actualización; separar ese objeto de búsqueda primero ayudó a solucionarlo.
John Zumbrum
33

A partir de Entity Framework 4.3, hay un AddOrUpdatemétodo en el espacio de nombres System.Data.Entity.Migrations:

public static void AddOrUpdate<TEntity>(
    this IDbSet<TEntity> set,
    params TEntity[] entities
)
where TEntity : class

que por el doc :

Agrega o actualiza entidades por clave cuando se llama SaveChanges. Equivalente a una operación "upsert" de la terminología de la base de datos. Este método puede ser útil al sembrar datos usando Migraciones.


Para responder al comentario de @ Smashing1978 , pegaré las partes relevantes del enlace proporcionado por @Colin

El trabajo de AddOrUpdate es garantizar que no se creen duplicados cuando se siembran datos durante el desarrollo.

Primero, ejecutará una consulta en su base de datos en busca de un registro donde lo que haya suministrado como clave (primer parámetro) coincida con el valor de la columna asignada (o los valores) proporcionados en AddOrUpdate. Por lo tanto, esto es un poco flojo para la coincidencia pero perfectamente bien para sembrar datos de tiempo de diseño.

Más importante aún, si se encuentra una coincidencia, la actualización actualizará todas y anulará las que no estaban en su AddOrUpdate.

Dicho esto, tengo una situación en la que estoy extrayendo datos de un servicio externo e insertando o actualizando valores existentes por clave principal (y mis datos locales para consumidores son de solo lectura), los he estado utilizando AddOrUpdateen producción durante más de 6 meses. Hasta ahora no hay problemas.

Erki M.
fuente
77
El espacio de nombres System.Data.Entity.Migrations contiene clases relacionadas con migraciones basadas en código y sus configuraciones. ¿Hay alguna razón por la que no deberíamos usar esto en nuestros repositorios para AddOrUpdates de entidades que no son de migración?
Matt Lengenfelder
10
Tenga cuidado con el método AddOrUpdate: thedatafarm.com/data-access/…
Colin
1
Este artículo describe por qué AddOrUpdate no debe usarse michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique
11

La magia ocurre cuando se llama SaveChanges()y depende de la corriente EntityState. Si la entidad tiene un EntityState.Added, se agregará a la base de datos, si tiene un EntityState.Modified, se actualizará en la base de datos. Entonces puede implementar un InsertOrUpdate()método de la siguiente manera:

public void InsertOrUpdate(Blog blog) 
{ 
    using (var context = new BloggingContext()) 
    { 
        context.Entry(blog).State = blog.BlogId == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 

        context.SaveChanges(); 
    } 
}

Más acerca de EntityState

Si no puede verificar Id = 0si es una entidad nueva o no, verifique la respuesta de Ladislav Mrnka .

Apilado
fuente
8

Si sabe que está utilizando el mismo contexto y no está separando ninguna entidad, puede hacer una versión genérica como esta:

public void InsertOrUpdate<T>(T entity, DbContext db) where T : class
{
    if (db.Entry(entity).State == EntityState.Detached)
        db.Set<T>().Add(entity);

    // If an immediate save is needed, can be slow though
    // if iterating through many entities:
    db.SaveChanges(); 
}

db Por supuesto, puede ser un campo de clase, o el método puede hacerse estático y una extensión, pero esto es lo básico.

calentar
fuente
4

La respuesta de Ladislav fue cercana, pero tuve que hacer un par de modificaciones para que esto funcione en EF6 (base de datos primero). Extendí mi contexto de datos con mi método AddOrUpdate y hasta ahora parece estar funcionando bien con objetos separados:

using System.Data.Entity;

[....]

public partial class MyDBEntities {

  public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) {
      if (ID != 0) {
          set.Attach(obj);
          ctx.Entry(obj).State = EntityState.Modified;
      }
      else {
          set.Add(obj);
      }
  }
[....]
cdonner
fuente
AddOrUpdate también existe como un método de extensión en System.Data.Entity.Migrations, por lo que si fuera usted, evitaría reutilizar el mismo nombre de método para su propio método.
AFract
2

En mi opinión, vale la pena decir que con el recientemente lanzado EntityGraphOperations para Entity Framework Code First , puede evitar escribir algunos códigos repetitivos para definir los estados de todas las entidades en el gráfico. Soy el autor de este producto. Y lo he publicado en el github , code-project ( incluye una demostración paso a paso y un proyecto de muestra está listo para descargar) y nuget .

Se ajusta automáticamente el estado de las entidades a Addedo Modified. Y elegirá manualmente qué entidades deben eliminarse si ya no existe.

El ejemplo:

Digamos que tengo un Personobjeto. Personpodría tener muchos teléfonos, un documento y podría tener un cónyuge.

public class Person
{
     public int Id { get; set; }
     public string FirstName { get; set; }
     public string LastName { get; set; }
     public string MiddleName { get; set; }
     public int Age { get; set; }
     public int DocumentId {get; set;}

     public virtual ICollection<Phone> Phones { get; set; }
     public virtual Document Document { get; set; }
     public virtual PersonSpouse PersonSpouse { get; set; }
}

Quiero determinar el estado de todas las entidades que se incluye en el gráfico.

context.InsertOrUpdateGraph(person)
       .After(entity =>
       {
            // Delete missing phones.
            entity.HasCollection(p => p.Phones)
               .DeleteMissingEntities();

            // Delete if spouse is not exist anymore.
            entity.HasNavigationalProperty(m => m.PersonSpouse)
                  .DeleteIfNull();
       });

Además, como sabe, las propiedades clave únicas podrían desempeñar un papel al definir el estado de la entidad Phone. Para tales propósitos especiales tenemos ExtendedEntityTypeConfiguration<>clase, que hereda de EntityTypeConfiguration<>. Si queremos usar tales configuraciones especiales, entonces debemos heredar nuestras clases de mapeo ExtendedEntityTypeConfiguration<>, en lugar de EntityTypeConfiguration<>. Por ejemplo:

public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
    {
        public PhoneMap()
        {
             // Primary Key
             this.HasKey(m => m.Id);
              
             // Unique keys
             this.HasUniqueKey(m => new { m.Prefix, m.Digits });
        }
    }

Eso es todo.

Farhad Jabiyev
fuente
2

Insertar más actualizar ambos

public void InsertUpdateData()
{
//Here TestEntities is the class which is given from "Save entity connection setting in web.config"
TestEntities context = new TestEntities();

var query = from data in context.Employee
            orderby data.name
            select data;

foreach (Employee details in query)
{
    if (details.id == 1)
    {
        //Assign the new values to name whose id is 1
        details.name = "Sanjay";
        details. Surname="Desai";
        details.address=" Desiwadi";
    }
    else if(query==null)
    {
        details.name="Sharad";
        details.surname=" Chougale ";
        details.address=" Gargoti";
    }
}

//Save the changes back to database.
context.SaveChanges();
}
Sharad Chougale
fuente
Utilicé este enfoque, pero verifiqué (después de primero o predeterminado) if (query == null)
Patrick
2

Verifique la fila existente con Cualquiera.

    public static void insertOrUpdateCustomer(Customer customer)
    {
        using (var db = getDb())
        {

            db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
            db.SaveChanges();

        }

    }
Ali Osman Yavuz
fuente
1

Alternativa para la respuesta @LadislavMrnka. Esto si para Entity Framework 6.2.0.

Si tiene un elemento específico DbSety un elemento que necesita actualizarse o crearse:

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
    _dbContext.Names.Add(name);
}
else
{
    _dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();

Sin embargo, esto también se puede usar para un genérico DbSetcon una sola clave primaria o una clave primaria compuesta.

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
    foreach (var value in values)
    {
        try
        {
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            {
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            }

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        }
        catch
        {
            throw;
        }
    }
    _dbContext.SaveChanges();
}

public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    {
        someDbSet.Add(value);
    }
    else
    {
        Entry(current).CurrentValues.SetValues(value);
    }
}
Ogglas
fuente
-1

Corregido

public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity) 
        where T : class
        where T2 : class
        {
            foreach(var e in updateEntity)
            {
                context.Set<T2>().InsertOrUpdate(e);
            }
        }


        public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity) 
        where T : class
        where T2 : class
        {
            if (context.Entry(updateEntity).State == EntityState.Detached)
            {
                if (context.Set<T2>().Any(t => t == updateEntity))
                {
                   context.Set<T2>().Update(updateEntity); 
                }
                else
                {
                    context.Set<T2>().Add(updateEntity);
                }

            }
            context.SaveChanges();
        }
Vadim Rychkow
fuente
2
Utilice editar en lugar de publicar otra respuesta
Suraj Rao