Alguém tem sugestões sobre a maneira mais eficiente de implementar a lógica "atualizar linha, se existir outra inserção" usando o Entity Framework?
Alguém tem sugestões sobre a maneira mais eficiente de implementar a lógica "atualizar linha, se existir outra inserção" usando o Entity Framework?
Respostas:
Se você estiver trabalhando com um objeto anexado (objeto carregado da mesma instância do contexto), poderá simplesmente usar:
if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
context.MyEntities.AddObject(myEntity);
}
// Attached object tracks modifications automatically
context.SaveChanges();
Se você pode usar algum conhecimento sobre a chave do objeto, pode usar algo como isto:
if (myEntity.Id != 0)
{
context.MyEntities.Attach(myEntity);
context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
context.MyEntities.AddObject(myEntity);
}
context.SaveChanges();
Se você não pode decidir a existência do objeto por seu ID, deve executar a consulta de pesquisa:
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
bloco. Tudo bem deixar o contexto na memória por um tempo? Por exemplo, durante a vida de um formulário do Windows? Normalmente, eu tento limpar os objetos do banco de dados para garantir uma carga mínima no banco de dados. Não há nenhum problema esperando para destruir meu contexto de EF?
No Entity Framework 4.3, existe um AddOrUpdate
método no espaço para nome System.Data.Entity.Migrations
:
public static void AddOrUpdate<TEntity>(
this IDbSet<TEntity> set,
params TEntity[] entities
)
where TEntity : class
qual pelo doc :
Adiciona ou atualiza entidades por chave quando SaveChanges é chamado. Equivalente a uma operação "upsert" da terminologia do banco de dados. Este método pode ser útil ao propagar dados usando Migrações.
Para responder ao comentário de @ Smashing1978 , colarei partes relevantes do link fornecido por @Colin
O trabalho do AddOrUpdate é garantir que você não crie duplicatas quando semear dados durante o desenvolvimento.
Primeiro, ele executará uma consulta no banco de dados procurando um registro em que tudo o que você forneceu como chave (primeiro parâmetro) corresponde ao valor (ou valores) da coluna mapeada fornecida no AddOrUpdate. Portanto, esse é um pouco confuso para correspondência, mas perfeitamente adequado para semear dados de tempo de design.
Mais importante, se uma correspondência for encontrada, a atualização atualizará tudo e anulará as que não estavam no seu AddOrUpdate.
Dito isso, tenho uma situação em que estou extraindo dados de um serviço externo e inserindo ou atualizando valores existentes por chave primária (e meus dados locais para consumidores são somente leitura) - uso AddOrUpdate
na produção há mais de 6 meses agora sem problemas.
A mágica acontece ao ligar SaveChanges()
e depende da corrente EntityState
. Se a entidade tiver um EntityState.Added
, ele será adicionado ao banco de dados, se tiver um EntityState.Modified
, será atualizado no banco de dados. Então você pode implementar um InsertOrUpdate()
método da seguinte maneira:
public void InsertOrUpdate(Blog blog)
{
using (var context = new BloggingContext())
{
context.Entry(blog).State = blog.BlogId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
Se você não pode verificar Id = 0
para determinar se é uma nova entidade ou não, verifique a resposta de Ladislav Mrnka .
Se você sabe que está usando o mesmo contexto e não desanexando nenhuma entidade, é possível criar uma versão 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
é claro que pode ser um campo de classe ou o método pode ser feito estático e uma extensão, mas esse é o básico.
A resposta de Ladislav foi próxima, mas tive que fazer algumas modificações para que isso funcionasse no EF6 (primeiro no banco de dados). Estendi meu contexto de dados com meu método AddOrUpdate e até agora isso parece estar funcionando bem com objetos desanexados:
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);
}
}
[....]
Na minha opinião, vale dizer que, com o recém-lançado EntityGraphOperations for Entity Framework Code Primeiro, você pode evitar escrever alguns códigos repetitivos para definir os estados de todas as entidades no gráfico. Eu sou o autor deste produto. E publiquei no github , code-project ( inclui uma demonstração passo a passo e um projeto de amostra está pronto para download) e nuget .
Ele definirá automaticamente o estado das entidades como Added
ou Modified
. E você escolherá manualmente quais entidades devem ser excluídas se não existir mais.
O exemplo:
Digamos que eu tenha um Person
objeto. Person
poderia ter muitos telefones, um documento e poderia ter um cônjuge.
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; }
}
Desejo determinar o estado de todas as entidades incluídas no 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();
});
Além disso, como você sabe, as propriedades-chave exclusivas podem desempenhar um papel ao definir o estado da entidade Telefone. Para tais propósitos especiais, temos ExtendedEntityTypeConfiguration<>
classe, que herda de EntityTypeConfiguration<>
. Se queremos usar essas configurações especiais, devemos herdar nossas classes de mapeamento ExtendedEntityTypeConfiguration<>
, em vez de EntityTypeConfiguration<>
. Por exemplo:
public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
{
public PhoneMap()
{
// Primary Key
this.HasKey(m => m.Id);
…
// Unique keys
this.HasUniqueKey(m => new { m.Prefix, m.Digits });
}
}
Isso é tudo.
Inserir outra atualização
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 a linha existente com Qualquer.
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 a resposta @LadislavMrnka. Isso se for para o Entity Framework 6.2.0.
Se você tiver um DbSet
item específico e um que precise ser atualizado ou criado:
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();
No entanto, isso também pode ser usado para um genérico DbSet
com uma única chave primária ou uma chave primária composta.
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);
}
}
Corrigido
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();
}