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

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

2

Przede wszystkim, nie korzystaj tak z ViewBaga. Co do przykładu, tu jest ciekawy:
http://weblogs.asp.net/scottgu/ef-code-first-and-data-scaffolding-with-the-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.

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

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

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/dlaczego-nie-warto-korzystac-z-viewbaga-w-asp-net-mvc/

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.

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.

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 ?

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.

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