Como atualizar o registro usando o Entity Framework 6?

245

Estou tentando atualizar o registro usando o EF6. Primeiro localizando o registro, se existir, atualize-o. Aqui está o meu código: -

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

Sempre que tento atualizar o registro usando o código acima, estou recebendo este erro: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: a declaração de atualização, inserção ou exclusão de armazenamento afetou um número inesperado de linhas (0). As entidades podem ter sido modificadas ou excluídas desde que as entidades foram carregadas. Atualizar entrada do ObjectStateManager

user1327064
fonte
7
Nota lateral: catch (Exception ex){throw;}é redundante e você pode removê-lo completamente.
Sriram Sakthivel
try catch block é apenas para descobrir o motivo da falha. Mas ainda não entendi por que esse código está falhando?
user1327064
2
Não sou especialista neste tópico, não posso responder a esta pergunta. mas sem o try catch, você também pode usar o recurso break quando a exceção é lançada para interromper o depurador quando houver uma exceção.
Sriram Sakthivel
1
Você não mudou nada. Jogar com o estado da entidade não altera o fato de que o objeto não foi realmente modificado.
Jonathan Allen
1
Bem, fiz o mesmo que você e não recebi o erro. A exceção diz DbUpdateConcurrencyException. Como você lidou com a simultaneidade? Você usou um carimbo de data / hora, clonou e depois mesclou os objetos novamente ou usou entidades de rastreamento automático? (3 abordagens mais usadas). Se você não lidou com a concorrência, acho que esse é o problema.
El Mac

Respostas:

344

Você está tentando atualizar o registro (o que para mim significa "alterar um valor em um registro existente e salvá-lo"). Portanto, você precisa recuperar o objeto, fazer uma alteração e salvá-lo.

using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue = "Some new value";
        db.SaveChanges();
    }
}
Craig W.
fonte
16
Atribuir o valor não atualiza o banco de dados, a chamada db.SaveChanges()com objetos modificados no contexto atualiza o banco de dados.
Craig W.
6
Ainda assim, me fascina ... então o resultado var fica realmente conectado ao dbcontext ... então isso significa que qualquer variável instanciada por qualquer membro do dbcontext terá esse associado ao banco de dados para que quaisquer alterações sejam aplicadas a essa variável , também é aplicado ou persistido?
WantIt
6
Como o contexto gerou o objeto, o contexto pode rastrear o objeto, incluindo alterações no objeto. Quando você chama SaveChangeso contexto, avalia todos os objetos que está rastreando para determinar se eles foram adicionados, alterados ou excluídos e emite o SQL apropriado no banco de dados conectado.
Craig W.
3
Estou enfrentando o mesmo problema - usando EF6, tentando atualizar uma entidade. Anexar + EntityState.Modified não está funcionando. A única coisa que funciona é - você precisa recuperar o objeto, fazer as alterações desejadas e salvá-lo via db.SaveChanges ();
Gurpreet Singh
7
NÃO é necessário recuperar o objeto primeiro para atualizá-lo. Eu tive o mesmo problema até perceber que estava tentando alterar um dos valores da chave primária (chave composta). Desde que você forneça uma chave primária correta, é possível definir o EntityState como Modified e SaveChanges () funcionará, desde que você não quebre nenhuma outra restrição de integridade definida na tabela.
adrianz
165

Estive revisando o código-fonte do Entity Framework e encontrei uma maneira de realmente atualizar uma entidade se você conhecer a propriedade Key:

public void Update<T>(T item) where T: Entity
{
    // assume Entity base class have an Id property for all items
    var entity = _collection.Find(item.Id);
    if (entity == null)
    {
        return;
    }

    _context.Entry(entity).CurrentValues.SetValues(item);
}

Caso contrário, verifique o implementação AddOrUpdate para obter idéias.

Espero que esta ajuda!

