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.



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


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ć.


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

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
}

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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.