¿Cómo actualizar solo un campo usando Entity Framework?


190

Aquí está la mesa

Los usuarios

UserId
UserName
Password
EmailAddress

y el código ..

public void ChangePassword(int userId, string password){
//code to update the password..
}

26
Por Password, te refieres a la contraseña hash, ¿verdad? :-)
Edward Brey

Respuestas:


370

La respuesta de Ladislav se actualizó para usar DbContext (introducido en EF 4.1):

public void ChangePassword(int userId, string password)
{
  var user = new User() { Id = userId, Password = password };
  using (var db = new MyEfContextName())
  {
    db.Users.Attach(user);
    db.Entry(user).Property(x => x.Password).IsModified = true;
    db.SaveChanges();
  }
}

55
Solo pude hacer que este código funcione agregando db.Configuration.ValidateOnSaveEnabled = false; antes de db.SaveChanges ()?
Jake Drew

3
Qué espacio de nombre incluir para usar db.Entry(user).Property(x => x.Password).IsModified = true;y nodb.Entry(user).Property("Password").IsModified = true;
Johan

55
Este enfoque arroja una OptimisticConcurencyException cuando la tabla tiene un campo de marca de tiempo.
Maksim Vi.

9
Creo que vale la pena mencionar que si está utilizando, db.Configuration.ValidateOnSaveEnabled = false;es posible que desee seguir validando el campo que está actualizando:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
Debe establecer ValidateOnSaveEnabled en falso si ha requerido campos en su tabla que no está proporcionando durante su actualización
Sal

54

Puede decirle a EF qué propiedades deben actualizarse de esta manera:

public void ChangePassword(int userId, string password)
{
  var user = new User { Id = userId, Password = password };
  using (var context = new ObjectContext(ConnectionString))
  {
    var users = context.CreateObjectSet<User>();
    users.Attach(user);
    context.ObjectStateManager.GetObjectStateEntry(user)
      .SetModifiedProperty("Password");
    context.SaveChanges();
  }
}

ObjectStateManager no está disponible para DBContext
LoxLox

17

Básicamente tienes dos opciones:

  • siga el camino EF hasta el final, en ese caso, lo haría
    • cargar el objeto según lo userIdproporcionado: se carga todo el objeto
    • actualizar el passwordcampo
    • guardar el objeto de nuevo usando el .SaveChanges()método del contexto

En este caso, depende de EF cómo manejar esto en detalle. Acabo de probar esto, y en el caso de que solo cambie un solo campo de un objeto, lo que EF crea es más o menos lo que crearías manualmente, algo como:

`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`

Por lo tanto, EF es lo suficientemente inteligente como para descubrir qué columnas realmente han cambiado, y creará una declaración T-SQL para manejar solo las actualizaciones que de hecho son necesarias.

  • define un procedimiento almacenado que hace exactamente lo que necesita, en el código T-SQL (simplemente actualice la Passwordcolumna para el dado UserIdy nada más, básicamente se ejecuta UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) y crea una importación de función para ese procedimiento almacenado en su modelo EF y llama a esto funcionar en lugar de seguir los pasos descritos anteriormente

1
@ marc-s ¡Realmente no tienes que cargar todo el objeto!
Arvand

14

En Entity Framework Core, Attachdevuelve la entrada, por lo que todo lo que necesita es:

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();

12

estoy usando esto:

entidad:

public class Thing 
{
    [Key]
    public int Id { get; set; }
    public string Info { get; set; }
    public string OtherStuff { get; set; }
}

dbcontext:

public class MyDataContext : DbContext
{
    public DbSet<Thing > Things { get; set; }
}

código de acceso:

MyDataContext ctx = new MyDataContext();

// FIRST create a blank object
Thing thing = ctx.Things.Create();

// SECOND set the ID
thing.Id = id;

// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing); 

// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";

// FIFTH save that thing
db.SaveChanges();

1
Recibo errores de validación de entidad cuando intento esto, pero seguro que se ve genial.
devlord

¡¡¡No funciona este método !!!: tal vez necesites dar más detalles sobre cómo usarlo !!! - este es el error: "No se pudo adjuntar una entidad del tipo 'Domain.Job' porque otra entidad del mismo tipo ya tiene el mismo valor de clave principal. Esto puede suceder cuando se usa el método 'Attach' o se establece el estado de una entidad "Sin cambios" o "Modificado" si alguna entidad en el gráfico tiene valores clave en conflicto. Esto puede deberse a que algunas entidades son nuevas y aún no han recibido valores clave generados por la base de datos ".
Lucian Bumb