Miguel
fonte
12
Agradável! Não há necessidade de enumerar todas as propriedades. Presumo que a SaveChanges()chamada seja necessária após a definição dos valores.
Jan Zahradník
3
Sim, as alterações serão mantidas em SaveChanges ()
Miguel
1
Ótima resposta, não ficou claro com o IntelliSense que fazer algo assim NÃO funcionaria: _context.MyObj = newObj; SaveChanges () ou .... _context.MyObj.Update (newObj) e SaveChanges (); Sua solução atualiza o objeto inteiro sem precisar percorrer todas as propriedades.
Adam
7
Isso se queixa de que estou tentando editar o campo ID
Vasily Hall
3
@VasilyHall - isso ocorre se os campos de ID (ou o que você definiu como Chave Primária) forem diferentes entre os modelos (incluindo nulo / 0 em um dos modelos). Verifique se os IDs correspondem entre os dois modelos e eles serão atualizados corretamente.
Gavin Coates
51

Você pode usar o AddOrUpdatemétodo:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
nicedev80
fonte
1
Melhor solução da OMI
Norgul
112
.AddOrUpdate()é usado durante a migração do banco de dados, é altamente desencorajado usar esse método fora das migrações, por isso, está no Entity.Migrationsespaço para nome.
Adam Vincent
1
Como o @AdamVincent disse, o AddOrUpdate()método é destinado a migrações e não é adequado para situações em que você precisa apenas atualizar a linha existente. Caso você não tenha um livro com referência de pesquisa (ou seja, ID), ele criará uma nova linha e poderá ser um problema nos próximos casos (por exemplo, você terá uma API que precisará retornar sua resposta 404-NotFound se você tente chamar o método PUT para a linha inexistente).
18717 Marko
4
Não use isso, a menos que você saiba o que está fazendo !!!!!!!!!!!!!!!! Leia: michaelgmccarthy.com/2016/08/24/…
Yusha
4
Voltei para isso de novo hoje, posso apenas avisá-lo tudo o que este é não uma boa solução para o caso de uso desejado
Yusha
23

Então você tem uma entidade que é atualizada e deseja atualizá-la no banco de dados com a menor quantidade de código ...

A simultaneidade é sempre complicada, mas suponho que você deseja que suas atualizações sejam vencidas. Aqui está como eu fiz isso no meu mesmo caso e modifiquei os nomes para imitar suas aulas. Em outras palavras, basta mudar attachpara adde funciona para mim:

public static void SaveBook(Model.Book myBook)
{
    using (var ctx = new BookDBContext())
    {
        ctx.Books.Add(myBook);
        ctx.Entry(myBook).State = System.Data.Entity.EntityState.Modified;
        ctx.SaveChanges();
    }
}
Duray Akar
fonte
10

Você deve usar o método Entry () para atualizar todos os campos do seu objeto. Lembre-se também de que você não pode alterar o ID do campo (chave); portanto, primeiro defina o ID para o mesmo que você edita.

using(var context = new ...())
{
    var EditedObj = context
        .Obj
        .Where(x => x. ....)
        .First();

    NewObj.Id = EditedObj.Id; //This is important when we first create an object (NewObj), in which the default Id = 0. We can not change an existing key.

    context.Entry(EditedObj).CurrentValues.SetValues(NewObj);

    context.SaveChanges();
}
Jarek
fonte
2
Você deve pelo menos tentar responder à pergunta, não apenas publicar o código #
StaceyGirl 17/17/17
Faça uma explicação para a pergunta, em vez de apenas deixar um trecho de código para ajudar a melhor a fazer a pergunta.
feanor07
9

Esse código é o resultado de um teste para atualizar apenas um conjunto de colunas sem fazer uma consulta para retornar o registro primeiro. Ele usa o código do Entity Framework 7 primeiro.

// This function receives an object type that can be a view model or an anonymous 
// object with the properties you want to change. 
// This is part of a repository for a Contacts object.

public int Update(object entity)
{
    var entityProperties =  entity.GetType().GetProperties();   
    Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

    if (con != null)
    {
        _context.Entry(con).State = EntityState.Modified;
        _context.Contacts.Attach(con);

        foreach (var ep in entityProperties)
        {
            // If the property is named Id, don't add it in the update. 
            // It can be refactored to look in the annotations for a key 
            // or any part named Id.

            if(ep.Name != "Id")
                _context.Entry(con).Property(ep.Name).IsModified = true;
        }
    }

    return _context.SaveChanges();
}

