Sitter och funderar lite hur andra gör när de ska implementera UML-modeller (eller andra typer av modeller) av en applikation mot en databas.
Låt oss säga att jag har två klasser som jag ska implementera i c# och i en relations databas. Vi har klasserna User och Adress (två enkla exempel). Associationen mellan dem är 1-1, alltså en användare kan ha en adress och en adress kan endast höra till en användare (användare måste ha en adress).
I DB:n implementerar jag det exempelvis
User
-UsrId (PK)
-AdressId (FK)
-Firstname
-Surname
Alt2
Class:User
private int m_UsrId;
private int m_AdressId;
private string m_Postcode;
private string m_Firstname;
private string m_Surname;
Och hur löser ni det när ni ska presentera data från dessa klasser. Lått oss säga att jag har metoder i varje klass för att ladda dem från databasen, loadUsr(usrId) och loadAdress(adressId). Hur gör ni där för att binda sambandet mellan objekt av de två klasserna. Gör ni så att ädressen laddas samtidigt när man anropar metoden loadUsr(usrId) i User.
När förhållandet mellan två objekt är "konstant", alltså förhållandet 1-1 som du kallar det, så kan man väl likaväl ha allt i samma objekt? Jag ser inga som helst fördelar med att dela upp det, när de ändå ska behandlas lika vid presentationen.
Bara för att det är OO, behöver man inte gå till överdrift. ;)
Re: Klassbyggnad vid databas, umlmodell av applikationen
erka skrev:
Hej!
Gör ni så att ädressen laddas samtidigt när man anropar metoden loadUsr(usrId) i User.
Generellt så laddar alla sådana här som referenser direkt, om man vet att de alltid kommer att användas. Är det en bild eller något annat så skulle jag inte rört dem
Jag håller med Pace, är du 100% säker på att du alltid kommer ha en 1-1 förhållande mellan användare och adress så lägg det i samma tabell.
Är det däremot så att du funderar på att bygga ut ditt system så att man kan ha flera adresser så normaliserar du din databas så som du har gjort. (återigen en avvägningen mellan prestanda och design).
Själv så hade jag inte laddat in adressen samtidigt som användaren. Även om du vet att du kommer att använda dig av adressen så kan det vara bra att sprida ut tiden det tar att ladda ditt objekt. Här kommer ett överdrivet exempel.
Säg att du har 100 användare och 100 adresser. Det tar 10 sekund att ladda och skapa dina användare, och 10 sekunder att skapa adress objekten.
Om du då väljer att ladda båda direkt, så måste man vänta 20 sekunder innan du kan gå vidare. Om du istället väljer att ladda alla användare så väntar man 10 sekunder, och sedan när du behöver använda adressen så tar det endast 0.1 sekund att ladda enskilt adress objekt. Det betyder att din applikation "verkar snabbare" än vad den är eftersom du har spridit ut laddaningen över längre tid.
Visst finns det vinster att ladda båda direkt, då detta kommer att genera mindre trafik mot databasen (givetviss beroende på HUR du hämtar datan), vilket är bra ur skalbarhetssynpunkt.
Min klass hade sett ut så här.
Class:User
private int m_UsrId;
private int m_AdressId;
private string m_Postcode;
private string m_Firstname;
private string m_Surname;
private Adress m_Adress;
#region Properties
.
.
.
public Adress Adress{
get{
if(m_Adress == null && m_AdressId != 0)
//-- Load and fill Adress objekt
return m_Adress;
}
}
Jag gör som Gladh.
Men jag har gjort så att jag kan välja om jag vill hämta min User "Detailed" eller "Simple". Om jag vet att jag kommer vilja visa adressen så använder jag "Detailed" och då skapas Adress-objektet, data hämtas i samma veva som användaren och de kopplas.
Ingen jättefördel i detta sammanhang men när jag hanterar stora objekt med många till många relationer så är det smidigt :)
Instämmer med ovan, jag gör ungefär som Gladh.
När det till exempel gäller adresser så använder jag 1-n relation. En användare kan ha fler adresser. I det fallet så ser mina klasser ut så här:
Class:User
private int _Id;
private int _addressId;
private string _firstName;
private string _surName;
private Addresses _addresses;
Class:Address
private int _Id;
private string _street;
private string _zipCode;
private string _city;
private string _country;
Class:Addresses (Detta är en starkt typad Collection)
SJälva relationen mellan User och Address sker i en relationstabell som innehåller UserID och AddressID.
När jag fyller mina Userobjekt så använder jag Lazy Load, dvs. jag fyller inte min AddressCollection förrän den behövs. Detta sker på samma sätt som Gladh beskriver ovan.
public Adresses Adresses{
get{
if(_Adresses == null)
//-- Load and fill Adresses objekt
return _Addresses;
}
}
Om relationen är 1-1 som du beskriver så skulle jag lägga ihop allt till en tabell och en klass. Men om man vill ha en skalbar lösning så är det ingen bra idé, man vet ju oftast inte om relationen kommer förbli 1-1.
Jag skrev som ett exempel för att påvisa vad jag menade, skulle tagit ett annat exempel än 1-1. Pace, jag kanske skulle ha ett företag som skulle ha adresser i samma tabell och också länkas 1-1. Nu var detta bara ett exempel, men jag borde varit lite tydligare
Det var mer det Jon var inne på som jag funderade på, borde formulerat min fråga lite bättre, men var rätt trött :e
Mer situationen om man har en klass med många referenser, så kan det som sagt bli tungladdat om man fyller alla objekt med fullständig data. Just nu sitter jag och pillar med en kokboksapplikation och har märkt att det blir lite tungrott vid själva laddningen av en instans av kokboksklassen
Klassen kokbok innehåller följande
Class: cookbook
int m_ID;
string m_title;
DateTime m_created;
User m_creator;
ArrayList m_recipes;
ArrayList m_cookbookCategories;
Nu när jag laddar en kokbok går det till på följande sätt, vilket känns lite klumpigt, jag laddar den genom metoded loadCookBook(int id).
Då laddas även en användare och bind genom metoden loadUsr(int id) i User, även vilka recept som hör till boken genom att använda metoden loadRecipes(int cookbookId) i en registerklass, även metoden loadCategories(int cookbookId) ifrån en registerklass.
Problemet jag har här är att i tillexempel metoden loadRecipes(int cookbookId) så laddas även klassen Recipes alla associationer bland annat en collection av ingredienser och en hel del annat som jag inte alltid behöver när jag laddar en kokbok. Det är här jag inte riktigt har bestämt mig för hur jag smidigast ska lösa det hela, man vill ju inte heller att kopplingen mellan klasserna ska bli hög.
Använd Lazy Load, dvs ladda dina collections när de behövs enligt det exemplet Gladh tog upp. Alltså fylls kollektionerna när man anropar Get propertien för den kollektionen.
På så sätt får du lättviktade klasser som kan användas i alla lägen. Jons exempel med Detailed och Simple är också en lösning, skulle dock inte använda den själv. Använd istället olika konstruktörer som hanterar detta.
Samtidigt så beror det på hur klasserna laddas i de olika funktionerna Detailed och Simple. I de flesta fall så spelar det ingen roll om du använder Jons eller min och Gladhs lösning.
Det andra jag tycker du ska göra är att kolla in cache. Innan databasen anropas för att fylla en kollektion så kollar man om objektet redan finns i cachen, om det inte finns så anropas databasen, objektet finns, läggs i cachen.
Det kan bli mycket i cachen men om man sätter en lite kortare tidsgräns så kommer bara de objekt som används ofta att ligga kvar länge i cachen.
Jag använder bara Simple om jag tror att jag inte kommer att använda de refererade objekten. Skulle man mot förmodan vilja ha dem så laddas de med lazyload.
Fördelen med detailed är att alla objekt laddas med samma connection vilket snabbar upp databashämtningen, särskilt som databasen nästan alltid ligger på annan server...
Precis som PDahlen säger så är cachening alltid bra, men jag tycker att det är knepigt och anser att det inte finns en generell modell utan jag bygger gärna olika funktionalitet för det i olika applikationer och försöker hantera cachen så nära UI och/eller så nära mitt DAL som möjligt.
PDahlen: bra idé med olika konstruktörer, mer användarvänligt, det ska jag nog gå över till :)
Fördelen med detailed är att alla objekt laddas med samma connection vilket snabbar upp databashämtningen
Det där beror helt och hållet på hur din kod ser ut. Är det inte korrekt skrivet så lär du sabbotera mer än du vinner på det.
Vilket brukar vara det vanliga fallet :(
Jag ska kolla mer på cachedelen, tack för allas åsikter. Oftast gör jag något liknande Gladhs alternativ.
OT:
Tänkte lite på folk skriver sina databashanteringsmetoder för klasser, tex metoder för att spara ner, uppdatera objekt. De som inte använder någon dbwrapper.
Själv brukar jag ha en metod i varje klass som heter tex saveUsr(), den anropar en privat metod som heter isUsrCorrectForDb():bool och är alla privata variablar korrekta anropar den dalet som sparar ner objektets värden i db:n. Sett en del lösningar där save metoden tar emot alla parametrar som ska in i databasen istället för att hämta dem från det populerade objektet. Hur gör folk ?
Hej igen. Tänkte kolla på hur folk gör med associationer där det behövs registerklasser. Som ett exempel tänkte jag ta användare, lått oss säga att vi har en app som hanterar användare i klassen User, vi behöver även funktionalitet för att få fram alla användare, användare som matchar ett visst kriteria etc.
Jag brukar här skapa en register klass som heter kanske i detta fall UserReg som innehåller en ArrayList med User-objekt och metoder för att tex sortera min ArrayList, fylla registret med användare. Har jag en metod i min registerklass för att hämta låt oss säga alla användare gör jag på följande sätt.
Anropa DAL och får ut en lista med ID't på alla användare i en ArrayList, går igenom denna collection och för varje position skapar jag en temporär användare, anropar metoden loadUser(int userId) på detta temporära objekt, slänger in objektet i min ArrayList med användare och hoppar till nästa plats i min collection jag hämtat ut från mitt DAL.
Någon som har några synpunkter eller andra åsikter
Jag använder mig av starkt typade collections. Skapar en klass, t.ex. Users, som ärver CollectionBase.
Namespace Modules.Accounts.Business
<Serializable()> _
Public Class Users
Inherits CollectionBase
Public Sub Fill(ByVal Reader As IDataReader)
While (Reader.Read = True)
Dim aUser As New User(Reader)
Me.Add(aUser)
End While
End Sub
#Region " Creating strongly typed collection "
Public Sub Insert(ByVal index As Integer, ByVal NewUser As User)
If IsExist(NewUser) = False Then
InnerList.Insert(index, NewUser)
End If
End Sub
Public Sub Remove(ByVal aUser As User)
InnerList.Remove(aUser)
End Sub
Public Function Contains(ByVal aUser As User)
Return InnerList.Contains(aUser)
End Function
Public Function IndexOf(ByVal aUser As User)
Return InnerList.IndexOf(aUser)
End Function
Public Function Add(ByVal NewUser As User) As Integer
If IsExist(NewUser) = False Then
Return InnerList.Add(NewUser)
End If
End Function
#End Region
#Region " Business Rules "
Private Function IsExist(ByVal newUser As User) As Boolean
Dim aUser As User
For Each aUser In Me
If aUser.Id = newUser.Id Then
Return True
End If
Next
Return False
End Function
#End Region
End Class
End Namespace
Klassen ligger i BusinessObjects och i min BusinessLogic har jag en funktion som först kollar om objektet finns i cachen annars anropar den en funktion i DAL som hämtar alla användare i databasen och returnerar en IDataReader.
Patrik då kommer du gå över till O/R Mappers nu när Generics kommer i .NET 2.0. Där kan man ju skriva sin BaseCollection så att den blir typad, och därmed få alla de fördelar som du har. Visst det tar lite längre tid att fylla ett objekt via reflextions än att göra det i kod, men man spar bra mycket tid vid utveckling på det.
Håller på att skriva om min O/R Mapper till .NET 2.0 kod, och det verkar lovande. Jag önskar dock att jag kunde få en enum över alla mina fält i mina klasser som jag kan komma åt i min BaseGenericCollection, det hade gjort koden lite snyggare när man skall sortera collection efter olika fält, samt när man skapar Querys som skall användas för att hämta ut delmängd av data från databasen.
Får väl göra någon fullösning tills jag kommit på den brilljanta lösningen :)
Jag vet faktiskt inte än om jag ska börja med O/R Mappers. Som det är nu så kör jag CodeSmith. Jag har skapat mallar för mina collections så CS genererar ca 95% av min collectionkod. Med andra ord inte så mycket tid. De sista 5% är de få collections där jag behöver ha någon speciell funktion för sortering eller dylikt, vilket inte hänt mer än två gånger hittils.
CodeSmith är suveränt. Jag håller även på och fixar med mallar för SPs, DAL och BusinessObjects. Det finns rätt många mallar på nätet som man kan tweaka så de passar ens egen lösning. Det skulle innebära att CS generar 70-80% av mitt DAL och BO. Inte så illa pinkat.
Då finns det inte så mycket utvecklingstid att spara in på och så slipper jag fylla objekt via reflections.
Men man ska aldrig säga aldrig. Det var inte så länge sen jag inte ens använde kodgenerering.
267 ms totalt · 4 externa anrop · v20260731065814-full.a51de22e