Zapis ViewModelu do bazy (kilka tabel)

0

Jak zapisać viewmodel do kilku tabel z których brałem dane do utworzenia tego poszczególnych modeli a nastepnie viewmodelu?
Załóżmy że mam kilka modeli:

public class Student
 {
     public int StudentId { get; set; }
     public string Name { get; set; }
     public string Lastname { get; set; }
     public DateTime BirthAt { get; set; }

     public int PlaceId { get; set; }
     public int DeskId { get; set; }
     public virtual Place Place { get; set; }
     public virtual Desk Desk { get; set; }
 }

 public class Place
 {
     public int PlaceId { get; set; }
     public string Name { get; set; }
     public string Description { get; set; }
 }

 public class Desk
 {
     public int DeskId { get; set; }
     public string Model { get; set; }
     public string Description { get; set; }
     public int Capacity { get; set; }
 }


 public class StudentViewModel
 {
     public int StudentId { get; set; }
     public string Name { get; set; }
     public string Lastname { get; set; }
     public DateTime BirthAt { get; set; }
     public int PlaceId { get; set; }
     public string PlaceName { get; set; }
     public int DeskId { get; set; }
     public string DeskModel { get; set; }
     public string DeskDescription { get; set; }
 }

 public class StudentService
 {

     public StudentViewModel GetStudentViewModel(int studentId)
     {
         using(var context = new StudentDbContext)
         {
             //pobranie danych z 3 tabel, przepisanie ich na StudentViewModel i zwrócenie StudentViewModel
         }
     }
   
     public void Save(StudentViewModel model)
     {
         //jak zapisywać dane?
     }
 
 } 

Mam klasy, zgodne z tabelami w bazie. W kontrolerze mam:

public class AdminController:Controller
{
  public ViewResult Student(int studentId)
  {
    StudentViewModel model = studentService.GetStudentViewModel(studentId);
    return View(model);
  }
}

teraz jesli chcialbym edytować dane studenta zawarte ww ViewModelu (chciałbym mieć możliwość edycji istniejących danych ale też dodania nowych danych, czyli musi być możliwość zapisania do tabel Desk i Place nowych danych, których tam wcześniej nie było, jak i edycji istniejących w nich danych) Jak to się robi?
Myślałem nad dodaniem w studentService metody Save (tak jak to wyżej dodałem w klasie). Moja metoda Save przyjmie ViewModel i rozbije sobie go na składowe dla poszczególnych klas, a następnie obiekt każdej klasy zapisze we właściwej tabeli. Czyli myślę nad czymś takim:

public class AdminController : Controller
   {
       //...

      [HttpPost]
       public ActionResult AddEditStudentData(StudentViewModel studentViewModel)
       {
           studentService.Save(studentViewModel);
           return View();
       }
   }


   public void Save(StudentViewModel model)
   {
       Place place = new Place { Name = model.PlaceName, Description = model.PlaceDescription };
       Desk desk = new Desk { Name = model.DeskName, Description = model.DeskDescription };
       Student student = new Student { Name = model.Name, Lastname = model.LastName, Desk = desk, Place = place };

       using (var context = new StudentDbContext())
       {
           context.Places.Add(place);
           context.Desks.Add(desk);
           context.Students.Add(students);
           context.SaveChanges();
       }
   }

