Entity Framework 5 Aktualizowanie rekordu

870

Badałem różne metody edycji / aktualizacji rekordu w Entity Framework 5 w środowisku ASP.NET MVC3, ale jak dotąd żadne z nich nie zaznacza wszystkich wymaganych pól. Wyjaśnię dlaczego.

Znalazłem trzy metody, o których wspomnę za i przeciw:

Metoda 1 - Załaduj oryginalny rekord, zaktualizuj każdą właściwość

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

Plusy

  • Można określić, które właściwości mają się zmienić
  • Widoki nie muszą zawierać wszystkich właściwości

Cons

  • 2 x zapytania do bazy danych, aby załadować oryginał, a następnie go zaktualizować

Metoda 2 - Załaduj oryginalny rekord, ustaw zmienione wartości

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

Plusy

  • Tylko zmodyfikowane właściwości są wysyłane do bazy danych

Cons

  • Widoki muszą zawierać każdą właściwość
  • 2 x zapytania do bazy danych, aby załadować oryginał, a następnie go zaktualizować

Metoda 3 - Dołącz zaktualizowany rekord i ustaw stan na EntityState.Modified

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

Plusy

  • 1 x zapytanie do bazy danych do aktualizacji

Cons

  • Nie można określić, które właściwości mają się zmienić
  • Widoki muszą zawierać każdą właściwość

Pytanie

Moje pytanie do was, chłopaki; Czy istnieje czysty sposób na osiągnięcie tego zestawu celów?

  • Można określić, które właściwości mają się zmienić
  • Widoki nie muszą zawierać wszystkich właściwości (takich jak hasło!)
  • 1 x zapytanie do bazy danych do aktualizacji

Rozumiem, że jest to drobna rzecz, na którą należy zwrócić uwagę, ale może mi brakować prostego rozwiązania tego problemu. Jeśli nie metoda, zwycięży ;-)

Stokedout
źródło
13
Używać ViewModels i dobrego silnika mapowania? Otrzymujesz tylko „właściwości do aktualizacji”, aby wypełnić widok (a następnie zaktualizować). Nadal będą 2 zapytania do aktualizacji (pobierz oryginał + zaktualizuj go), ale nie nazwałbym tego „Con”. Jeśli to twój jedyny problem z wydajnością, jesteś szczęśliwym człowiekiem;)
Raphaël Althaus
Dzięki @ RaphaëlAlthaus, bardzo ważny punkt. Mógłbym to zrobić, ale muszę utworzyć operację CRUD dla wielu tabel, więc szukam metody, która może bezpośrednio współpracować z modelem, aby zaoszczędzić mi tworzenia n-1 ViewModel dla każdego modelu.
Stokedout
3
Cóż, w moim obecnym projekcie (także wielu podmiotach) zaczęliśmy od pracy z modelami, myśląc, że stracimy czas na pracę z ViewModels. Przechodzimy teraz do ViewModels, a dzięki (nie bez znaczenia) pracom nad infrastrukturą na początku, jest teraz o wiele, dużo wyraźniej i łatwiejsza w utrzymaniu. I bezpieczniejsze (nie trzeba się bać złośliwych „ukrytych pól” itp.)
Raphaël Althaus
1
I nigdy więcej (okropnych) toreb ViewBag do wypełnienia twoich DropDownLists (mamy co najmniej jeden DropDownList na prawie wszystkich naszych widokach CRU (D) ...)
Raphaël Althaus
Myślę, że masz rację, mój zły za próbę przeoczenia ViewModels. Tak, ViewBag czasami wydaje się trochę brudny. Zwykle robię krok dalej, jak na blogu Dino Esposito, i tworzę też InputModels, tad pas i aparat ortodontyczny, ale działa całkiem dobrze. Oznacza tylko 2 dodatkowe modele na modele - doh ;-)
Stokedout

Odpowiedzi:

681

