Excluir propiedad en la actualización en Entity Framework

79

He estado buscando una forma adecuada de marcar una propiedad para que NO se cambie al actualizar un modelo en MVC.

Por ejemplo, tomemos este pequeño modelo:

class Model
{
    [Key]
    public Guid Id {get; set;}
    public Guid Token {get; set;}

    //... lots of properties here ...
}

entonces el método de edición que crea MVC se ve así:

[HttpPost]
public ActionResult Edit(Model model)
{
    if (ModelState.IsValid)
    {
        db.Entry(model).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(model);
}

ahora, si mi Vista no contiene el Token, se anulará mediante esa edición.

Estoy buscando algo como esto:

db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();

La mejor manera que he encontrado hasta ahora es ser inclusivo y establecer todas las propiedades que quiero incluir a mano, pero en realidad solo quiero decir cuáles excluir.

Manuel Schweigert
fuente
Posible duplicado: stackoverflow.com/questions/3809583/…
Nate-Wilkins
No creo que esté duplicado: quiero excluir siempre una determinada propiedad para que no se actualice. El usuario no debería tener la posibilidad de cambiarlo.
Manuel Schweigert
2
puede usar modelos de vista y simplemente mapear lo que desea actualizar.
frennky
Yo podría. Hay varias formas de solucionar este problema. Pero quiero saber si hay una buena manera de hacer esto y, si existe, cómo funciona. Por cierto, la "solución" más pequeña que tengo para este cajero automático es abrir otra transacción: using (var db2 = new DataContext()) model.Token = db2.Models.Find(model.Id).Token;Pero tampoco estoy contento con esta.
Manuel Schweigert
3
Reconozco que esta es la forma "adecuada" de hacerlo, pero hay razones para no hacerlo de esa manera en este caso: a) sobrecarga, b) no ágil, c) inmantenible / propenso a errores. Así que sí, me niego a crear dos clases idénticas excepto por una propiedad.
Manuel Schweigert

Respuestas:

156

podemos usar así

 db.Entry(model).State = EntityState.Modified;
 db.Entry(model).Property(x => x.Token).IsModified = false;
 db.SaveChanges();

se actualizará pero sin la propiedad Token

Nitin Dominic
fuente
2
¿Qué pasa si estás usando AddOrUpdate? - ¿Cómo sabes usar EntityState.Modifiedo EntityState.Added?
Jess
1
ACTUALIZACIÓN: Para que funcione en EF 6 .. necesitas db.Model.Attach (model);
Maxi
6
Solo una nota para los demás de que el orden aquí es importante: si lo configura db.Entry(model).State = EntityState.Modified;después de configurar db.Entry(model).Property(x => x.Token).IsModified = false; , la propiedad se actualizará al guardar.
akerra
1
Y también esto debería ser después de actualizar los valores del modelo db.Entry (model) .CurrentValues.SetValues ​​(sourceModel); De lo contrario, la propiedad también se actualiza al guardar.
Fan de DotNet
10

Cree un nuevo modelo que tendrá un conjunto limitado de propiedades que desea actualizar.

Es decir, si su modelo de entidad es:

public class User
{
    public int Id {get;set;}
    public string Name {get;set;}
    public bool Enabled {get;set;}
}

Puede crear un modelo de vista personalizado que permitirá al usuario cambiar el nombre, pero no la marca habilitada:

public class UserProfileModel
{
   public int Id {get;set;}
   public string Name {get;set;}
}

Cuando desee actualizar la base de datos, haga lo siguiente:

YourUpdateMethod(UserProfileModel model)
{
    using(YourContext ctx = new YourContext())
    { 
        User user = new User { Id = model.Id } ;   /// stub model, only has Id
        ctx.Users.Attach(user); /// track your stub model
        ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
        ctx.SaveChanges();
    }
}

Cuando llame a este método, actualizará el Nombre, pero la propiedad Enabled permanecerá sin cambios. Usé modelos simples, pero creo que te darás una idea de cómo usarlo.