public static object ToType<T>(object obj, T type)
{
    // Create an instance of T type object
    object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

    // Loop through the properties of the object you want to convert
    foreach (PropertyInfo pi in obj.GetType().GetProperties())
    {
        try
        {
            // Get the value of the property and try to assign it to the property of T type object
            tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
        }
        catch (Exception ex)
        {
            // Logging.Log.Error(ex);
        }
    }
    // Return the T type object:         
    return tmp;
}

Aqui está o código completo:

public interface IContactRepository
{
    IEnumerable<Contacts> GetAllContats();
    IEnumerable<Contacts> GetAllContactsWithAddress();
    int Update(object c);
}

public class ContactRepository : IContactRepository
{
    private ContactContext _context;

    public ContactRepository(ContactContext context)
    {
        _context = context;
    }

    public IEnumerable<Contacts> GetAllContats()
    {
        return _context.Contacts.OrderBy(c => c.FirstName).ToList();
    }

    public IEnumerable<Contacts> GetAllContactsWithAddress()
    {
        return _context.Contacts
            .Include(c => c.Address)
            .OrderBy(c => c.FirstName).ToList();
    }   

    //TODO Change properties to lambda expression
    public int Update(object entity)
    {
        var entityProperties = entity.GetType().GetProperties();

        Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

        if (con != null)
        {
            _context.Entry(con).State = EntityState.Modified;
            _context.Contacts.Attach(con);

            foreach (var ep in entityProperties)
            {
                if(ep.Name != "Id")
                    _context.Entry(con).Property(ep.Name).IsModified = true;
            }
        }

        return _context.SaveChanges();
    }

    public static object ToType<T>(object obj, T type)
    {
        // Create an instance of T type object
        object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

        // Loop through the properties of the object you want to convert
        foreach (PropertyInfo pi in obj.GetType().GetProperties())
        {
            try
            {
                // Get the value of the property and try to assign it to the property of T type object
                tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
            }
            catch (Exception ex)
            {
                // Logging.Log.Error(ex);
            }
        }
        // Return the T type object
        return tmp;
    }
}    

public class Contacts
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Company { get; set; }
    public string Title { get; set; }
    public Addresses Address { get; set; }    
}

public class Addresses
{
    [Key]
    public int Id { get; set; }
    public string AddressType { get; set; }
    public string StreetAddress { get; set; }
    public string City { get; set; }
    public State State { get; set; }
    public string PostalCode { get; set; }  
}

public class ContactContext : DbContext
{
    public DbSet<Addresses> Address { get; set; } 
    public DbSet<Contacts> Contacts { get; set; } 
    public DbSet<State> States { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = "Server=YourServer;Database=ContactsDb;Trusted_Connection=True;MultipleActiveResultSets=true;";
        optionsBuilder.UseSqlServer(connString);
        base.OnConfiguring(optionsBuilder);
    }
}
Juan
fonte
7

Para o núcleo .net

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
Chris Rosete
fonte
5

Aqui está a melhor solução para esse problema: No modo de exibição, adicione todo o ID (chaves). Considere ter várias tabelas nomeadas (Primeira, Segunda e Terceira)

@Html.HiddenFor(model=>model.FirstID)
@Html.HiddenFor(model=>model.SecondID)
@Html.HiddenFor(model=>model.Second.SecondID)
@Html.HiddenFor(model=>model.Second.ThirdID)
@Html.HiddenFor(model=>model.Second.Third.ThirdID)

No código C #,

[HttpPost]
public ActionResult Edit(First first)
{
  if (ModelState.Isvalid)
  {
    if (first.FirstID > 0)
    {
      datacontext.Entry(first).State = EntityState.Modified;
      datacontext.Entry(first.Second).State = EntityState.Modified;
      datacontext.Entry(first.Second.Third).State = EntityState.Modified;
    }
    else
    {
      datacontext.First.Add(first);
    }
    datacontext.SaveChanges();
    Return RedirectToAction("Index");
  }

 return View(first);
}
Kumar R
fonte
5

