sql - related - ¿Cómo actualizar solo un campo usando Entity Framework?
update entity framework relationship (13)
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;
Aquí está la mesa
Usuarios
UserId
UserName
Password
EmailAddress
y el código ...
public void ChangePassword(int userId, string password){
//code to update the password..
}
En Entity Framework Core, Attach
devuelve 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();
Entity framework rastrea los cambios en los objetos que ha consultado desde la base de datos a través de DbContext. Por ejemplo, si el nombre de 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();
}
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();
}
}
Llegué tarde al juego aquí, pero así es como lo hago, pasé un tiempo buscando una solución con la que me satisficiera; esto produce una instrucción UPDATE
SOLAMENTE para los campos que se cambian, ya que define explícitamente lo que 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 de 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 una prueba de captura si así lo desea, pero personalmente me gusta que mi interlocutor conozca las excepciones en este escenario.
Se llamaría de esta manera (para mí, esto fue a través de una API web de ASP.NET):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
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 una expresión de una función. Esto permitirá usar 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 brinda una solución similar: https://.com/a/5749469/2115384 )
El método que estoy usando actualmente en mi propio código , extendido para manejar también (Linq) Expresiones de tipo ExpressionType.Convert
. Esto fue necesario en mi caso, por ejemplo, con Guid
y otras propiedades de 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();
}
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();
}
}
Sé que este es un hilo viejo, pero también estaba buscando una solución similar y decidí ir con la solución @ Doku, según lo previsto. Estoy comentando para responder la pregunta formulada por @Imran Rizvi, seguí @ Doku-tan el enlace que muestra una implementación similar. La pregunta de @Imran Rizvi fue que estaba obteniendo 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 de 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 utilizar 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 algoritmo 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 de actualización debe agregar la palabra clave '' nueva '', especificar el tamaño de la matriz y, finalmente, utilizar 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, él está especificando una matriz de expresiones, por lo que debe pasar las propiedades para actualizarlas en una matriz, debido a la matriz también debe especificar el tamaño de la matriz. Para evitar esto, también podría 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, por lo que proporcioné un enfoque genial usando generic''s, utilicé el concepto para resolver mi problema, pero no puedes usar la solución de @ Doku-so tal como está y tanto en esta publicación como en la vinculada no respondieron las preguntas de error de uso.
Tienes básicamente dos opciones:
- sigue el camino de EF todo el camino, en ese caso, lo harías
- cargar el objeto en función del
userId
proporcionado: el objeto completo se carga - actualizar el campo de
password
- guarde el objeto de nuevo utilizando el método
.SaveChanges()
del contexto
- cargar el objeto en función del
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 básicamente lo mismo que crearías manualmente, algo así como:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
Así que 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.
- usted define un procedimiento almacenado que hace exactamente lo que necesita, en código T-SQL (simplemente actualice la columna de
Password
para elUserId
dado y nada más - básicamente ejecutaUPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId
) y usted cree una importación de función para ese procedimiento almacenado en su modelo EF y llame a esta función en lugar de seguir los pasos descritos anteriormente
Uso ValueInjecter
nuget para inyectar Binding Model en la base de datos Entity 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 convenciones personalizadas que no actualiza 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);
Valor Injecter V2
Busque esta respuesta
Advertencia
No sabrá si la propiedad se borró intencionalmente para anular 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.
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();
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;
}
}
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();
}
}