Perfec! ¡Comprueba mi respuesta para ver un enfoque flexible para cualquier modelo!
kryp

10

Mientras buscaba una solución a este problema, encontré una variación en la respuesta de GONeale a través del blog de Patrick Desjardins :

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
  DatabaseContext.Entry(entity).State = EntityState.Unchanged;
  foreach (var property in properties)
  {
    var propertyName = ExpressionHelper.GetExpressionText(property);
    DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
  }
  return DatabaseContext.SaveChangesWithoutValidation();
}

" Como puede ver, toma como segundo parámetro la expresión de una función. Esto permitirá utilizar este método al especificar en una expresión Lambda qué propiedad actualizar " .

...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);

(Aquí también se ofrece una solución algo similar: https://stackoverflow.com/a/5749469/2115384 )

El método que estoy usando actualmente en mi propio código , extendido para manejar también Expresiones de tipo (Linq) ExpressionType.Convert. Esto fue necesario en mi caso, por ejemplo, con Guidy otras propiedades del objeto. Esos fueron 'envueltos' en un Convert () y por lo tanto no manejados por System.Web.Mvc.ExpressionHelper.GetExpressionText.

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
    DbEntityEntry<T> entry = dataContext.Entry(entity);
    entry.State = EntityState.Unchanged;
    foreach (var property in properties)
    {
        string propertyName = "";
        Expression bodyExpression = property.Body;
        if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
        {
            Expression operand = ((UnaryExpression)property.Body).Operand;
            propertyName = ((MemberExpression)operand).Member.Name;
        }
        else
        {
            propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
        }
        entry.Property(propertyName).IsModified = true;
    }

    dataContext.Configuration.ValidateOnSaveEnabled = false;
    return dataContext.SaveChanges();
}

1
Cuando uso esto me da el siguiente error: No se puede convertir la expresión Lambda a Tipo 'Expresión <Func <RequestDetail, objeto >> []' porque no es un tipo de delegado
Imran Rizvi

@ImranRizvi, simplemente necesita actualizar los parámetros a: public int Update (entidad T, expresión de parámetros <Func <T, objeto >> [] propiedades) NOTA la palabra clave params antes de la expresión
dalcam

6

Llego tarde al juego aquí, pero así es como lo estoy haciendo, pasé un tiempo buscando una solución con la que estaba satisfecho; esto produce una UPDATEdeclaración SOLO para los campos que se cambian, ya que usted define explícitamente qué son a través de un concepto de "lista blanca" que es más seguro para evitar la inyección de formularios web de todos modos.

Un extracto de mi repositorio de datos ISession:

public bool Update<T>(T item, params string[] changedPropertyNames) where T 
  : class, new()
{
    _context.Set<T>().Attach(item);
    foreach (var propertyName in changedPropertyNames)
    {
        // If we can't find the property, this line wil throw an exception, 
        //which is good as we want to know about it
        _context.Entry(item).Property(propertyName).IsModified = true;
    }
    return true;
}

Esto podría envolverse en un intento ... captura si así lo desea, pero personalmente me gusta que mi interlocutor sepa acerca de las excepciones en este escenario.

Se llamaría de esta manera (para mí, esto fue a través de una API web ASP.NET):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

2
¿Entonces tu mejor solución es lo que Elisa? Debe indicar explícitamente qué propiedades permite que se actualicen (al igual que la lista blanca requerida para el UpdateModelcomando ASP.NET MVC ), de esa manera se asegura de que la inyección de formularios de piratas informáticos no pueda ocurrir y que no puedan actualizar los campos que no pueden actualizar. Sin embargo, si alguien puede convertir la matriz de cadena en algún tipo de parámetro de expresiones lambda y trabajar con ella en el Update<T>, excelente
GONeale

3
@GONeale - Solo de paso. ¡Alguien lo ha descifrado usando Lambdas!
David Spence

1
@Elisa Se puede mejorar usando Func <T, List <object>> en lugar de string []
Spongebob Comrade

