Jak poprawnie korzystać z relacji wykorzystując ich przeznaczenie?

Odpowiedz Nowy wątek
2015-10-05 23:28
0

Ostatni tydzień spędziłem nad czytaniem o EF i relacjach, które chciałbym wykorzystać w swojej pracy inżynierskiej korzystającej z Entity Framework. Nawet założyłem temat w którym zapytałem się czy dobrze ułożyłem relacje pomiędzy klasami. Jako że mi to nie działało tak jak chciałem postanowiłem że zrobię sobie jakiś mały przykład i na nim potrenuje relacje. No i okazało się że chyba nie rozumiem jak się tego używa i po co :( tydzień za tygodniem leci czasu na dokończenie pracy coraz mniej a ja jeszcze sporo mam do zrobienia.
Do rzeczy.

Mam dwie klasy z relacją one-to-many

public class Artist
    {
        public int ArtistID { get; set; }

        [Required]
        public string Name { get; set; }
        public virtual List<Album> Albums { get; set; }
    }
public class Album
    {
        public int AlbumID { get; set; }

        [Required]
        public string Title { get; set; }

        public int ArtistID { get; set; } //ForeignKey
        public virtual Artist Artist { get; set; } ///navigation property
    }

I teraz tworzę sobie ArtistController w którym chcę mieć możliwość dodawania nowych artystów(Create) a następnie mieć możliwość dodania do nich albumów(CreateAlbum).

public class ArtistController : Controller
    {
        MusicContext context = new MusicContext();

        //wyświetlenie wszystkich artystów
        public ActionResult Index()
        {
            return View(context.Artists.ToList());
        }

        //dodanie nowego artysty
        public ActionResult Create()
        {
            Artist artist = new Artist();

            return View(artist);
        }

        [HttpPost]
        public RedirectToRouteResult Create(Artist artist)
        {
            context.Artists.Add(artist);
            context.SaveChanges();

            return RedirectToAction("Index");
        }

        //Informacje o Artyście - widzę tutaj jakie Albumy należą do tego artysty i mogę dodać nowe.
        public ActionResult Details(int id)
        {
            var listOfArtists = context.Artists.ToList();

            Artist artist = listOfArtists.FirstOrDefault(a => a.ArtistID == id);

                return View(artist);
        }

        public ActionResult CreateAlbum(int id)
        {
            Artist artist = context.Artists.FirstOrDefault(a => a.ArtistID == id);
            ViewBag.artist = artist;

            Album album = new Album();
            return View(album);
        }

        [HttpPost]
        public RedirectToRouteResult CreateAlbum(Album album)
        {
            context.Albums.Add(album);
            context.SaveChanges();
            return RedirectToAction("Index");
        }

    } 

Tak wygląda widok Details w którym tworzę ActionLink do metody akcji CreateAlbum żeby dodać nowy album do już istniejącego artysty.

@model RelationshipsPractice.Models.Artist

@{
    ViewBag.Title = "Details";
}

<h2>Details</h2>

<h3>@Html.ActionLink("Add Album", "CreateAlbum", new { id = Model.ArtistID })</h3>

<fieldset>
    <legend>Artist</legend>

    <div class="display-label">
        @Html.DisplayNameFor(model => model.Name)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>

    <h4>Albums</h4>
    <ul>
        @foreach (var album in Model.Albums)
        {
            <li>@album.Title</li>
        }
    </ul>
</fieldset>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.ArtistID })
    @Html.ActionLink("Back to List", "Index")
</p>

I tutaj mój widok CreateAlbum

@model RelationshipsPractice.Models.Album

@{
    ViewBag.Title = "CreateAlbum";
}

<h2>CreateAlbum</h2>

@{ Model.Artist = ViewBag.artist; }

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Album</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        @Html.HiddenFor(x => x.Artist)
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Jeżeli chcę to zrobić w ten sposób to dostaje błąd
{"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_dbo.Albums_dbo.Artists_ArtistID\". The conflict occurred in database \"RelationshipsTest\", table \"dbo.Artists\", column 'ArtistID'.\r\nThe statement has been terminated."}

Zresztą zdaję sobie sprawę że robię to źle. I tu moja prośba, żeby ktoś mi powiedział jak ja mam to zrobić na tym prostym przykładzie, żeby to działało a jednocześnie korzystało z tych relacji jakkolwiek ma to z tego korzystać.

Pozostało 580 znaków

2015-10-05 23:50
2

Przede wszystkim, nie korzystaj tak z ViewBaga. Co do przykładu, tu jest ciekawy:
http://weblogs.asp.net/scottg[...]he-asp-net-mvc-3-tools-update

Jeśli chcesz sprawdzić co jest nie tak u Ciebie - zachęcam zapięcie się debuggerem w metodzie i zobaczenie co się dzieje - jaki obiekt dostajesz na przykład.


Pozostało 580 znaków

2015-10-06 08:35
Złoty Kaczor
public ActionResult CreateAlbum(int id)
        {
            Artist artist = context.Artists.FirstOrDefault(a => a.ArtistID == id);
            ViewBag.artist = artist;

            Album album = new Album();
            return View(album);
        }

