Förslag önskas för lösning på ASP.NET MVC utmaning


Sitter under de regniga timmarna av sommaren och hackar lite på en ASP.NET MVC lösning för att lära mig mera och kanske till och med skapa något användbart. Det är en lösning för att planera evenemang typ CodeCamps och jag har kommit en bit på vägen men märker att jag hamnar åter och åter i samma mönster… Därför tänkte jag se vad du skulle rekommendera för lösning på det här “problemet”:

Jag har en objektmodell som ser ut så här (och det är bara en liten del av den hela modellen):

CodeCampModel Med andra ord, ett “event” består av ett eller flera “tracks” som i sin tur består av en eller flera “sessioner”. Jag använder mig av Entity Framework (så klart) för mappningen mot databasen och utmaningen som jag har är hur jag ska vandra “uppåt” i kedjan (från track till event) utan att behöva göra överdrivet många anrop till databasen.

Jag har nämligen skapat ett antal vyer, exempelvis listor över “tracks”, “events” och “sessioner”, samt vyer för att editera, föreslå nya osv. Men det jag märker är att jag behöver kontinuerligt spara undan ID’t på den överliggande strukturen. Alltså, vilket “event” som respektive “track” hör till och vilket “track” som respektive “session” hör till. Detta behövs för att kunna filtrera listorna på det aktuella evenemanget eller aktuella spåret efter editering osv.

Ännu mer förklaring:

När jag har en lista på evenemang så kan jag välja att titta på det evenemangets “tracks”. Den listan innehåller alltså en filtrerad lista av alla tracks som hör till det aktuella evenemanget. Om jag då väljer att exempelvis editera ett “track” så måste jag alltid skicka med “eventId” för att kunna komma tillbaka till rätt evenemang efter editering, eller så ska jag kontinuerligt hämta eventId för respektive spår vid anrop till databasen.¨

Vilket är det bästa alternativet? Som jag ser det har jag följande alternativ:

1) Vid varje anrop till databasen efter “track” använda .Include(“Event”) för att få tillbaka information om evenemanget också. Eventuellt göra överlagrade metoder på min TrackRepository som då skulle kunna vara: GetTracks(bool includeEventInResult) osv… Leder till ett ganska “tjatigt” repository, eller?

2) Behålla eventId som en parameter till alla anrop på TracksController-objektet. Vilket jag märker efter ett tag kan bli ohållbart eftersom det också kommer att resultera i att jag kommer att behöva skicka det ned till SessionsController-objektet också för att i efterhand kunna navigera tillbaka.

Är frågan tillräckligt luddigt ställd? Vad är din rekommendation?