Admir Tuzović
fuente
gracias, esto se ve bien, pero sigue siendo una lista blanca, no una lista negra de propiedades.
Manuel Schweigert
Está poniendo en "lista negra" todo lo que no está en su modelo de vista y esto no requiere codificación adicional, solo está usando funciones EF. Además, cuando la entidad de código auxiliar se adjunta con Adjuntar, todos los valores de propiedad se establecen en nulo / predeterminado. Cuando usa SetValues ​​(modelo), si la propiedad de su modelo de vista es nula, dado que ya estaba adjunta como nula, el rastreador de cambios no lo marcará como modificado y, por lo tanto, esa propiedad no se guardará. Intentalo.
Admir Tuzović
3
No quiero discutir contigo. Las listas negras y las listas blancas son enfoques diferentes con el mismo resultado, su enfoque es la lista blanca. Como dije antes, hay muchas formas, pero preguntaba por una en particular. Además, su solución solo funciona con tipos que aceptan valores NULL.
Manuel Schweigert
1. Adjunte su modelo con Adjuntar 2. recorra las propiedades con db.Entry (modelo) .Property ("Nombre de propiedad"). State = PropertyState.Modified; 3. Haga SaveChanges.
Admir Tuzović
Lo que debe hacer es configurar todo el modelo para que se modifique y luego establecer algunas propiedades en Sin modificar. Lo que le escribí es adjuntar el modelo primero (nada está configurado como modificado) y luego marcar las propiedades que desea actualizar como Modificadas, que es exactamente lo que deseaba => lista blanca.
Admir Tuzović
8

Cualquiera que busque cómo lograr esto en EF Core. Es básicamente lo mismo, pero su IsModified debe ser después de agregar el modelo que se actualizará.

db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
Jesse
fuente
No sé por qué, pero mi EF Core solo tenía una versión de cadena y no pude usar lambda. Usé nameof en su lugar, pero este es el camino a seguir. Gracias
Cubelaster
Esta respuesta es tan "Microsofty" que sabía que funcionaría antes de probarla. Contrariamente al comentario anterior, tanto la versión de cadena como la de lambda arrojaron los mismos resultados. quizás estemos usando diferentes versiones.
T3.0
3

Creé una manera fácil de editar propiedades de entidades que compartiré contigo. este código editará las propiedades de Nombre y Familia de la entidad:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

Y este código se ignorará para editar las propiedades de Nombre y Familia de la entidad y editará otras propiedades:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

Utilice esta extensión:

public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
   where TEntity : class
{
    var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
    if (find == null)
        throw new Exception("id not found in database");
    if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (!properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    context.SaveChanges();
}

public enum TypeOfEditEntityProperty
{
    Ignore,
    Take
}
Ali Yousefi
fuente
1

Supongo que no desea que se cambie la propiedad solo en algunos casos, porque si no la va a usar nunca en su aplicación, simplemente elimínela de su modelo.

En caso de que quiera usarlo solo en algunos escenarios y evitar su "anulación" en el caso anterior, puede intentar:

  • Ocultar el parámetro en la vista con HiddenFor:

    @Html.HiddenFor(m => m.Token)

Esto hará que su valor original se mantenga sin modificar y se devuelva al controlador.

Cargue nuevamente su objeto en el controlador desde su DBSety ejecute este método. Puede especificar tanto una lista blanca como una lista negra de parámetros que se actualizarán o no.

Jaime
fuente
Puede encontrar una buena discusión sobre TryUpdateModel aquí: enlace . Como se dice en la respuesta validada, es mejor crear modelos de vista que coincidan exactamente con las propiedades necesarias en cada vista.
Jaime
1
El uso @Html.HiddenForescribirá el valor en el HTML de la vista y permitirá al usuario usar el elemento de inspección dentro de su navegador y modificarlo. Después de hacer eso, todavía se pasa al controlador pero con un valor diferente y se actualizará. Lo acabo de probar.
duckwizzle