¿Cómo actualizar el registro con Entity Framework 6?

245

Estoy tratando de actualizar el registro usando EF6. Primero encuentre el registro, si existe, actualícelo. Aquí está mi 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;
        }
    }
}

Cada vez que intento actualizar el registro con el código anterior, aparece este error:

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: la declaración de actualización, inserción o eliminación del almacén afectó un número inesperado de filas (0). Las entidades pueden haber sido modificadas o eliminadas desde que se cargaron las entidades. Actualizar entrada de ObjectStateManager

usuario1327064
fuente
77
Nota al margen: catch (Exception ex){throw;}es redundante y puede eliminarlo por completo.
Sriram Sakthivel
intente catch block es solo para descubrir la razón de la falla. Pero todavía no lo entendí, ¿por qué falla este código?
user1327064
2
No soy experto en este tema, no puedo responder esta pregunta. pero sin try catch también puede usar la función de interrupción cuando se lanza una excepción para romper el depurador cuando hay una excepción.
Sriram Sakthivel
1
No has cambiado nada. Jugar con el estado de entidad no cambiará el hecho de que el objeto no ha sido modificado.
Jonathan Allen
1
Bueno, hice lo mismo que tú y no recibí el error. La excepción dice DbUpdateConcurrencyException. ¿Cómo manejaste la concurrencia? ¿Usó una marca de tiempo, clonó y luego fusionó los objetos nuevamente o usó entidades de seguimiento automático? (3 enfoques más utilizados). Si no manejaste la concurrencia, supongo que ese es el problema.
El Mac

Respuestas:

344

Estás intentando actualizar el registro (lo que para mí significa "cambiar un valor en un registro existente y guardarlo de nuevo"). Por lo tanto, debe recuperar el objeto, realizar un cambio y guardarlo.

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.
fuente
16
Asignar el valor no actualiza la base de datos, llamar db.SaveChanges()con objetos modificados en el contexto actualiza la base de datos.
Craig W.
66
Aún así me fascina ... por lo que el resultado var, en realidad se conecta al dbcontext ... así que esto significa que cualquier variable que sea instanciada por cualquier miembro de dbcontext en realidad se asociará a la base de datos para que cualquier cambio se aplique a esa variable , también se aplica o persiste?
WantIt
66
Debido a que el contexto generó el objeto, el contexto puede rastrear el objeto, incluidos los cambios en el objeto. Cuando llama SaveChangesal contexto, evalúa todos los objetos que está rastreando para determinar si se agregan, cambian o eliminan y emite el SQL apropiado a la base de datos conectada.
Craig W.
3
iam enfrenta el mismo problema: usar EF6 e intentar actualizar una entidad. Attach + EntityState.Modified no funciona. Lo único que funciona es que necesita recuperar el objeto, realizar los cambios deseados y guardarlo a través de db.SaveChanges ();
Gurpreet Singh
77
NO debería tener que recuperar el objeto primero para actualizarlo. Tuve el mismo problema hasta que me di cuenta de que estaba tratando de cambiar uno de los valores de la clave principal (clave compuesta). Siempre que proporcione una clave primaria correcta, puede establecer EntityState en Modified y SaveChanges () funcionará, siempre que no rompa alguna otra restricción de integridad definida en la tabla.
adrianz
165

He estado revisando el código fuente de Entity Framework y encontré una manera de actualizar realmente una entidad si conoces la propiedad 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);
}

De lo contrario, verifique la implementación de AddOrUpdate para obtener ideas.

¡Espero que esto ayude!

Miguel
fuente
12
¡Agradable! No es necesario enumerar todas las propiedades. Supongo que SaveChanges()se requiere una llamada después de establecer los valores.
Jan Zahradník
3
Sí, los cambios persistirán en SaveChanges ()
Miguel
1
Gran respuesta, con IntelliSense no estaba demasiado claro que hacer algo como esto NO funcionaría: _context.MyObj = newObj; luego SaveChanges () o .... _context.MyObj.Update (newObj) luego SaveChanges (); Su solución actualiza todo el objeto sin tener que recorrer todas las propiedades.
Adam
77
Esto me queja de que estoy tratando de editar el campo de identificación
Vasily Hall
3
@VasilyHall: esto ocurre si los campos de ID (o lo que sea que haya definido la Clave primaria como) son diferentes entre los modelos (incluido nulo / 0 en uno de los modelos). Asegúrese de que las ID coincidan entre los dos modelos y se actualizarán perfectamente.
Gavin Coates
51

