Wyklucz właściwość przy aktualizacji w Entity Framework

79

Szukałem właściwego sposobu, aby oznaczyć właściwość jako NIE zmienianą podczas aktualizacji modelu w MVC.

Na przykład weźmy ten mały model:

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

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

wówczas metoda edycji utworzona przez MVC wygląda następująco:

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

teraz, jeśli mój widok nie zawiera tokenu, zostanie on unieważniony przez tę edycję.

Szukam czegoś takiego:

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

Jak dotąd najlepszym sposobem jest uwzględnienie wszystkich właściwości, które chcę uwzględnić, ręcznie, ale tak naprawdę chcę tylko powiedzieć, które z nich mają być wykluczone.

Manuel Schweigert
źródło
Możliwy duplikat: stackoverflow.com/questions/3809583/ ...
Nate-Wilkins
Nie sądzę, że to duplikat: chcę zawsze wykluczyć jakąś właściwość z aktualizacji. Użytkownik nie powinien mieć możliwości jego zmiany.
Manuel Schweigert
2
możesz użyć modeli widoku i po prostu zmapować to, co chcesz zaktualizować.
frennky
Mógłbym. Istnieje kilka sposobów obejścia tego problemu. Ale chcę wiedzieć, czy można to zrobić w fajny sposób, a jeśli istnieje, jak to działa. btw, najmniejszym "rozwiązaniem" jakie mam do tego bankomatu jest otwarcie kolejnej transakcji: using (var db2 = new DataContext()) model.Token = db2.Models.Find(model.Id).Token;Ale ja też nie jestem z tego zadowolony.
Manuel Schweigert
3
Przyznaję, że jest to „właściwy” sposób, aby to zrobić, ale są powody, aby tego nie robić w tym przypadku: a) narzut, b) nie jest zwinny, c) nie można go konserwować / jest podatny na błędy. Więc tak, odmawiam tworzenia dwóch identycznych klas z wyjątkiem jednej właściwości.
Manuel Schweigert

Odpowiedzi:

156

możemy użyć w ten sposób

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

zaktualizuje się, ale bez właściwości tokena

Nitin Dominic
źródło
2
A co jeśli używasz AddOrUpdate? - Skąd wiesz, jak używać EntityState.Modifiedlub EntityState.Added?
Jess
1
UPDATE: Aby to działało w EF 6 .. musisz db.Model.Attach (model);
Maxi
6
Tylko uwaga dla innych, że kolejność tutaj jest ważna: jeśli ustawisz db.Entry(model).State = EntityState.Modified;po ustawieniu db.Entry(model).Property(x => x.Token).IsModified = false; , właściwość zostanie zaktualizowana przy zapisywaniu.
akerra
1
Powinno to również nastąpić po zaktualizowaniu wartości modelu db.Entry (model) .CurrentValues.SetValues ​​(sourceModel); Jeśli nie, właściwość jest również aktualizowana podczas zapisywania.
DotNet Fan
10

Utwórz nowy model, który będzie miał ograniczony zestaw właściwości, które chcesz zaktualizować.

To znaczy, jeśli model Twojej jednostki to:

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

Możesz utworzyć niestandardowy model widoku, który pozwoli użytkownikowi zmienić nazwę, ale nie opcję Włączono:

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

Jeśli chcesz zaktualizować bazę danych, wykonaj następujące czynności:

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();
    }
}

Wywołanie tej metody spowoduje zaktualizowanie nazwy, ale właściwość Enabled pozostanie niezmieniona. Użyłem prostych modeli, ale myślę, że zrozumiesz, jak go używać.