Czyli korzystam dalej z studentService gdzie rozbijam mój ViewModel na poszczególne modele i zapisuje je osobno do tabel (oczywiście muszę zrobić jakies sprawdzanie czy obiekt istnieje w bazie, wtedy robie update(ustaiwam EntityState.Modified) a jeśli go nie ma to robie instert (EntityState.Added)

2

Nie powinieneś zapisywać ViewModelu do bazy. ViewModel ma za zadanie dostarczyć danych do wyświetlenia. Te dane są zbierane z odpowiedniego widoku (w sensie np. SQL VIEW) albo z innych źródeł danych. Do bazy danych idzie tylko model.

2

Pytanie zasadnicze - czemu chcesz używać tej samej klasy w trzech różnych zastosowaniach (wyświetlanie, dodawanie, edycja)? To powinny być oddzielne "viewmodele". Zwłaszcza, że w przypadku dodawania/edycji studenta trzeba jakoś rożrónić też sytuacje dodawania nowego miejsca/biurka albo użycia już istniejącego.

0

@Juhas: no właśnie w serwisie gdzie mam zapis do bazy, chce rozbic ViewModel na poszczególne modele i je wszystkie zapisać do własciwych. tabel. ViewModel zbierze mi dane z widoku i zostanie przekazany do serwisu gdzie bedzie rozbity na osobne modele. Nie tak się to robi?

@somekind bo np chciałbym wyswietlic uzytkownikowi Studenta z polami do edycji, np jakas lista rozwijana z biurkami, w ktorej jako domysla wartość będzie się wyswietlac wartość która zostala wczesniej ustawiona, ale uzytkownik będzie mógł z listy wybrać sobie inne biurko, lub recznie wpisac w pole rozwijane nowa wartosć ktora powinna sie wtedy dodac do tabeli Desks i ustawic jako domyślna dla tego Studenta w tabeli Students

0

Utrudniasz sobie robotę. Co się stanie w momencie, gdy dojdzie Ci jakieś pole do klasy Student? Stanie się tyle, że będziesz musiał pozmieniać we wszystkich innych miejscach (viewmodelach). Posłuż się w ViewModel klasą student. Czyli coś takiego:

class StudentViewModel
{
  public Student Student {get; set;}
  public Desk Desk {get; set;}
//itd.
}

Musiałbyś wtedy odpowiednio zaczytywać dane, żeby nie robić np. 2 selectów, tylko 1 select z joinem.

0

To jest dopiero utrudnianie sobie roboty (jak każde łamanie SRP zresztą). Przy tym podejściu, dodanie pola na backendzie magicznie zmienia to, co user widzi.
Model bazy danych musi być oddzielony od modelu widoku.

0

Ale to drugie rozwiązanie duplikuje kod. A jeśli używasz studenta w kilku widokach, no to masz te same dane już w kilku widokach. To widok (w sensie np. xaml) powinien być odpowiedzialny za to, co user widzi.

2

Nie duplikuje kodu, tylko co najwyżej fragment struktury danych. Za to spełnia SRP i zapewnia oddzielenie logiki prezentacji od modelu perzystencji. Do tego nie ma potem takiego głupiego efektu, że jedna klasa jest zawalona atrybutami ORM i GUI.

0

Nie powinieneś zapisywać ViewModelu do bazy. ViewModel ma za zadanie dostarczyć danych do wyświetlenia. Te dane są zbierane z odpowiedniego widoku (w sensie np. SQL VIEW) albo z innych źródeł danych. Do bazy danych idzie tylko model.

Nie czytałem całej dyskusji, odnoszę się tylko do tej wypowiedzi. To nie jest do końca prawda. Zależnie od przyjętego podejścia, jak najbardziej można mieć dedykowane view modele zapisane w bazie. Musza one być oczywiście oddzielne od modeli domeny (lub zwykłych encji). Słowa-klucze w tym przypadku to: materialised view, CQRS, event-based messaging (to ostatnie niekoniecznie).

0

Trochę sie pogubiłem w tym co piszecie.
Chodzi mi o sytuacje ze do widoku przekazuje ViewModel który zawiera pola (oczywiscie tylko te potrzebne, nie wszystkie) z 3 klas modelowych. Uzytkownik otrzymuje widok w ktorym niektore elementy moze zmodyfikowac lub dodac nowe(takie ktorych wczesniej w bazie nie było, np inne biurko niz student miał przypisane). Teraz mechanizm ModelBindingu zapewnia mi ze w ViewModelu mam aktualny stan tego co uzytkownik pozmieniał. I teraz pytanie czy w serwisie do którego przekazuje ten ViewModel celem zapisania danych do bazy mam rozbić sobie jego pola i potworzyc 3 klasy z których powstał ten ViewModel i poprzypisywac odpowiednie własciwości z ViewModelu do pól tych klas. Następnie zapisuje te 3 klasy do bazy. Tak to ma wyglądać?

0

@goodfather:
Ok, a wiec od poczatku :) Przede wszystkim tytul watku jest mylny, i w pewnym sensie obnaza problem z jakim sie zmierzasz- a konkretnie bledne podejscie do tego problemu. To co chcesz dokonac to nie zapisac view model (co sam wiesz, jako ze to wyjasniles) a dokonac szereg pewnych operacji. Nie zmienia to faktu ze posiadasz widok agregujacy dane z roznych zrodel (modeli domenowych/tabel/encji/cokolwiek) i probujesz z tego miejsca dokonac modyfikacji tychze roznych modeli. Proponuje wiec poczytac o task-based UI i zaadoptowac pewien tok mysleniowy. Operacje ktore chcesz dokonac na poszczegolnych elementach najlepiej powinny byc rozbite na oddzielne procesy (taski), dzieki czemu model widoku ktory poczatkowo uzyles do wyswietlenia zagregowanych danych nie bedzie juz bral w tym w ogole udzialu. Zamiast tego, probujac np. zmienic dane studenta przejdziesz z jednego widoku na inny (niekoniecznie musi to oznaczac zaladowanie innej strony), dokonasz zmian i wyslesz DTO do endpointu odpowiedzialnego tylko za ta operacje. DTO oczywiscie powinno posiadac tylko niezbedne dla tej operacji dane.

Ewentualnie, jesli koniecznie musisz to zrobic w jednej operacji przy uzyciu tego samego widoku (chociaz jak juz bylo wymienione, to nie najlepszy pomysl) to przeslij go z powrotem. Na serwerze natomiast zastostuj command-handler przy wsparciu chociazby wzorca mediator (biblioteka MediatR). Dla kazdej operacji (np. update studenta, update desk'a) stworz oddzielne polecenie (command). Wtedy wysylasz je oddzielnie. Np:

await _mediator.Send(new UpdateStudent(model.StudentId, model.Name, model.Lastname)); // Name i Lastname zostana uzyte to zaktualizowania studenta
await _mediator.Send(new UpdateDesk(model.DeskId, model.DeskDescription);

1 użytkowników online, w tym zalogowanych: 0, gości: 1