Puedes usar el AddOrUpdatemétodo:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
nicedev80
fuente
1
La mejor solución de la OMI
Norgul
112
.AddOrUpdate()se usa durante la migración de la base de datos, no se recomienda utilizar este método fuera de las migraciones, por lo tanto, está en el Entity.Migrationsespacio de nombres.
Adam Vincent
1
Como dijo @AdamVincent, el AddOrUpdate()método está destinado a migraciones y no es adecuado para situaciones en las que solo necesita actualizar la fila existente. En caso de que no tenga un libro con referencia de búsqueda (es decir, ID), creará una nueva fila y puede ser un problema en los próximos casos (por ejemplo, tiene una API que necesita devolverle la respuesta 404-NotFound si intente llamar al método PUT para una fila no existente).
Marko
44
No lo use a menos que sepa lo que está haciendo !!!!!!!!!!!!!!!! leer: michaelgmccarthy.com/2016/08/24/…
Yusha
44
Volví a este nuevo hoy, puedo simplemente que advertir a todos que esto es no una buena solución para el caso de uso deseada
Yusha
23

Entonces, tiene una entidad que se actualiza y desea actualizarla en la base de datos con la menor cantidad de código ...

La concurrencia siempre es complicada, pero supongo que solo quieres que tus actualizaciones ganen. Así es como lo hice para mi mismo caso y modifiqué los nombres para imitar sus clases. En otras palabras, simplemente cambie attacha add, y funciona para mí:

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
fuente
10

Debe usar el método Entry () en caso de que desee actualizar todos los campos de su objeto. También tenga en cuenta que no puede cambiar la identificación del campo (clave), por lo tanto, primero configure la Id al mismo tiempo que 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
fuente
2
Al menos debería intentar responder la pregunta, no solo publicar el código
StaceyGirl
Haga una explicación a la pregunta en lugar de simplemente dejar un fragmento de código para ayudar mejor al que hace la pregunta.
feanor07
9

Este código es el resultado de una prueba para actualizar solo un conjunto de columnas sin hacer una consulta para devolver el registro primero. Utiliza el código de Entity Framework 7 primero.

// 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;
}

Aquí está el 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
fuente
7

Para .net core

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

Aquí está la mejor solución para este problema: en la vista, agregue todas las ID (claves). Considere tener varias tablas con nombre (Primero, Segundo y Tercero)

@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)

En el 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
fuente
5

Attaching una entidad establecerá su estado de seguimiento en Unchanged. Para actualizar una entidad existente, todo lo que necesita hacer es establecer el estado de seguimiento en Modified. Según los documentos de EF6 :

Si tiene una entidad que sabe que ya existe en la base de datos pero a la que se han realizado cambios, puede indicarle al contexto que adjunte la entidad y establezca su estado en Modificado. Por ejemplo:

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
fuente
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
fuente
4

Encontré una manera que funciona bien.

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

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

Deberías eliminar db.Books.Attach(book);

Renat Seyfetdinov
fuente
1

Aquí está mi método de actualización de entidad posterior a RIA (para el marco de tiempo 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();
}

Tenga en cuenta que FrameworkTypeUtility.SetProperties()es una pequeña función de utilidad que escribí mucho antes de AutoMapper en 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
fuente
Nota: Funciona solo si sus propiedades son exactamente las mismas en su modelo que su objeto ViewModel que se está guardando en él.
vapcguy
1

Como dijo Renat, elimine: db.Books.Attach(book);

Además, cambie su consulta de resultados para usar "AsNoTracking", porque esta consulta está desviando el estado del modelo del marco de la entidad. Piensa que "resultado" es el libro a seguir ahora y no quieres eso.

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

Intentalo....

UpdateModel (libro);

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
fuente
1

Sé que ya se ha respondido bien algunas veces, pero me gusta la siguiente forma de hacerlo. Espero que ayude a alguien.

//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
fuente
1

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