Incluso más tarde al juego, y tal vez esta es una sintaxis mucho más reciente, pero var entity=_context.Set<T>().Attach(item);seguida por entity.Property(propertyName).IsModified = true;el ciclo debería funcionar.
Auspex

4

Entity Framework realiza un seguimiento de sus cambios en los objetos que ha consultado desde la base de datos a través de DbContext. Por ejemplo, si el nombre de la instancia de DbContext es dbContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}

¿Y cómo debería ser la vista en este caso?
Emanuela Colta

Esto es incorrecto porque guardaría todo el objeto Usuario con una contraseña modificada.
amuliar el

eso es cierto, pero el resto del objeto Usuario será el mismo que anteriormente en el contexto, lo único que posiblemente sea diferente es la contraseña, por lo que esencialmente solo se actualiza la contraseña.
Tomislav3008

3

Sé que este es un hilo antiguo, pero también estaba buscando una solución similar y decidí ir con la solución @ Doku, así que la proporcioné. Estoy comentando para responder la pregunta hecha por @Imran Rizvi, seguí el enlace @ Doku-so que muestra una implementación similar. La pregunta de @Imran Rizvi fue que estaba recibiendo un error al usar la solución provista 'No se puede convertir la expresión Lambda a Tipo' Expresión> [] 'porque no es un tipo delegado'. Quería ofrecer una pequeña modificación que hice a la solución de @ Doku-so que corrige este error en caso de que alguien más se encuentre con esta publicación y decida usar la solución de @ Doku-so.

El problema es el segundo argumento en el método de actualización,

public int Update(T entity, Expression<Func<T, object>>[] properties). 

Para llamar a este método utilizando la sintaxis proporcionada ...

Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

Debe agregar la palabra clave 'params' delante del segundo argumento como tal.

public int Update(T entity, params Expression<Func<T, object>>[] properties)

o si no desea cambiar la firma del método, para llamar al método Actualizar, debe agregar la palabra clave ' nueva ', especificar el tamaño de la matriz y, finalmente, usar la sintaxis del inicializador del objeto de colección para que cada propiedad se actualice como se ve abajo.

Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

En el ejemplo de @ Doku-so, está especificando una matriz de Expresiones, por lo que debe pasar las propiedades para actualizar en una matriz, debido a la matriz también debe especificar el tamaño de la matriz. Para evitar esto, también puede cambiar el argumento de expresión para usar IEnumerable en lugar de una matriz.

Aquí está mi implementación de la solución de @ Doku-so.

public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties)
     where TEntity: class
    {
        entityEntry.State = System.Data.Entity.EntityState.Unchanged;

        properties.ToList()
            .ForEach((property) =>
            {
                var propertyName = string.Empty;
                var bodyExpression = property.Body;
                if (bodyExpression.NodeType == ExpressionType.Convert
                    && bodyExpression is UnaryExpression)
                {
                    Expression operand = ((UnaryExpression)property.Body).Operand;
                    propertyName = ((MemberExpression)operand).Member.Name;
                }
                else
                {
                    propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
                }

                entityEntry.Property(propertyName).IsModified = true;
            });

        dataContext.Configuration.ValidateOnSaveEnabled = false;

        return dataContext.SaveChanges();
    }

Uso:

this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);

@ Doku-so proporcionó un enfoque genial usando genéricos, utilicé el concepto para resolver mi problema, pero simplemente no puede usar la solución de @ Doku-so como está y en esta publicación y en la publicación vinculada nadie respondió las preguntas de error de uso.


Yo estaba trabajando en su solución cuando pases programa de línea de entityEntry.State = EntityState.Unchanged;todos los valores actualizados en el parámetro entityEntryget Revert, por lo que no se guardan los cambios, puede usted por favor ayuda en él, gracias
sairfan

3

En EntityFramework Core 2.x no hay necesidad de Attach:

 // get a tracked entity
 var entity = context.User.Find(userId);
 entity.someProp = someValue;
 // other property changes might come here
 context.SaveChanges();

Probé esto en SQL Server y lo perfilé:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = @p0
WHERE [UserId] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 bit',@p1=1223424,@p0=1

