Jag har ett ASP.NET 2.0-projekt i form av en foto-sida. Mitt upplägg är så att jag har entitetsklasser som endast innehåller data och ingen logik. Dessa är just nu Album, Photo och Comment. Dessa relaterar till varandra genom att Photo har ett fält PhotoId och Comment har PhotoId/AlbumId och UserId. Jag använder Access för detta då jag tänkte att det inte skulle krävas allt för mycket prestanda plus att det var enkelt att ta backup och se datan på en dator som har Access installerat. Men men. För att visa senaste 10 kommenteraren kan du väl göra en join för att hämta kommentarer + användare + album? Ah.. du hade kommentarer på foton också... Tack för svaren. Det är förstås tilltalande att ha en ren objektmodell som "löser allt". Men som du ser, det blir lätt prestandaproblem. Fråga 1 du ska ställa dig är var prestandaflaskhalsen egentligen ligger. Har du t.ex. indexerat de kolumner som du söker på? Icke indexerade databaser är det vanligaste prestandaproblemet. Att skapa objekt går snabbt. Tackar för inlägget! Du kanske kan cacha undan lite data? Ofta är det ju samma som man använder upprepade gånger. Jakob, "Du kanske kan cacha undan lite data? Ofta är det ju samma som man använder upprepade gånger." 1. Det är det som är så konstigt. Att den verkar ha en minimi-tid på 4 hundradelar varje anrop. Jag använder Data Application Block med OleDb som drar i trådarna. Här är kodexempel: Glömde en viktig del :) Rikigt stor skillnad, trodde inte det skulle vara så mycket. Värt att lägga på minnet! Jakob, det finns fundamentala skillnader i hur Access och SQL server/express arbetar. Okej. Det var bra att veta. Ja men man bör nog alltid sträva efter att inte hämta mer information än nödvändigt och inte hämta den innan den verkligen skall användas. Ola: "tack vare en bra O/R Mapper kan object läggas i cache i den vilket gör att man itne behöve rgå så ofta mot databasen." Hittade denna någonstans, kommer inte ihåg var. Jakob: Joo, den cachningen är ju ganska enkel. Men finns det något mönster hur man går tillväga om man vill ha en gemensam cache för både hämtning av enstaka objekt och av listor av objekt. När man hämtar listor kan man göra det på olika sätt i olika metoder, t.ex. olika sorteringar och urval. Då blir det inte lika självklart hur cachningen fungerar? Hej, Johan: Tack för svaret! Ola att man kom på kod som if och cursors och annan skit som bara sabbar för produkten man bygger då man till slut inte vet vem som ansvarar för vad beror på att alla system inte kräver skitkad lösning etc. Vad menar du att det ligger för businesslogik i SP? Join-satserna? De är väl starkt knutna till datalagret? Jakob, jag menar att du ska hålla så lite data som möjligt i minnet. T.ex. lista foton i ett album skulle kunna se ut så här: Johan, jag förstår absolut fördelarna med den modell du förespråkar. Jag tilltalas också av den teoretiska enkelhet och tydlighet som "äkta OOP" ger oss, samt fördelen men att inte knyta sig till en specifik DB-motor. Teoretiskt sett är det jättefint. Men ärligt talat, hur ofta byter du ut SQL server mot XML-filer. Eller flyttar till Oracle. Har det ens någon gång i världshistorien hänt att man bytt databasmotor och sedan inte varit tvungen att programmera om stora delar av systemet ändå? En teoretiskt perfekt objektmodell kanske är ett självändamål i akademiska sammanhang. Men objektmodeller är helt meningslösa för slutanvändarna. De bryr sig ju bara om funktioner, utseende, prestanda och sånt. Ola. Bryter du inte direkt mot vad du skriver när du anropar koden GetAllActiveProjects(), borde det inte vara GetAllProjects() och sen i affärslagret loopa genom och kolla vilka som är aktiva? Intressanta kommentarer annars... Erik, Johan, nej du skrev inte att join inte får användas, vilket jag inte trodde heller. Jag menade att affärslogik kan ligga i en join. Och enligt mig skall det ligga där om det gör otroligt mycket för prestandan. Vilket det kan göra. Mitt exempel med 1200 projekt var kanske inte så bra. Låt säga att du har en tabell med 500.000 fakturor i stället, och du ska ta fram en lista på de fakturor som en användare får se. Ska du då hämta alla fakturor till BOL och sedan med en for-loop kolla behörigheten? Varför söka seriellt när du kan använda dig av databasens index? Varför skicka 499.000 rader mellan databas och applikationen helt i onödan? En riktigt bra O/R mappare löser det här åt dig. Den kan översätta frågor mot objektsstrukturer till Joins när det behövs. Ola, Jag har aldrig skrivit en avancerad O/R mappare men de jag använt mig av (NHibernate / NPersist) har den möjligheten inbyggd. Läste än artikel tidigare http://lakshmik.blogspot.com/2005/04/how-to-call-c-function-inside-yukon.html . Kan ju vara intressant att använda då man slipper ha en del affärslogik i SP. O/R-mappning och prestanda
Jag har sedan en klass, PhotoAlbum, som hanterar persistensen av dessa. t.ex. PhotoAlbum.GetAlbums(), PhotoAlbum.GetPhotos(int albumId), PhotoAlbum.InsertPhoto(Photo photo) osv osv.
1. Är detta generellt en bra lösning på hur man hanterar O/R-mappning osv?
Nu har jag märkt att det på vissa sidor tar lång tid att ladda dessa. Framsidan som visar mycket statistik tar nu 0,5s (!) att ladda och allt är t.om inte klart :)
Det jag märkt är att varje anrop verkar ta minst 4 hundradelar som ni ser i följande lista:
I början
Hämtat alla album 0,060
Hämtat ett album 0,041
Hämtat alla foton från ett album 0,058
Hämtat ett foto 0,038
Räknat alla album 0,038
Räknat alla foton 0,038
Hämtat alla personer 0,038
Laggt till ett photo 0,040
Jag använder "Application Data Block" och OleDb för att komma åt datan.
Anledningen att den tar så lång tid att ladda (sidan alltså) är att det är många anrop som räknar olika saker. Så vid typ 10 anrop så tar det upp mot en halv sekund att ladda. Och nu tänkte jag visa de senaste kommentarerna på framsidan och då hämtar jag alla senaste Comment-objekt. Sen måste jag hämta användarnamn och album-namn separat för dessa enligt min modell då inte de finns i Comment-objektet? Dvs, 10 användarnamn, 10 album-namn om det är 10 senaste. Och dubbla det om det är för fotona också som också har kommentarer.
2. Hur brukar ni lösa sådana här scenarion där man egentligen vill hämta all data genom ett databasanrop men eftersom jag har ovanstående modell får man hämta varje relaterat objekt separat?
Vore kul om ni skrev hur ni bryukar hantera liknande scenarion.
//JakobSv: O/R-mappning och prestanda
SELECT TOP 10 Comment.Name, User.Name, Album.Name FROM Comment INNER JOIN User ON Comment.UserID = User.ID INNER JOIN Album ON Album.Id = Comment.AlbumId ORDER BY Comment.Date DESC
(Jag hittade på lite kolumnnamn eftersom jag inte vet hur din databas ser ut exakt, du får "översätta" det... )Sv: O/R-mappning och prestanda
Då kan du göra en OUTER JOIN och ändå få till det rätt tror jag.. hmm.. men det blev krångligt då iofs...
SELECT TOP 10 Comment.Name AS Kommentar, User.Name, Album.Name AS AlbumNamn, Photo.Name AS FotoNamn FROM Comment INNER JOIN User ON User.ID = Comment.UserID LEFT OUTER JOIN Album ON Comment.AlbumId = Album.ID LEFT OUTER JOIN Photo ON Comment.PhotoId = Photo.Id ORDER BY Comment.Date DESC
Om AlbumNamn är NULL så är det ett Foto och tvärtom så du kan detektera det i din applikation...
Hmm.. Jag kanske vände på LEFT/RIGHT...
Äh... vet inte om det blev bra det här.. ;)Sv:O/R-mappning och prestanda
Jag vet att det går att få ut kommentarerna med en SQL-sats. Men det jag menade var att min modell ser ut som jag förklarade ovan med entitetsobjekten och metoderna för att få ut de olika relaterade entitetsobjekten vid behov utifrån de främmande nycklarna i objekten. Det blir krångligt om jag ska börja göra en specialanpassad Query/Metod för varje typ av informationssammansättning jag vill ha. Och vad ska returneras i det här fallet när det behövs lite blandad info från alla tre entitetsobjekt?
Så frågan är hur man kan lösa detta på ett smidigt sätt utan att bygga en speciell query för varje situation. Jag vill ha ett DAL som går att använda till allt utan att behöva modifiera för prestanda vid nya typer av datasammansättningar i presentationslagret.
(För övrigt så är kommentarerna på foton och album fristående. Så det är lika dan query för att hämta kommentarer för de båda förutom just vilken tabell det hämtas ifrån av foto resp. album.)Sv: O/R-mappning och prestanda
Generellt är jag ändå emot principen att ta fram listor baserade på en samling av objekt som lever ett kort liv i minnet och sedan kastas bort, bara i syftet att presentera en HTML-tabell för en användare. Det är inte smart att implementera objektorientering på det sättet. Gör helt enkelt en metod:
Photos.GetPhotoList(albumId) As SqlDatareader
Det är objektorienterat och ger god prestanda. I denna SQL-fråga kan du såklart (och bör du) joina mot andra tabeller om datamodellen kräver det, trots att dessa tabeller egentligen hör till andra objekt. Det blir kanske lite mindre objektorienterad perfektionism. Men det är en akademisk fråga som dina användare struntar i. Syftet är väl trots allt att bygga ett bra system och inte en snygg objektmodell?
Edit, tillägg: Det finns ingen OO-teori som säger att datamodellens tabeller måste mappa till objekt i förhållandet 1:1. (hoppas jag!)Sv:O/R-mappning och prestanda
1. De flesta sökningar går nog mot primärnyckeln vilka är indexerade. Det som är flaskhalsen verkar vara att varje kommando som exekveras har en startsträcka på 4 hundradelar vilket blir ganska mycket tid om man exekverar många frågor. Detta kanske är en begränsning i Access som kanske måste öppna upp filen osv vid varje fråga? Är det stor skillnad vid användande av en "riktig databas", dvs är det okej att ha en sådan här modell eller är den helt uppåt väggarna?
2. Det kommer inte vara någon större belastning på sidan så jag gjorde mest den här objektmodellen för att mer testa på hur man kanske skulle tänkt vid större projekt. Om jag också vill komma åt modellen från något annat gränssnitt än just en webbsida. Sammanfattningsvis nog mer i akademiskt intresse som du uttryckte det.
3. Det du säger medför väl att det kommer bli nedrans massa olika liknande metoder, en för varje scenario beroende på vilken kombination av data man är ute efter (dvs vilka olika tabeller man joinar ihop. ibland vill man ju ha kommentarerna ensamma, dvs en tabell, och ibland vill man ha med vilket album och vilken användare också, osv osv). Mitt mål var lite att slippa uppdatera data-lagret allt eftersom jag vidarutvecklar webb-delen..?
4. Ang. OO-teorin så har du säkert rätt. Men man ju inte ha entitetsklasser/objekt för varje kombination av data som kommer upp från datalagret. Så då är frågan hur man ska lösa det om man vill ha en objektmodell?Sv: O/R-mappning och prestanda
Hur fungerar din O/R mapper. Stödjer den lazy-loading så kanske det är att föredra dvs om du har klasserna "PhotoAlbum" som innehåller en Collection av "Photo", om du laddar in ett nytt album kommer den då direkt att ladda in "Photo-collection" eller laddas den in direkt när du anropar den? Vet du att du kommer att använda det direkt kanske du kan ladda in allt direkt?
Det är inte så att det är Access som är seg? Testar någon annan databas?Sv: O/R-mappning och prestanda
1. Det låter ändå märkligt att det skulle ta 4 hundradelar med den lösning du har.
Men låg belastning borde inte din arkitektur vara något stort problem. När jag tipsar om skalbarhet osv så tänker jag mig flera hundra användare samtidigt. Du kanske kan posta lite kod om du vill ha hjälp med att snabba upp det (hitta felet..)
2. I större projekt (system som ska användas av många) då måste man vara noga med prestandan. Man får inte hög prestanda genom att "stirra sig blind" på O/R-mappning eller en teoretiskt optimal objektmodell. Man måste se hela kedjan från databas, affärsobjekt, till HTML-kod/UI/script.
3. Njae, är det verkligen så många kombinationer av datamängder du ska ha, vi pratar ju om fotoalbum här..? :) Det går väl knappast snabbare att skriva en massa kod i affärslagret som sätter ihop data från olika objekt, än att skriva några rader SQL och läsa från en Datareader. Du förlorar ju helt fördelen med att använda SQL JOIN, kanske en av de mest kraftfulla funktionerna i en databas.
4. Som sagt OOA/OOP handlar ju inte om att översätta tabeller till objekt. OO handlar om att göra en bra modell av verkligheten, att strukturera kod på ett sätt som känns logiskt för människor. Kunder, Produkter, Leverantörer, Projekt, XmlDocument, TextBox, osv. Man ska använda OO för att göra livet lite enklare. Det finns ingen universalmall som säger hur alla objektorienterade system skall byggas. Vid varje nytt fall ska man analysera läget för att ta ställning till vad som ska bli klasser och vad som ska bli något annat (t.ex SQL). Detta gör med med fördel på papper eller med ritprogram (OOAD - objektorienterad analys och design)Sv:O/R-mappning och prestanda
Det kommer nog inte behövas nu eftesom jag jag kör Sql Server Express (se nedan), men bra tips annars :)
"Hur fungerar din O/R mapper. Stödjer den lazy-loading så kanske det är att föredra dvs om du har klasserna "PhotoAlbum" som innehåller en Collection av "Photo", om du laddar in ett nytt album kommer den då direkt att ladda in "Photo-collection" eller laddas den in direkt när du anropar den? Vet du att du kommer att använda det direkt kanske du kan ladda in allt direkt?"
Jag har en PhotoAlbum-klass som man först t.ex anropar med PhotoAlbum.GetAlbums(). Då får man en lista av album men Album-objekten innehåller enbart Album-data och inget med foton att göra. För att hämta de associerade fotona kör man PhotoAlbum.GetPhotos(album.AlbumId) typ..
"Det är inte så att det är Access som är seg? Testar någon annan databas?"
Joo, det verkar så. Se nedan igen.Sv:O/R-mappning och prestanda
public static IList<Album> GetAllAlbums()
{
Database database = DatabaseFactory.CreateDatabase();
IList<Album> albums = new List<Album>();
string query = @"SELECT Albums.AlbumId, Albums.Name, Albums.Description, Albums.StartDateTime, Albums.StopDateTime, COUNT(Photos.PhotoId) as NumberOfImages " +
"FROM Albums LEFT JOIN Photos ON Albums.AlbumId = Photos.AlbumId " +
"GROUP BY Albums.AlbumId, Albums.Name, Albums.Description, Albums.StartDateTime, Albums.StopDateTime " +
"ORDER BY Albums.StartDateTime ASC";
IDataReader reader = database.ExecuteReader(CommandType.Text, query);
while (reader.Read())
albums.Add(CreateAlbum(reader));
reader.Close();
return albums;
}
private static Album CreateAlbum(IDataReader reader)
{
int albumId = (int)reader["AlbumId"];
string name = GetNullableString(reader, "Name");
string description = GetNullableString(reader, "Description");
DateTime? startDateTime = GetNullableDateTime(reader, "StartDateTime");
DateTime? stopDateTime = GetNullableDateTime(reader, "StopDateTime");
int numberOfImages = (int)reader["NumberOfImages"];
return new Album(albumId, name, description, startDateTime, stopDateTime, numberOfImages);
}
3. :) Sant, i det här fallet blir det inte så mycket. Jag tänkte när det blir i större skala så ökar nog variationerna ganska mycket och man kanske inte är så sugen på att lägga till en till metod/query/SP för varje scenario man kommer på. Det blir nog ganska mycket svårare att underhålla också?
Min huvudfundering är hur man löser det i de fall man arbetar med entitets-objekt som jag har. Som jag uppfattat det finns det i de allra flesta större projekt? Man kanske kan använda listor av kopplade objekt som nämndes i ett inlägg ovan med lazy loading. Vissa metoder fyller enbart data i objektet medan en annan metod även joinar andra tabeller och lägger till den här listan i objektet. Då får man ju både en bra objektmodell plus att man kan utnyttja databasens join i de krävande fallen när man vet att man behöver de kopplade objekten? Det kanske låter rimligt? :)
Tack för engegamenget och tiden :)
Sv: O/R-mappning och prestanda
Jag bytte till Sql Server Express och testade exakt samma kod på tre olika sidor. Resultatet blev detta:
Access Sql Server Express
Sida 1: 0,48s 0,02s
Sida 2: 0,27s 0,06s
Sida 3: 0,30s 0,03s
Jag trodde inte Access var så extremt trögt :) Det verkar som sagt att Access kräver en liten initiering innan varje query. Det ska ju vara connectionpooling på Access också när man använder OleDb, men den verkar inte hjälpa så mycket. Någon som vet om det är normalt med ~4 hundradelar minimum per query?
Vad är en okej laddtid för en sida på en modern CPU för att den ska kunna hantera en hög belastning på över hundra samtidiga användare? Kanske svårt att svara på.Sv:O/R-mappning och prestanda
Sv:O/R-mappning och prestanda
Access är ingen riktig databashanterare utan helt enkelt en fil som klientprocessen läser av och bearbetar (i det här fallet är klientprocessen din webb-applikation). I princip måste alltså dit .net program i det här fallet överföra flera tabeller från disk till ditt programs minne. Här kan sedan dotnet-programmet genom OleDb -> Jet4 (eventuellt COM-interop(!) bakom kulisserna) utföra de filter, grupperingar, och summor som du frågat efter.
Undantag om du t.ex. ställer frågan
SELECT Name FROM Customer WHERE id = 12345
och Id är indexerad kan Jet-motorn läsa bara den posten från disk.
Men i ditt fall läses alltså i princip hela databasen upp i minnet innan några urval/grupperingar påbörjas.
Det är antagligen detta som tar tid. Ungefär hur mycket data är det?
SQL server däremot är en aktiv databashanterare, en egen process som snurrar på och som är mycket kompetent på att filtrera, gruppera och summera. Vad som händer i SQL server är att allt jobb görs i den processen (nära datat som helt eller delvis också kan vara cachat i RAM) och du får helt enkelt ett resultatset tillbaks som dotnet-processen inte behöver bearbeta på något sätt.
Ok laddtid = alltid så kort som möjligt ;)
Överslagsräkning säger att om det tar 0,01s att få svar så kan hundra samtidiga användare få svar inom 1 sek. (Exakt 100 inom en sekund innebär många fler i praktiken). Men man måste ju ha säkerhetsmarginaler. Sällan har du en dedikerad server som kör din app. Det kanske ligger 10 andra sajter som kräver mycket på samma server. Då har du ännu mindre att spela på.
Så tumregel nr 1 blir att alltid skriva så optimal kod som möjligt.Sv: O/R-mappning och prestanda
Min access-fil var 11,7mb. Men det är för att jag fyllt och tagit bort data flera gånger. Efter compacting så blev den ~1mb, men det var dock ingen skillnad i tid när den var 11,7mb och när den var 1mb.
Tack så mycket för alla svar. Det jag fortfarande har lite funderingar kring är fortfarande, som jag skrev ovan, hur man löser det i de fall som man arbetar med entitets-objekt som jag har. Som jag uppfattat det finns det i de allra flesta större projekt? Man kanske kan använda listor av kopplade objekt som nämndes i ett inlägg ovan med lazy loading. Vissa metoder fyller enbart data i objektet medan en annan metod även joinar andra tabeller och lägger till den här listan i objektet. Då får man ju både en bra objektmodell plus att man kan utnyttja databasens join i de krävande fallen när man vet att man behöver de kopplade objekten? Det kanske låter rimligt, eller? :) Sv:O/R-mappning och prestanda
Sv:O/R-mappning och prestanda
Edit, tillägg: Det finns ingen OO-teori som säger att datamodellens tabeller måste mappa till objekt i förhållandet 1:1. (hoppas jag!)
Nej nej.. hehe du skall helst inte ens mappa från DB till Objekt utan tvärt om.
ta din Domän modell och mappa den till DB men absolut inte 1:1 för det kommer aldrig gå då OO och relationskopplingar måste ske med cross reff tabeller m.m.
Prestandan är det inget fel på i en OO modell med O/R Mapper ottroligt snabbt...
Det viktiga är inte heller prestanda för det är så mkt billigare att skala ut elle rupp idag än att spendera utv tid på prestanda. Många lägger mer än 80% krut på otimering helt i onödan det är rätt mkt pengar att spara där...
Optimering skall man lägga sist på schemat för det är då man väl kan testa sina funktioner mäta dem och se om de håller de mått man skall uppnå. Oftast gör man det och om man inte gör det så är det mkt enklare att optimera i efterhand och spar mer tid då man endast kommer optimera de stycken som tar tid på sig att utföra saker och det är långt från 80% av sin kod.
tack vare en bra O/R Mapper kan object läggas i cache i den vilket gör att man itne behöve rgå så ofta mot databasen. En bra DM modell ökar oxå återanvändandet av objekt man redan har läst in istället för en slarvig model där man hela tiden vid varje access läser om ett objekt.
Logik skall oxå undvikas i Databasen då dess huvuduppfigt egentligne är läsa och spara data. Datorn är mkt snabbare än en SQL Server på att göra beräkningar av datat i ens objekt så sådan logik skall läggas i affärslagret och inte belasta DBn med massa beräkningar, den har redan fullt upp med att läsa och skriva data. säger bara CRUD ;-)
Mvh JohanSv: O/R-mappning och prestanda
har du någon bra källa för information hur man kan lägga upp en cachning av objekt? Som jag har det nu så finns det flera metoder för att dels hämta enskilda objekt och sen flera för att hämta olika delmängder av objekt. Känns svårt att cacha på ett bra sätt så man utnyttjar samma cache till alla typer av hämt-satser? Har man en gemensam cache och man har olika funktioner beroende på vilken delmängd av objekt man vill ha måste man sortera och filtrera på objekten i cahcen. Är det inte sånt databasen är bra på?
"En bra DM modell ökar oxå återanvändandet av objekt man redan har läst in istället för en slarvig model där man hela tiden vid varje access läser om ett objekt."
Återanvändande av objekt. Är inte det cachning? ;)Sv:O/R-mappning och prestanda
<code>
using System;
using System.Collections;
using System.Threading;
using System.Collections.Specialized;
namespace ProductName.Data
{
/// <summary>
/// Cache represents a simple time-expiring data cache with optional polling data removal.
/// </summary>
public class Cache : System.IDisposable
{
private Hashtable _data;
private DateTime _expiresAt = DateTime.MaxValue;
private static Thread _pollThread;
private static ArrayList _caches;
public Cache()
{
_data = new Hashtable();
}
public Cache(bool removeExpiredItems)
{
_data = new Hashtable();
if (removeExpiredItems)
Cache.AddPolledCache(this);
}
#region Properties
/// <summary>
/// Retrieves the object with the specified key from the cache
/// </summary>
public object this [string index]
{
get
{
CacheItem item = (CacheItem) _data[index];
if (item == null)
return null;
else
{
if (item.Expires && DateTime.Now > item.ExpiresAt)
{
lock(_data.SyncRoot)
{
_data.Remove(index);
}
return null;
}
else
{
return item.Data;
}
}
}
}
/// <summary>
/// The number of objects in the cache
/// </summary>
public int Count
{
get
{
return _data.Count;
}
}
/// <summary>
/// Collection of all keys to cached objects
/// </summary>
public ICollection Keys
{
get
{
return _data.Keys;
}
}
#endregion
#region Methods
/// <summary>
/// Adds an object to the cache
/// </summary>
/// <param name="key">Identification key to the object</param>
/// <param name="obj">Object to add to the cache</param>
/// <param name="minutes">Number of minutes until the object should expire</param>
/// <returns>The object added to the cache</returns>
public object Add (string key, object obj, double minutes)
{
DateTime expiresAt = DateTime.Now.AddMinutes(minutes);
lock (_data.SyncRoot)
{
_data[key] = new CacheItem(obj, expiresAt);
if (expiresAt < _expiresAt)
_expiresAt = expiresAt;
}
return obj;
}
/// <summary>
/// Removes an object from the cache
/// </summary>
/// <param name="key">Identification key of the object to remove</param>
/// <returns>The object removed from the cache</returns>
public object Remove (string key)
{
CacheItem item = (CacheItem) _data[key];
if (item == null)
return null;
else
{
lock (_data.SyncRoot)
{
_data.Remove(key);
DateTime expiresAt = DateTime.MaxValue;
foreach (CacheItem tempItem in _data)
{
if (tempItem.ExpiresAt < expiresAt)
expiresAt = tempItem.ExpiresAt;
}
_expiresAt = expiresAt;
}
return item.Data;
}
}
/// <summary>
/// Returns the DateTime at which the object at the specified key expires
/// </summary>
/// <param name="key">Identification key of the object</param>
/// <returns>The DateTime at which the object expires or null if the object doesn't expire or doesn't exist</returns>
public object ExpiresAt (string key)
{
CacheItem item = (CacheItem) _data[key];
if (item == null)
return null;
else
{
if (item.Expires)
return item.ExpiresAt;
else
return null;
}
}
/// <summary>
/// Returns true if the object at the spcified key has expired
/// </summary>
/// <param name="key">Identification key of the object</param>
/// <returns>True if the object has expired, false otherwise</returns>
public bool IsExpired (string key)
{
CacheItem item = (CacheItem) _data[key];
if (item == null)
return false;
else
{
if (item.Expires && DateTime.Now > item.ExpiresAt)
return true;
else
return false;
}
}
/// <summary>
/// Returns true if any item has expired in this cache
/// </summary>
/// <returns>True if an item has expired, false otherwise</returns>
public bool IsExpired()
{
if (DateTime.Now > _expiresAt || _data.Count == 0)
return true;
else
return false;
}
/// <summary>
/// Clears the cache of all cached objects
/// </summary>
public void Clear ()
{
lock (_data.SyncRoot)
{
_data.Clear();
_expiresAt = DateTime.MaxValue;
}
}
/// <summary>
/// Removes all expired items
/// </summary>
public void RemoveExpiredItems()
{
lock (_data.SyncRoot)
{
CacheItem item;
DateTime expiresAt = DateTime.MaxValue;
StringCollection keys = new StringCollection();
foreach (string key in _data.Keys)
{
keys.Add(key);
}
foreach (string key in keys)
{
item = (CacheItem) _data[key];
if (item == null || (item.Expires && item.ExpiresAt < DateTime.Now))
_data.Remove(key);
else if (item.Expires && item.ExpiresAt < expiresAt)
expiresAt = item.ExpiresAt;
}
_expiresAt = expiresAt;
}
}
#endregion
#region Destructor
~Cache ()
{
Cache.RemovePolledCache(this);
}
/// <summary>
/// Disposes of cache resources
/// </summary>
public void Dispose()
{
Cache.RemovePolledCache(this);
}
#endregion
#region Static/Thread Methods
static Cache()
{
_caches = new ArrayList();
_pollThread = new Thread(new ThreadStart(Cache.PollExpiredItems));
_pollThread.Priority = ThreadPriority.BelowNormal;
_pollThread.IsBackground = true;
}
private static void AddPolledCache(Cache cache)
{
lock (_caches.SyncRoot)
{
_caches.Add(cache);
if (!_pollThread.IsAlive)
{
_pollThread = new Thread(new ThreadStart(Cache.PollExpiredItems));
_pollThread.Priority = ThreadPriority.BelowNormal;
_pollThread.IsBackground = true;
}
}
}
private static void RemovePolledCache(Cache cache)
{
lock (_caches.SyncRoot)
{
int i = _caches.IndexOf(cache);
if (i >= 0)
{
_caches.RemoveAt(i);
if (_caches.Count == 0)
_pollThread.Abort();
}
}
}
/// <summary>
/// The current number of polled caches
/// </summary>
public static int PolledCacheCount
{
get
{
return _caches.Count;
}
}
/// <summary>
/// Returns true if the Cache is polling expiring caches
/// </summary>
public static bool IsPolling
{
get
{
return _pollThread == null || _pollThread.IsAlive;
}
}
private static void PollExpiredItems()
{
try
{
int i;
Cache cache;
while (true)
{
try
{
for (i = 0; i < _caches.Count; i++)
{
cache = (Cache) _caches[i];
if (cache != null && cache.IsExpired())
cache.RemoveExpiredItems();
}
// sleep until items should next expire
Thread.Sleep(ProductName.Configuration.CachePollTime);
}
catch (Exception e)
{
if (e is ThreadAbortException)
return;
}
}
}
catch
{
}
}
#endregion
}
}
</code>Sv:O/R-mappning och prestanda
Återanvändande av objekt. Är inte det cachning? ;)
Nä det är det inte... eller det beror väl på men jag syftade på OOP sammanhang :-)
cache är bara en teknik för att slippa ladda in samma sak flera ggr om.
Identity Map är ett helt ok mönster för just hantering av cache av objekt.
Att du lägger ett objekt i cache beror på att du faktiskt vill slippa belasta DBn hela tiden.
Ex säg att du hämtar in en Article entitet den är fylld med data som du lagrat i databas.
När 30 andra kanske vill hämta samma Article så är det rätt onödigt att göra 30 nya accesser mot databasen när du faktiskt redan läst in Article dessa 30 kan istället använda samma Article och slippa gå mot databasen.
I korta drag fungerar Identity Map så att du lagrar undan ditt objekt ihop med någon slagt identitet som känner igen den, ex ID eller nått. När du skall hämta upp en Article kollar du först om denna redan finns i din Identity map om den gör det plocka ut den och använd den annars ladda in en ny.
Mvh JohanSv: O/R-mappning och prestanda
Johan; (eller annan :))
Ang. diskussion längre upp. Har du nåt tips på hur man lägger upp designen av objekten och DAL i allmänhet för att det ska gå smidigt att hämta olika kombinationer av objekt. T.ex. om man har User, Photo och Comment och man vill ha en kommentar (eller en lista) och samtidigt ha både den användaren och fotot kommentaren är kopplad till. Som jag har nu består entitetsklasserna enbart av data och ingen CRUD-funktionalitet. Detta använder man en DAL-komponent till. Man vill ju kunna utnyttja, som Ola sa, databasens JOIN-funktioner och slippa hämta Comment-objekten och sen hämta Photo och User var och en för sig (typ DALComponent.GetPhoto(comment.PhotoId) osv) Speciellt om det är en lång lista med kommentarer?
Tacksam för tips!Sv:O/R-mappning och prestanda
Joins är ju alltid lite klurigt. I mitt fall hämtar jag faktiskt objekt efter objekt. Om de har en koppling mellan sig kan det hända att jag fyller upp dem med den gång eller med Lazy Load hanterar sånt jag "kanske" vill läsa in men senare i så fall.
Man kan även göra en klass som i sin tur motsvarar en vy till sitt UI lager ex en större klass som heter Album där man har attribut som pekar på Comment, User objekten.
Album album = AlbumRepository.GetbyId("23");
labelComment.Text = album.Comment.Text;
Denna AlbumRepository kan i sin tur nyttja Comment repository, User repository för att fylla upp dessa åt en. Eller kör man O/R Mapper kan man få till det rätt smidigt lite automagiskt.
Om Comment bara är en test och inte har en massa special attribut så som tid de skrevs så kan ju Album lika väl läsa in Comments direkt från DB. CRUD innebär ju inte att joins inte får användas. CRUD innebär att man inte har någon direkt logik i sina frågor. Count, If, etc...Kan man undvika Joins så är det itne fel det heller. Och gör lite tester skall du få sen en intressant sak. Det kan ibland gå fortare att ställa två rena SELCT frågor än ställa en med Join då Join i sig är lite krävande.
När det gäller prestanda så skall man veta att det som verkar mest logiskt inte alltid är det tyvärr.
Ang sammling av data så fungerar Identity Map bra där med. Oftast kanske man läser in ett kvitto som har en samling kvittorader dessa tillhör kvittot så att ha kvittoraderna (collection) i sitt kvitto som man lagrar i en identity map fungerar fint.
Men allt är inte lämpligt att spara ner i Identity map heller ex Users.GetAll(); här hade jag nog laddat in alla hela tiden än leta i Identity mappen om de redan finns där. För det gäller ju att inte fylla sin Identity map oxå. Jag gillar idén att använda sin Identity map som en begränsad LIFO (last in first out) hanterare. Där typ de 200-500 senast använda objekt hela tiden lagras så som GC arbetar typ.
Allt beror ju på prestandakrav man har m.m. Identity map kanske inte ens behöver användas att hela tidne gå mot DB kanske håller prestanda måtten. Man skall inte snöa in sig så mycket på prestanda förrän man faktiskt vet via mätningar om man inte uppnår det mål man måste uppfylla.
Mvh JohanSv: O/R-mappning och prestanda
"Joins är ju alltid lite klurigt."
Njaeeee..
Allt är ju lätt när man kan det. :)
Jag föredrar att lösa så mycket jag kan i databasen. Jag gillar databaser, jag kan SQL rätt bra, och det blir grym prestanda på allt.Sv: O/R-mappning och prestanda
"Denna AlbumRepository kan i sin tur nyttja Comment repository, User repository för att fylla upp dessa åt en."
Alternativet är väl annars att propertyn "Comment" i det här fallet fyller på sig själv vid behov genom att använda Comment-repository?
"Om Comment bara är en test och inte har en massa special attribut så som tid de skrevs så kan ju Album lika väl läsa in Comments direkt från DB."
Vad spelar det för roll om det är tid osv med också? Menar du att man här gör en Join till alla tabeller och sedan skapar de objekt som behövs kopplas till Album, dvs User och Comment?
"CRUD innebär ju inte att joins inte får användas. CRUD innebär att man inte har någon direkt logik i sina frågor."
CRUD betyder väl bara att man har update-, insert-, select- och delete-metoder? Och alternativet är väl att ha det på antingen en repository som du kallar det eller direkt på entitetsobjekten som sedan anropar vidare till repositoryt..?
Ola;
Vad är ditt förslag på upplägg i det här scenariot som jag har med dessa objekt? Att CRUD-metoder i objekten eller i en DAL-komponent laddar de tillhörande objekten själv genom en Join, istället för att t.ex. properties i entitetsklasserna själva laddar upp enskilda objekt vid behov? Man kan iof kanske ha en kombination där man använder det senare, men när man vet att man kommer behöva tillhörande data har en speciell metod som fyller på de i förväg med joins?Sv:O/R-mappning och prestanda
Varför ha Business logik i en SP som ligger i skitket för data? och sedan ovan det ha ett annat skikt man kallar business skikt? Den spagettin har jag alrid förstått mig på mer än det svaga argumentet man kan ju skriva business logik i SP då skall man ju använda det...
PS låt de olika alger göra det de är bäst på och strukturera modellen på ett konsist sätt så lovar jag att du kommer bli av med 80% av de problem som många har idag för att de kör en massa kod i SPs m.m. Till slut vet ingen vart logiken buggar om det är i ens SP eller i en klasser och debuggandet kan vara för evigt i ett sådant projekt samma kan det klassiska företagsspöket dyka upp, 3 business objekt delar på en SP som någon hackar om och 10 olika ställen i systemet pajar men ingen vet vad som orsakar felet så vida man inte letar i flera timmar kanske dagar :-)
Tänk nu på att jag säger KAN och i många projekt jag arbetat i där man byggt på detta sätt så har projekten dragit över tiden, de har vart svåra att underhålla till slut sitter alla och skyller på varandra och det hela är en enda röra.
UI skall visa ett gränsnitt ha kod för gränsnittshantering här... kod som sedan pratar är affärskoden där alla algroritmen och rutiner ligger för affärslogiken. som i sin tur Hämtar och sparar data mot en datakälla... Vad händer om du har massa kod i en SP och sedan byter till en Oracle DB eller XML-filer istället? Det gör man lätt i en modell där man faktiskt ser till så alla lager gör det de är till för att göra...
Mvh JohanSv: O/R-mappning och prestanda
Sv:O/R-mappning och prestanda
<code>
objAlbum.GetPhotos() As PhotoCollection
' i ditt UI binder du till denna Collection.
' varje photo är ett objekt som tar upp plats i minnet
' objekten har i sin tur andra objekt eller collections i sig
' t.ex. CommentsCollection
Eller
objAlbum.GetPhotos() As SqlDatareader
'eller IDatareader om du vill skriva databasoberoende kod...
(vilket jag inte tycker att man behöver göra speciellt ofta..)
' metoden anropar SQL som returnerar en lista (inkl eventuella joins som datamodellen kräver).
</code>
Fördelen med det senare alternativet är:
1. Det är mindre kod att skriva.
2. Det är enklare att begripa.
3. Det ger mycket bättre prestanda.
Sv: O/R-mappning och prestanda
Exempel.
Hur tänker du lösa följande problem med din modell?
tbl_Anvandare (AnvId, AnvNamn)
tbl_AnvProj (AnvId, ProjId)
tbl_Projekt (ProjId, ProjNamn, Aktivt)
Du har 10.000 användare och 3000 projekt (1200 är aktiva).
Systemet kräver att du ska lista alla Aktiva projekt
som en användare är behörig till just nu.
Hämtar du (från DB el cache) alla användare och alla projekt till primärminnet,
för att sedan loopa igenom och kontrollera där? För varje request?
Det måste du väl, om din filosofi säger dig att all affärslogik
måste ligga i BL-kod, och aldrig i någon SQL-sats...?
Användarens behörighet till projekt är ju affärslogik.
Likaså om aktiva projekt ska synas eller inte.
För mig är det alldeles självklart att relationsmodellen är den bästa lösningen här.
Det räcker ju med följande:
SELECT ProjektNamn
FROM tbl_Projekt P
INNER JOIN rel_AnvProj AP ON P.ProjId=AP.ProjId
WHERE AP.AnvID = @anvID
Hur många kodrader blir det med din modell?
Och inte minst... hur många felaktiga kodrader..? :-O
Samt hur bra presterar din kod jämfört med SQL-frågan här?
(Jag säger tvärtom mot vad du sa förut. Tänk DATABAS! :-))Sv:O/R-mappning och prestanda
Ang din fråga så beror det lite på. Mest kraven som finns, prestanda, minne etc just 1200 objekt i minnet tar inte så mkt och ett DataSet skulle ta mkt mer minne än 12000 Projekt entiteter.
SÅ om jag skall lista alla projekt en användare är aktiv till skulle jag nog på rak arm med YAGNI i sikte anropa min ProectRespository.GetAllActiveProjects() där efter loopa genom denna samling och komma om den användare jag är är med i något av dessa projekt. Jag brukar to m lägga till Visitor pattern för mina collections så jag enklet kan plugga på speciel frågor mot min samlng av objekt eller vad jag nu vill kunna göra med min collection.
koden i BOL kan blir:
ProjectCollection projectCollection = PrijectRepositories.GetActiveProjects();
AllowedPriojectVisitor allowedPrijectVistor = new AllowedPrjectVisitor(user);
projectCollection.Visit(allowedProjectVisitor);
ProjectCollcetion allowedPriojects = allowedProjectVisitors.Projects;
Eller kanske.
ProjectCollection projectCollection = PrijectRepositories.GetActiveProjects();
foreach(Project project in projectCollection)
{
.....
if( project.User == User )
... ok ...
}
Finns andra sätt att göra det på med det handlar lite om hur man bygger upp sin modell och vilka mönster man använder sig av så det blir en konsist kod. Lite svårt att komma med bästa svaret på din fråga när jag inte vet hur resterande saker för modellen ser ut.
Det kan to m vara så att min Respository faktiskt direkt har en metod som hämtar alla project som en användare är aktiv till via JOIN. OBS! jag sa inte att JOINs inte fick användas jag sa att man skall undvika affärslogik i SP sånt som hör hemma i annat lager dvs BUS.
Det är även sant att man kanske inte byter datakälla så ofta men även här är det beroende av projekt och krav. I det projekt jag arbetar nu har vi olika datakällor men samma grund modell, för oss är det väldigt enkelt att koppla oss mot annan datakälla vi behöver inte tänka på affärsregler eller nått då detta sker i en högre nivå i vår arkitektur vi behöver endast justera de klasser som kör sin CRUD och då vi oftast även använder interface orienterad programmering behöver interfacen aldrig brytas m.m.
Vi har våra pluggbara Repositories som vi byter ut.
Är det ett mindre projekt som man vet inte skall vara färändringsbart så kan jag to m bara använda två skikt och binda mot datasets hela vägen. Men inte i större enterprise peojekt.
Och du har rätt det kan bli lite mer kod i själva BOL lagret men mkt enklare att lokalisera felet och fixa till det än om en massa logik skulle ligga spritt bland lagren. dvs i SPs m.m.
Men det kan även bli mycket mindre kod då ens modell blir mer ren och förändrings och återanvändbar.
Jag kräver inte att du skall förstå hur koden kan se ut under en DDD modell genom att försöka säga i text hur bra det kan bli eller hur kass det kan bli med om man gör fel. Det är sånt man märker då man bygger efter sådan modell. Jag vill heller inte på stå att DDD är bäst. Det viktiga är att kunna identifiera vilken modell som passar just sitt projekt bäst. Samt vilka desingmönster man kan använda ex, observer, factory, singleton, visitor, eller större Enterprise patterns eller inte skall använda, vilka hjälpramverk man vill ha eller inte ha. Ex IoC Hanterare, O/R Mapper, AOP etc...
Mvh JohanSv: O/R-mappning och prestanda
Sv:O/R-mappning och prestanda
Sant men det beror lite på hur ofta metoden används, prestanda, etc... Men i första hand hade jag gjort som du sa hämta alla och kolla vilka som är aktiva. Vet jag dock att det kan bli 30 000 projekt kanske jag gör en special metod om jag har krav som inte kan uppfyllas.
Mvh JohanSv: O/R-mappning och prestanda
Sv:O/R-mappning och prestanda
Sv:O/R-mappning och prestanda
Patrik svarade på din fråga.
Men utan O/R mapper så vist hade jag i ett sådant fall gjort en JOIN (om en join måste användas) beror ju på hur ens tabeller ser ut. Och ha en Where sats typ.
Invoice.GetByUser(user);
Som sagt så byggs modellen upp efter de specar (use case, User stories) man har och metoder m.m. speglar dessa rätt bra. (med undantag som alltid).
ex, så kan det vara så att Invoice alltid plockas baserat på dess id och användarens id då skulle jag
valt att inte hämta all invoice o fråga om jag får ha dem utan då fråga efter alla invoice baserat på en användare. Detta pga prestanda som du säger. Men en where sats och en join är för mig inte affärslogik på det sättet, joins är ibland nödvändigt för att kunna översätta sin modell rätt. En OO model är svå att spergla i en relations db där av måste join med.
Man skulle även kunna hämta 500 000 fakturor om man vill fast bara en ID struktur ex bara få ut ID och UserID och där efter plocka fram alla IDs och hämta fulla objekten. Det blir typ ett table pattern för att öka prestanda men fortfarande orientera sig med objekt för att slippa metoder som GetByUser i en Invoice klass.
Sunt förnuft och konsistens är viktiga faktorer.
Mvh JohanSv:O/R-mappning och prestanda
Det innebär tex att om jag skulle skriva typ så här med ett O/R frågespråk:
IList<Product> products = context.Query("Select * From Product Where Product.Category.Name == 'Misc'");
Så kommer O/R mapparen att göra de joins som behövs för att where satsen skall kunna översättas från O/R frågespråket till T-SQL.Sv: O/R-mappning och prestanda