¿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?
¿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?
Respuestas:
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();
using
bloque 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?
A partir de Entity Framework 4.3, hay un AddOrUpdate
mé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 AddOrUpdate
en producción durante más de 6 meses. Hasta ahora no hay problemas.
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();
}
}
Si no puede verificar Id = 0
si es una entidad nueva o no, verifique la respuesta de Ladislav Mrnka .
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.
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);
}
}
[....]
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 Added
o Modified
. Y elegirá manualmente qué entidades deben eliminarse si ya no existe.
El ejemplo:
Digamos que tengo un Person
objeto. Person
podrí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.
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();
}
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();
}
}
Alternativa para la respuesta @LadislavMrnka. Esto si para Entity Framework 6.2.0.
Si tiene un elemento específico DbSet
y 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 DbSet
con 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);
}
}
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();
}