Find asegura que las entidades ya cargadas no activen un SELECT y también adjunta automáticamente la entidad si es necesario (de los documentos):

    ///     Finds an entity with the given primary key values. If an entity with the given primary key values
    ///     is being tracked by the context, then it is returned immediately without making a request to the
    ///     database. Otherwise, a query is made to the database for an entity with the given primary key values
    ///     and this entity, if found, is attached to the context and returned. If no entity is found, then
    ///     null is returned.

1

Combinando varias sugerencias, propongo lo siguiente:

    async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
    {
        try
        {
            var entry = db.Entry(entity);
            db.Set<T>().Attach(entity);
            foreach (var property in properties)
                entry.Property(property).IsModified = true;
            await db.SaveChangesAsync();
            return true;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message);
            return false;
        } 
    }

llamado por

UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);

O por

await UpdateDbEntryAsync(dbc, d => d.Property1);

O por

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;

¿Cómo hacer que este método esté disponible para otra clase, puede ser como un método de extensión?
Velkumar

en este tutorial de .NET CORE muestran las mejores prácticas usando (el nuevo) EF Core para actualizar propiedades específicas en MVC. busque 'TryUpdateModelAsync'.
Guy

1
@Guy Impresionante. Sin embargo, una vez más, la "mejor práctica" de Microsoft es hacer algo diferente a lo que construyen sus herramientas ...
Auspex

Esta es una buena solución.
Timothy Macharia

1

Utilizo ValueInjecternuget para inyectar el modelo de enlace en la entidad de base de datos usando lo siguiente:

public async Task<IHttpActionResult> Add(CustomBindingModel model)
{
   var entity= await db.MyEntities.FindAsync(model.Id);
   if (entity== null) return NotFound();

   entity.InjectFrom<NoNullsInjection>(model);

   await db.SaveChangesAsync();
   return Ok();
}

Observe el uso de la convención personalizada que no actualiza las propiedades si son nulas desde el servidor.

ValueInjecter v3 +

public class NoNullsInjection : LoopInjection
{
    protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
    {
        if (sp.GetValue(source) == null) return;
        base.SetValue(source, target, sp, tp);
    }
}

Uso:

target.InjectFrom<NoNullsInjection>(source);

Value Injecter V2

Busca esta respuesta

Consideración

No sabrá si la propiedad se borró intencionalmente como nula O simplemente no tenía ningún valor. En otras palabras, el valor de la propiedad solo se puede reemplazar con otro valor pero no se borra.


0

Estaba buscando lo mismo y finalmente encontré la solución

using (CString conn = new CString())
{
    USER user = conn.USERs.Find(CMN.CurrentUser.ID);
    user.PASSWORD = txtPass.Text;
    conn.SaveChanges();
}

créeme, me funciona como un encanto.


0

Esto es lo que uso, usando InjectNonNull personalizado (obj dest, obj src) lo hace completamente flexible

[HttpPost]
public async Task<IActionResult> Post( [FromQuery]Models.Currency currency ) {
  if ( ModelState.IsValid ) {
    // find existing object by Key
    Models.Currency currencyDest = context.Currencies.Find( currency.Id ); 

    context.Currencies.Attach( currencyDest );

    // update only not null fields
    InjectNonNull( currencyDest, currency );

    // save
    await context.SaveChangesAsync( );
  }  
  return Ok();
}

// Custom method
public static T InjectNonNull<T>( T dest, T src ) {
  foreach ( var propertyPair in PropertyLister<T, T>.PropertyMap ) {
    var fromValue = propertyPair.Item2.GetValue( src, null );
    if ( fromValue != null && propertyPair.Item1.CanWrite ) {
       propertyPair.Item1.SetValue( dest, fromValue, null );
    }
  }
  return dest;
}

-1
public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    try
    {
        this.Context.Set<TEntity>().Attach(entity);
        EntityEntry<TEntity> entry = this.Context.Entry(entity);
        entry.State = EntityState.Modified;
        foreach (var property in properties)
            entry.Property(property).IsModified = true;
        await this.Context.SaveChangesAsync();
        return true;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

-7
public void ChangePassword(int userId, string password)
{
  var user = new User{ Id = userId, Password = password };
  using (var db = new DbContextName())
  {
    db.Entry(user).State = EntityState.Added;
    db.SaveChanges();
  }
}

1
Esto agregará una nueva fila. La pregunta es cómo actualizar una existente.
Edward Brey
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.