Comments (9)

  1. Hejsan Johan!

    Jag antar att det är imellan varje POST-GET session du vill använda dig av ditt Id för att spåra vilket "Event" du är på inte sant?

    Jag skulle ta det enkla före det krångliga och helt enkelt köra en liten ful select-sats på något sätt.

    T.ex.: "Select t.Event From tracks t Where t.Id == $ID".

    Antar att man antingen kan köra någon pseudo-SQL eller riktig SQL igenom Entity Framework? Lösningen i NHibernate skulle helt enkelt vara att ladda ett Track och sedan köra track.Event.Id bara, då sköter den allt som behövs och allt är lazy-laddat.

    Vi får hoppas att Entity Framework 2 erbjuder bättre möjligheter. 🙂

    Mvh Kim Johansson

  2. JohanLindfors says:

    Kim: Tack för förslaget, och det går att göra något liknande med EF (speciellt version 4 som är nästa), men då kommer jag också att göra två anrop till databasen vilket i sig blir lite pratigt kan jag tycka.

  3. Jonas Cannehag says:

    Hej Johan,

    jag har inte använt EF i någon större utsträckning. Däremot Linq to SQL en del.

    Det jag undrar är: När du i din TracksController visar upp och editerar ett specifik Track, innehåller då in din Track entitet ett EventID? Eller döljer EF detta helt och hållet?

    I Linq to Sql syns ju både ID och själva entiteten på en relation som properties.

    Man vill ju självklart kanske inte köra Include() på den eftersom man i det läget inte är intresserad av hela Event objektet men eftersom EventID ligger som ett fält på varje Track så borde EventID vara publik.

  4. Morten says:

    Är det inte så att du vill gå via eventet varje gång du accessar tracks och vidare sessioner. Då kan du använda lazy loading och annat. Visst det blir pratigt, men samtidigt är det olika entiteter du arbetar med. Samtidigt, hur mycket trafik tänker du köra via systemet? Är det lönt att lägga ner mödan på att få en sql fråga istället för två?

    Om du arbetar med ett repository pattern kan du ju dessutom förändra det i efterhand och istället satsa på att fånga in beteendet i början.

    Då var alltså mitt förslag att du alltid gick via event, d.v.s. ett event är aggregats roten för de andra entiteterna.

  5. Hej,

    Jag är oftast försiktig med att använda Include() eftersom tillexempel Refresh() inte uppdaterar hela grafen och det kan vara otydligt för den som använder koden.

    Jag är även försiktig med Include() då exempelvis hårt typade listor kan köra över den specifikationen. Exempelvis om du väljer att hämta List<Events> myEvents = GetEvents()… så får du bara eventen och inte de övriga entiteter du inkluderat i din graf.

    Detta är naturligtvis förvirrande för den som skall använda din kod och i mina ögon en designbugg.

    Om du har kvar ditt context kan du pröva med IsLoaded -> Load() på dina refererade objekt för att jobba dig vidare. Annars kan du kanske editera din EDM manuellt och lägga in relevanta ID-referenser som egna fält.

  6. Jag hade nog gjort någonting i stil med ditt andra förslag. Då kan man ju, för att få en snygg url, skicka in namnet på eventet istället för id’t. Det kommer gå lite långsammare att hämta, men bör tjäna in det genom att du slipper includa event varje gång. Din routing skulle då bli någonting i stil med {event]/{track} och sedan även ner till session ({event}/{track}/{seassion}). Din url skulle då fungera som breadcrumbs och det skulle, enligt mig, bli ett logiskt flöde.

    Annars kan du, om jag inte har helt fel, hämta id’t genom EventReference propertyn. Inget jag skulle föreslå dock då det blir ganska fult.

  7. JohanLindfors says:

    Mattias: Jag gillar ditt förslag med {eventId}/{trackId}/{sessionId} url-routen, men undrar då lite hur jag skulle förändra min objektstruktur eller kanske till och med min datastruktur (som jag har utgått från)…

    Ska {trackId} vara relativt till {eventId} eller ett unikt värde? Det känns som om det bör vara relativt till {eventId} för annars så skulle potentiellt codecamp/1/2 och codecamp/12/2 peka på samma ställe vilket känns väldigt avigt. Så nu funderar jag på att ha ett unikt id i varje entitet som används för själva strukturen och sedan relativa id’n som som används för url…

    Kommentarer?

  8. Jag hade skrivigt om frågan i repositoryn för att kontrollera så att du har en "godkänd" parent. Nu använder jag själv inte entity framework allt för offta utan föredrar nhibernate. Men i nhibernate skulle man iaf kunna skriva en fråga som, i linq, skulle sett ut ungefär på detta vis: Where(x => x.Event.ID == eventId && x.ID == trackId). Man skulle då bara selecta ditt track, men även kolla så att det finns under det event som url’en pekar på, och gör detta i en fråga.

    Detta bör gå att lösa i ef också?

  9. richard says:

    istället för att bara använda namn som "id" i sökvägarna kanske man skulle använda sig av en kombination av både namn och id, dvs. /eventname-e{id}/trackname-t{id}/sessionname-s{id}/ – motverkar att dubbletter skapas (skalbart), snabbare svarstider och sökvänliga länkar (lite fulare).

    gör sedan en fråga så som Mattias ovan nämnt som har alla 3 potentiella id i åtanke.