Admir Tuzović
źródło
dzięki, wygląda dobrze, ale to nadal jest biała lista, a nie czarna lista nieruchomości.
Manuel Schweigert
Umieszczasz na „czarnej liście” wszystko, czego nie ma w Twoim modelu widoku i nie wymaga to dodatkowego kodowania, używasz tylko funkcji EF. Ponadto, gdy jednostka pośrednicząca jest dołączona za pomocą Attach, wszystkie wartości właściwości są ustawiane na null / default. Gdy używasz SetValues ​​(model), jeśli właściwość modelu widoku ma wartość null, ponieważ została już dołączona jako null, moduł śledzenia zmian nie oznaczy go jako zmodyfikowanego, a zatem ta właściwość zostanie pominięta podczas zapisywania. Spróbuj.
Admir Tuzović
3
Nie chcę się z tobą kłócić. Czarna lista i biała lista to różne podejścia, które dają ten sam wynik. Twoje podejście polega na umieszczaniu na białej liście. Jak powiedziałem wcześniej, jest wiele sposobów, ale pytałem szczególnie o jeden. Ponadto Twoje rozwiązanie działa tylko z typami dopuszczającymi wartość null.
Manuel Schweigert
1. Dołącz model za pomocą Attach 2. pętla przez właściwości za pomocą db.Entry (model) .Property ("Propertyname"). State = PropertyState.Modified; 3. Wykonaj SaveChanges.
Admir Tuzović
To, co robisz, to ustawienie całego modelu do modyfikacji, a następnie ustawienie niektórych właściwości na Unmodified. To, co ci napisałem, to najpierw dołącz model (nic nie jest ustawione jako zmodyfikowane), a następnie zaznacz właściwości, które chcesz zaktualizować jako Zmodyfikowane, czyli dokładnie to, czego chciałeś => biała lista.
Admir Tuzović
8

Każdy, kto szuka sposobu na osiągnięcie tego w EF Core. Zasadniczo jest tak samo, ale IsModified musi być po dodaniu modelu do aktualizacji.

db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
Jesse
źródło
Nie wiem, dlaczego, ale mój EF Core miał tylko wersję ciągu i nie mogłem użyć lambda. Zamiast tego użyłem nameof, ale to jest droga. Dzięki
Cubelaster
Ta odpowiedź jest taka „Microsofty”, że wiedziałem, że zadziała, zanim ją przetestuję. W przeciwieństwie do powyższego komentarza, zarówno wersja string, jak i lambda dały takie same wyniki. być może używamy różnych wersji.
T3.0
3

Stworzyłem łatwy sposób na edycję właściwości podmiotów, którymi się z Tobą podzielę. ten kod będzie edytować właściwości nazwy i rodziny podmiotu:

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

A ten kod zignoruje, aby edytować właściwości nazwy i rodziny jednostki i będzie edytować inne właściwości:

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

Użyj tego rozszerzenia:

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
źródło
1

Myślę, że nie chcesz, aby ta właściwość była zmieniana tylko w niektórych przypadkach, ponieważ jeśli nie zamierzasz jej nigdy używać w swojej aplikacji, po prostu usuń ją z modelu.

Jeśli chcesz go użyć tylko w niektórych scenariuszach i uniknąć jego „unieważnienia” w powyższym przypadku, możesz spróbować:

  • Ukryj parametr w widoku za pomocą HiddenFor:

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

Dzięki temu oryginalna wartość pozostanie niezmieniona i zostanie przekazana z powrotem do kontrolera.

Załaduj ponownie swój obiekt do kontrolera ze swojego DBSeti uruchom tę metodę. Możesz określić zarówno białą listę, jak i czarną listę parametrów, które mają być aktualizowane lub nie.

Jaime
źródło
Możesz znaleźć dobrą dyskusję na temat TryUpdateModel tutaj: link . Jak zostało powiedziane w zweryfikowanej odpowiedzi, lepiej jest tworzyć modele widoków, aby dokładnie odpowiadały właściwościom wymaganym w każdym widoku.
Jaime
1
Użycie @Html.HiddenForzapisze wartość w kodzie HTML widoku i umożliwi użytkownikowi użycie elementu inspect w przeglądarce i zmodyfikowanie go. Po wykonaniu tej czynności jest nadal przekazywana do kontrolera, ale z inną wartością i zostanie zaktualizowana. Właśnie to przetestowałem.
duckwizzle