tworząc album nie potrzebujesz pełnego modelu artysty a jedynie jego id,
tak więc sugeruje zrobić to w ten sposób:

public ActionResult CreateAlbum(int id)
        {

            Album album = new Album();
            album.ArtistId = id;
            return View(album);
        }

dzięki temu w widoku masz już model albumu przypisany do artysty, teraz tylko model uzupełniasz nazwą albumu itp i dalej go zapisujesz.

edytowany 1x, ostatnio: somekind, 2016-12-13 18:26

Pozostało 580 znaków

2015-10-06 18:45
0

Dzięki! rzeczywiście działa, musiałem jeszcze tylko w widoku dodać @Html.HiddenFor(a => a.ArtistID)
Powiem szczerze że dzięki temu przykładowi chyba zacząłem rozumieć relacje. Do tej pory ich nie używałem i w gruncie rzeczy przekładało się to na dodawanie zdublowanych atrybutów w tabelach dwóch klas. Ale tak w gruncie rzeczy przeanalizowałem sobie kod który bym napisał gdybym z tej relacji nie korzystał i jest to w sumie "to samo", tyle że widzę teraz różnicę dlaczego powinno się używać tej relacji i jakie są z tego korzyści. Poćwiczę to trochę dokładając inne relacje i zobaczę czy rzeczywiście już wiem jak to działa ;)

@AreQrm co masz na myśli pisząc nie korzystaj tak z ViewBaga? Chodzi o to, żeby używać go tylko do przesłania zmiennych typu int, string itp.?

edytowany 2x, ostatnio: RideorDie, 2015-10-06 22:37

Pozostało 580 znaków

2015-10-06 22:54
1

Generalnie używania ViewBaga należy unikać jak ognia ;-). Czasem ma swoje zalety, ale w większości wypadków to ViewModel już powinien mieć te informacje. Pamiętaj, że do widoku nie musisz przesyłać modelu, a nawet nie powinieneś, a właśnie ViewModel - który może być identyczny z modelem (ja bym wtedy wrzucał model, architektura "encje na twarz i pchasz" ;-) ale to jest 1 procent takich sytuacji w komercyjnych projektach....), ale zwykle potrzebujesz czegoś więcej, albo połączenia informacji z kilku modeli, albo czegoś jeszcze innego, i tam właśnie, w ViewModelu to powinieneś umieścić. Nie w ViewBag.

Na początku może to być ciężkie do ogarnięcia, ale powoli wejdzie w krew ;-)

Tutaj masz ciekawy artykuł na temat nie używania ViewBaga:
http://zajacmarek.com/2014/09[...]tac-z-viewbaga-w-asp-net-mvc/


Przekazywanie encji do warstwy GUI to nie jest architektura tylko antyarchitektura. - somekind 2015-10-06 23:00
Zgadzam się. - AreQrm 2015-10-06 23:02
Dzięki za wytłumaczenie, na pewno zainteresuję się tematem ;) - RideorDie 2015-10-06 23:26

Pozostało 580 znaków

2015-10-07 14:53
0

@AreQrm Trochę sobie dzisiaj poczytałem o ViewBag i ViewModel ale po komentarzu somekind

Przekazywanie encji do warstwy GUI to nie jest architektura tylko antyarchitektura.
już się chyba pogubiłem.
W związku z tym mam jedno pytanie. Bazując na moim przykładzie a konkretnie tym fragmencie:

public ActionResult CreateAlbum(int id)
{
Album album = new Album();
album.ArtistId = id;
return View(album);
}