Attachuma entidade definirá seu estado de rastreamento para Unchanged. Para atualizar uma entidade existente, basta definir o estado de rastreamento para Modified. De acordo com os documentos EF6 :

Se você possui uma entidade que você sabe que já existe no banco de dados, mas cujas alterações podem ter sido feitas, pode dizer ao contexto para anexar a entidade e definir seu estado como Modificado. Por exemplo:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Modified;

    // Do some more work...  

    context.SaveChanges();
}
Bondolin
fonte
4
using(var myDb = new MyDbEntities())
{

    user user = new user();
    user.username = "me";
    user.email = "[email protected]";

    myDb.Users.Add(user);
    myDb.users.Attach(user);
    myDb.Entry(user).State = EntityState.Modified;//this is for modiying/update existing entry
    myDb.SaveChanges();
}
Nikhil Dinesh
fonte
4

Eu encontrei uma maneira que funciona muito bem.

 var Update = context.UpdateTables.Find(id);
        Update.Title = title;

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();
Farhan
fonte
3

Você deve remover db.Books.Attach(book);

Renat Seyfetdinov
fonte
1

Aqui está o meu método de atualização de entidade pós-RIA (para o período Ef6):

public static void UpdateSegment(ISegment data)
{
    if (data == null) throw new ArgumentNullException("The expected Segment data is not here.");

    var context = GetContext();

    var originalData = context.Segments.SingleOrDefault(i => i.SegmentId == data.SegmentId);
    if (originalData == null) throw new NullReferenceException("The expected original Segment data is not here.");

    FrameworkTypeUtility.SetProperties(data, originalData);

    context.SaveChanges();
}

Observe que FrameworkTypeUtility.SetProperties()é uma pequena função utilitária que escrevi muito antes do AutoMapper no NuGet:

public static void SetProperties<TIn, TOut>(TIn input, TOut output, ICollection<string> includedProperties)
    where TIn : class
    where TOut : class
{
    if ((input == null) || (output == null)) return;
    Type inType = input.GetType();
    Type outType = output.GetType();
    foreach (PropertyInfo info in inType.GetProperties())
    {
        PropertyInfo outfo = ((info != null) && info.CanRead)
            ? outType.GetProperty(info.Name, info.PropertyType)
            : null;
        if (outfo != null && outfo.CanWrite
            && (outfo.PropertyType.Equals(info.PropertyType)))
        {
            if ((includedProperties != null) && includedProperties.Contains(info.Name))
                outfo.SetValue(output, info.GetValue(input, null), null);
            else if (includedProperties == null)
                outfo.SetValue(output, info.GetValue(input, null), null);
        }
    }
}
rasx
fonte
Nota: Funciona apenas se suas propriedades forem exatamente iguais no seu modelo que seu objeto ViewModel que está sendo salvo nele.
vapcguy
1

Como Renat disse, remova: db.Books.Attach(book);

Além disso, altere sua consulta de resultado para usar "AsNoTracking", porque esta consulta está descartando o estado do modelo da estrutura da entidade. Ele acha que "resultado" é o livro para rastrear agora e você não quer isso.

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);
Nez
fonte
1

Tente....

UpdateModel (livro);

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            UpdateModel(book);
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}
Karan
fonte
1

Eu sei que já foi respondido algumas vezes, mas eu gosto abaixo da maneira de fazer isso. Espero que ajude alguém.

//attach object (search for row)
TableName tn = _context.TableNames.Attach(new TableName { PK_COLUMN = YOUR_VALUE});
// set new value
tn.COLUMN_NAME_TO_UPDATE = NEW_COLUMN_VALUE;
// set column as modified
_context.Entry<TableName>(tn).Property(tnp => tnp.COLUMN_NAME_TO_UPDATE).IsModified = true;
// save change
_context.SaveChanges();
Pawel Czapski
fonte
1

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