Szukasz:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
Ladislav Mrnka
źródło
59
cześć @Ladislav Mrnka, jeśli chcę zaktualizować wszystkie właściwości jednocześnie, czy mogę użyć poniższego kodu? db.Departments.Attach (departament); db.Entry (departament) .State = EntityState.Modified; db.SaveChanges ();
Foyzul Karim
23
@Foysal: Tak, możesz.
Ladislav Mrnka
5
Jednym z problemów związanych z tym podejściem jest to, że nie można wyśmiewać db.Entry (), co jest poważną PITA. EF ma dość dobrą historię drwiącą gdzie indziej - denerwuje ją (o ile mogę powiedzieć), że jej tu nie ma.
Ken Smith
23
@Foysal Doing.Entry (byt) .State = EntityState.Modified sam wystarczy, nie trzeba wykonywać załączania. Zostanie automatycznie dołączony jako zmodyfikowany ...
HelloWorld,
4
@ Sandman4, oznacza to, że każda inna właściwość musi tam być i być ustawiona na bieżącą wartość. W niektórych projektach aplikacji nie jest to możliwe.
Dan Esparza
176

Naprawdę podoba mi się zaakceptowana odpowiedź. Wierzę, że istnieje jeszcze inny sposób, aby podejść do tego. Załóżmy, że masz bardzo krótką listę właściwości, których nigdy nie chciałbyś uwzględniać w Widoku, więc przy aktualizacji encji zostaną one pominięte. Powiedzmy, że te dwa pola to Hasło i SSN.

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

Ten przykład pozwala zasadniczo pozostawić logikę biznesową w spokoju po dodaniu nowego pola do tabeli Użytkownicy i do Widoku.

smd
źródło
Nadal otrzymam błąd, jeśli nie podam wartości właściwości SSN, mimo że ustawię IsModified na false, nadal weryfikuję właściwość względem reguł modelu. Więc jeśli właściwość jest oznaczona jako NIE NULL, nie powiedzie się, jeśli nie ustawię żadnej wartości innej niż null.
RolandoCC
Nie otrzymasz błędu, ponieważ te pola nie będą w twojej formie. Pomijasz pola, których na pewno nie zaktualizujesz, pobierasz wpis z bazy danych za pomocą formularza przekazanego przez dołączenie go i mówisz, że pola te nie są modyfikowane. Sprawdzanie poprawności modelu jest kontrolowane w ModelState, a nie w kontekście. Ten przykład odnosi się do istniejącego użytkownika, stąd „updatedUser”. Jeśli Twój SSN jest polem wymaganym, byłby tam, kiedy był tworzony po raz pierwszy.
smd
4
Jeśli dobrze rozumiem, „updatedUser” jest instancją obiektu już wypełnionego funkcją FirstOrDefault () lub podobną, dlatego aktualizuję tylko zmienione właściwości i ustawiam inne na ISModified = false. To działa dobrze. Ale to, co próbuję zrobić, to zaktualizować obiekt bez wypełniania go w pierwszej kolejności, bez dokonywania jakiejkolwiek zmiany FirstOrDefault () przed aktualizacją. To wtedy pojawia się błąd, jeśli nie podam wartości dla wszystkich wymaganych pól, nawet jeśli ustawię ISModified = false dla tych właściwości. entry.Property (e => e.columnA) .IsModified = false; Bez tego wiersza kolumna A zawiedzie.
RolandoCC
To, co opisujesz, to utworzenie nowego bytu. Dotyczy to tylko aktualizacji.
smd
1
RolandoCC, wstaw db.Configuration.ValidateOnSaveEnabled = false; przed db.SaveChanges ();
Wilky
28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();
Stefano Camisassi
źródło
Wydaje się, że to naprawdę fajne rozwiązanie - bez problemów i kłopotów; nie musisz ręcznie określać właściwości, a bierze ono pod uwagę wszystkie pociski PO - czy jest jakiś powód, dla którego nie ma więcej głosów?
nocarrier
Ale tak nie jest. Ma jeden z największych „minusów”, więcej niż jedno trafienie do bazy danych. Nadal będziesz musiał załadować oryginał z tą odpowiedzią.
smd
1
@smd, dlaczego według ciebie trafia on do bazy danych więcej niż raz? Nie widzę tego, chyba że użycie SetValues ​​() ma taki efekt, ale nie wydaje się, żeby to było prawdą.
parlament
@parlament Myślę, że musiałem spać, kiedy to napisałem. Przeprosiny. Rzeczywistym problemem jest przesłanianie zamierzonej wartości zerowej. Jeśli zaktualizowany użytkownik nie ma już odniesienia do czegoś, niewłaściwe byłoby zastąpienie go oryginalną wartością, jeśli miałbyś to wyczyścić.
smd
22