Czy powinienem klasę `Album` opakować `ViewModelem` jeżeli w widoku nie będę potrzebował korzystać z innych właściwości niż te z klasy `Album'?
Bo napisałeś że takie przypadki to 1% w komercyjnych projektach, i teraz nie wiem czy to jest ten 1% czy mam złe nawyki albo nie potrafię napisać "odpowiednich" klas.

Pozostało 580 znaków

2015-10-07 16:21
2
RideorDie napisał(a):

Czy powinienem klasę Album opakować ViewModelem jeżeli w widoku nie będę potrzebował korzystać z innych właściwości niż te z klasy Album'?

A będziesz potrzebował korzystać ze wszystkich właściwości klasy Album w widoku?
Będziesz przeprowadzał jakąś walidację danych wprowadzanych przez użytkowników przy tworzeniu albumu? Będziesz gdzieś miał dropdown z chociażby gatunkiem muzyki danego albumu?
Praktycznie nigdy się nie zdarza, żeby viewmodel nie różnił się od odpowiadającego mu modelu. Co więcej, jeden model będzie miał raczej wiele viewmodeli - oddzielne do listy, tworzenia, edycji, szczegółów, itd.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
edytowany 2x, ostatnio: somekind, 2015-10-07 16:21

Pozostało 580 znaków

2015-10-07 18:24
0

Przyznam szczerze że do tej pory jak tworzyłem jakiś viewmodel to wyglądał on tak. Tak się nauczyłem.

public class CreateAlbumViewModel
{
      public Album Album {get; set;}
      (...)
}

Po tym co napisałeś, zacząłem widzieć głębszy sens tworzenia viewmodeli ale nie do końca wiem jak to zrobić. Napisałem coś takiego:


 public class Album
    {
        public int AlbumID { get; set; }
        public string Title { get; set; }

        public int ArtistID { get; set; } //ForeignKey
        public virtual Artist Artist { get; set; } ///navigation property
    }

public class CreateAlbumViewModel
    {
        public string Title { get; set; }
        public int ArtistID { get; set; }
    }
public ActionResult CreateAlbum(int id)
        {
            CreateAlbumViewModel vModel = new CreateAlbumViewModel();
            vModel.ArtistID = id;

            return View(vModel);
        }

        [HttpPost]
        public ActionResult CreateAlbum(CreateAlbumViewModel vModel)
        {
            var album = new Album();
            album.ArtistID = vModel.ArtistID;
            album.Title = vModel.Title;

            context.Albums.Add(album);
            context.SaveChanges();
            return RedirectToAction("Index");
        }

Czy w taki sposób powinienem napisać viewModel dla tego przykładu ?

edytowany 1x, ostatnio: RideorDie, 2015-10-07 18:25

Pozostało 580 znaków

2015-10-07 18:34
2

Tak.
Teraz inne rzeczy :-), powolutku.
Następny krok to byłoby wyrzucenie tego z kontrolera.
Poza tym, jak już tworzysz obiekt, mógłbyś to robić przez konstruktor który przejmuje parametry, nie przez propertiesy.

Edycja, kod z palca bez VS :)
Konstruktor:

public Album(int artistId, string title)
{
    this.ArtistID = artistId;
    this.Title = title;
}

A co do wyrzucenia to np do jakiejś osobnej klasy, nie chce jej szumnie nazywać np serwisem (czy command handlerem, bo za dużo wtedy trzeba by napisać infrastruktury do tego) , bo to za dużo informacji na raz by było. Nie chce Cie też wprowadzać w tematy repozytoriów, bo osobiście nie przepadam za nimi. Generalnie im uboższy w logikę kontroler, tym lepiej. Najlepiej gdy nie posiada jej w ogóle. Ale nie wiem czy to tematy dla Ciebie na teraz, bo trzeba by poruszyć też inne kwestie.


edytowany 2x, ostatnio: AreQrm, 2015-10-07 20:35
Pokaż pozostałe 7 komentarzy
@AreQrm zależy mi na tym żeby moja aplikacja na pracę inżynierską była napisana na dobrym poziomie, bo po obronie będę szukał pracy w branży i chcę pisać kod tak jak powinno się to robic, zacząłem drążyć temat i sposobem na pozbycie się tej logiki z kontrolerów jest command handler o którym pisałeś. Nie znalazłem innego "prostszego sposobu" na zrobienie tego. Mam w aplikacji 5 encji i generyczne repozytorium ninject. Proszę skomentuj jakoś czy rzeczywiście mam się handlerem zainteresować. Podrzuć coś do zapoznania się w tym temacie jakbyś miał ;p - RideorDie 2015-10-15 18:19
Musisz wygooglać sobie sam coś ciekawego. Ja znalazłem :https:lostechies.com/jimmybogard/20[...]on-a-diet-posts-and-commands/ ale poszukaj jeszcze też innych. Sam się tego nauczyłem już pracując z tym :) Nie mam żądnych materiałów. Do tego na tym etapie nie koniecznie bym się tym interesował na Twoim miejscu, gdyż po 1 wiąże się to z szerszym tematem CQR oraz CQRS (poczytaj o tym np tu http:martinfowler.com/bliki/CQRS.html), po drugie nie wiesz czy będzie to wykorzystywane tam gdzie trafisz. Narazie napisz pracę, obroń ją, a refactoring - AreQrm 2015-10-15 20:14
zostaw sobie na później. Warto znać ten temat i napewno się z nim zapoznać, ale nie koniecznie już teraz próbować stosować, skoro masz dość prostą aplikację na razie. A cel, jest najważniejszy - napisać i obronić. Powtórzę : refactoring zrobisz sobie później. A w tym wcześniejszym linku z lostechies, jest cała seria artykułów o tym, nie tylko ten jeden. To nie był 1 z serii! Sorry:) - AreQrm 2015-10-15 20:17
Dzięki za odpowiedź, jakoś nie dawało mi to spokoju, byłem ciekawy. Masz rację, zostawię to sobie na później jak zrobię inne rzeczy które czekają w kolejce, a jest ich jeszcze sporo ;) - RideorDie 2015-10-15 21:35
To dobrze że chcesz się rozwijać, jest wiele tematów gdzie warto. Sam jestem na tej drodze ;-) Ale trzeba mieć odpowiednie priorytety. :) - AreQrm 2015-10-15 21:50

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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