Atualizar linha se existir outra lógica de inserção com o Entity Framework

179

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?

Jonathan Wood
fonte
2
Isso é algo que deve ser feito no nível do mecanismo de banco de dados, em um procedimento armazenado. Caso contrário, você precisará envolver a detecção / atualização / inserção em uma transação.
Stephen Chung
1
@ Stephen: Isso, de fato, foi o que acabei fazendo. Obrigado.
Jonathan Madeira
Jonathan, sua pergunta é muito útil para mim. Por que você mudou para um procedimento armazenado?
Anar khalilov
2
@Anar: Foi apenas mais fácil e espero muito mais eficiente.
Jonathan Wood
Você precisa escrever um procedimento armazenado para todas as tabelas?
tofutim

Respostas:

174

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();
Ladislav Mrnka
fonte
Obrigado. Parece o que eu preciso. Posso fazer uma pergunta que me incomoda há um tempo? Normalmente, coloco meu contexto em um pequeno usingbloco. 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?
Jonathan Madeira
Verifique isto: stackoverflow.com/questions/3653009/… O contexto do objeto deve permanecer o mais curto possível, mas no caso de winforms ou wpf, isso pode significar que o contexto permanece o tempo que o apresentador. A questão vinculada contém um link para o artigo msdn sobre o uso da sessão nhibernate nos winforms. A mesma abordagem pode ser usada para o contexto.
Ladislav Mrnka
1
Mas e se eu precisar fazer isso com uma lista de objetos ... no meu banco de dados, há uma lista de linhas com o mesmo ID e eu quero substituir se elas existirem ou inserir se não existirem .. como eu faço isso? obrigado!
Phoenix_uy 14/10
1
Esta resposta parece incrível, mas estou enfrentando esse problema na atualização: já existe um objeto com a mesma chave no ObjectStateManager. O ObjectStateManager não pode rastrear vários objetos com a mesma chave.
21812 John Zumbrum
1
Parece que eu estava tendo um pouco de problema ao buscar o objeto existente para recuperar sua chave antes de fazer a atualização; desanexar esse objeto de pesquisa primeiro ajudou a corrigi-lo.
21812 John Zumbrum
33

No Entity Framework 4.3, existe um AddOrUpdatemé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 AddOrUpdatena produção há mais de 6 meses agora sem problemas.

Erki M.
fonte
7
O espaço para nome System.Data.Entity.Migrations contém classes relacionadas a migrações baseadas em código e suas configurações. Existe alguma razão pela qual não devemos usar isso em nossos repositórios para a entidade de não migração AddOrUpdates?
Matt Lengenfelder
10
Tome cuidado com o método AddOrUpdate: thedatafarm.com/data-access/...
Colin
1
Este artigo descreve por que o AddOrUpdate não deve ser usado michaelgmccarthy.com/2016/08/24/…
Nolmë Informatique 15/10/1919
11

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

Mais sobre EntityState

Se você não pode verificar Id = 0para determinar se é uma nova entidade ou não, verifique a resposta de Ladislav Mrnka .

Empilhados
fonte
8

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.

ciscoheat
fonte
4

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);
      }
  }
[....]
cdonner
fonte
AddOrUpdate também existe como um método de extensão em System.Data.Entity.Migrations, portanto, se eu fosse você, evitaria reutilizar o mesmo nome de método para seu próprio método.
24718 AFract
2

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 Addedou Modified. E você escolherá manualmente quais entidades devem ser excluídas se não existir mais.

O exemplo:

Digamos que eu tenha um Personobjeto. Personpoderia 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.

Farhad Jabiyev
fonte
2

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();
}
Sharad Chougale
fonte
Eu usei esta abordagem, mas e verificado (após a primeira ou padrão) se (query == null)
Patrick
2

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

        }

    }
Ali Osman Yavuz
fonte
1

Alternativa para a resposta @LadislavMrnka. Isso se for para o Entity Framework 6.2.0.

Se você tiver um DbSetitem 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 DbSetcom 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);
    }
}
Ogglas
fonte
-1

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();
        }
Vadim Rychkow
fonte
2
Utilize editar em vez de postar uma outra resposta
Suraj Rao