Dodałem dodatkową metodę aktualizacji do mojej podstawowej klasy repozytorium, która jest podobna do metody aktualizacji generowanej przez Scaffolding. Zamiast ustawiać cały obiekt na „zmodyfikowany”, ustawia zestaw indywidualnych właściwości. (T jest parametrem ogólnym klasy.)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

A potem zadzwonić, na przykład:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

Lubię jedną wycieczkę do bazy danych. Prawdopodobnie lepiej jest to jednak zrobić z widokami modeli, aby uniknąć powtarzania zestawów właściwości. Jeszcze tego nie zrobiłem, ponieważ nie wiem, jak uniknąć przesyłania komunikatów sprawdzania poprawności w moim modelu sprawdzania poprawności do mojego projektu domeny.

Ian Warburton
źródło
Aha ... osobny projekt dla modeli widoków i osobny projekt dla repozytoriów, które działają z modelami widoków.
Ian Warburton
11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}
Matthew Steven Monkan
źródło
Dlaczego nie tylko DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
nelsontruran
To kontroluje setczęść instrukcji aktualizacji.
Tanveer Badar
4

Wystarczy dodać do listy opcji. Możesz także pobrać obiekt z bazy danych i użyć narzędzia do automatycznego mapowania, takiego jak Auto Mapper, aby zaktualizować części rekordu, które chcesz zmienić.

Bostwick
źródło
3

W zależności od przypadku zastosowania obowiązują wszystkie powyższe rozwiązania. Tak zazwyczaj jednak to robię:

W przypadku kodu po stronie serwera (np. Proces wsadowy) zwykle ładuję encje i pracuję z dynamicznymi serwerami proxy. Zwykle w procesach wsadowych dane należy ładować w każdym momencie w momencie uruchomienia usługi. Próbuję wsadowo ładować dane zamiast korzystać z metody find, aby zaoszczędzić trochę czasu. W zależności od procesu używam optymistycznej lub pesymistycznej kontroli współbieżności (zawsze używam optymistycznej, z wyjątkiem scenariuszy wykonywania równoległego, w których muszę zablokować niektóre rekordy za pomocą zwykłych instrukcji SQL, jednak jest to rzadkie). W zależności od kodu i scenariusza wpływ można ograniczyć do niemal zera.

W przypadku scenariuszy po stronie klienta masz kilka opcji

  1. Użyj modeli widoku. Modele powinny mieć właściwość UpdateStatus (niezmodyfikowane-wstawione-zaktualizowane-usunięte). Klient jest odpowiedzialny za ustawienie poprawnej wartości w tej kolumnie w zależności od działań użytkownika (wstaw-aktualizuj-usuń). Serwer może albo zapytać db o oryginalne wartości, albo klient powinien wysłać oryginalne wartości do serwera wraz ze zmienionymi wierszami. Serwer powinien dołączyć oryginalne wartości i użyć kolumny UpdateStatus dla każdego wiersza, aby zdecydować, jak obsłużyć nowe wartości. W tym scenariuszu zawsze używam optymistycznej współbieżności. Spowoduje to jedynie wykonanie instrukcji insert-update-delete, a nie wyborów, ale może potrzebować sprytnego kodu, aby przejść wykres i zaktualizować encje (w zależności od scenariusza - aplikacji). Program mapujący może pomóc, ale nie obsługuje logiki CRUD

  2. Użyj biblioteki takiej jak breeze.js, która ukrywa większość tej złożoności (jak opisano w 1) i spróbuj dopasować ją do swojego przypadku użycia.

Mam nadzieję, że to pomoże

Chriss
źródło