Dette svar fokuserer hovedsageligt på 'vælg' versus opdatering/opret/slet operationer. Jeg tror, det er sjældnere at opdatere mere end én eller få poster ad gangen, og derfor tror jeg også, at 'select' er der, hvor flaskehalsene plejer at opstå. Når det er sagt, skal du kende din ansøgning (profil). Det bedste sted at fokusere din optimeringstid er næsten altid på databaseniveauet i selve forespørgslerne i stedet for klientkoden. Klientkoden er alt sammen bare VVS:det er ikke hovedkraften i din app. Men da VVS har en tendens til at blive genbrugt i mange forskellige apps, sympatiserer jeg med ønsket om at få det så tæt på optimalt som muligt, og derfor har jeg meget at sige om, hvordan man bygger den kode.
Jeg har en generisk metode til at vælge forespørgsler/procedurer i mit datalag, der ser sådan ud:
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
//ConnectionString is a private static property in the data layer
// You can implement it to read from a config file or elsewhere
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
rdr.Close();
}
}
}
Og det lader mig skrive offentlige datalagsmetoder, der bruger anonyme metoder til at tilføje parametrene. Den viste kode fungerer med .Net 2.0+, men kan skrives endnu kortere med .Net 3.5:
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead of a full sql query
return Retrieve(
@"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
Jeg stopper lige her, så jeg igen kan henvise dig til koden lige ovenfor, der bruger den anonyme metode til parameteroprettelse.
Dette er en meget ren kode, idet den sætter forespørgselsdefinitionen og parameteroprettelse på samme sted, mens den stadig giver dig mulighed for at abstrahere kedeldatabaseforbindelsen/kaldekoden til et sted, der er mere genanvendeligt. Jeg tror ikke, at denne teknik er dækket af nogen af punkterne i dit spørgsmål, og den er tilfældigvis også ret hurtig. Jeg tror, at dette dækker essensen af dit spørgsmål.
Jeg vil dog fortsætte med at forklare, hvordan det hele hænger sammen. Resten er ret ligetil, men det er også nemt at smide dette til en liste eller lignende og få tingene galt, hvilket i sidste ende skader præstationen. Så fortsæt, så bruger forretningslaget en fabrik til at oversætte forespørgselsresultater til objekter (c# 3.0 eller nyere):
public class Foo
{
//various normal properties and methods go here
public static Foo FooFactory(IDataRecord record)
{
return new Foo
{
Property1 = record[0],
Property2 = record[1]
//...
};
}
}
I stedet for at have disse i deres klasse, kan du også gruppere dem alle sammen i en statisk klasse, der specifikt er beregnet til at holde fabriksmetoderne.
Jeg er nødt til at foretage en ændring af den oprindelige genfindingsmetode. Den metode "giver" det samme objekt igen og igen, og det fungerer ikke altid så godt. Det, vi ønsker at gøre anderledes for at få det til at fungere, er at tvinge en kopi af objektet repræsenteret af den aktuelle post, så når læseren muterer til den næste post, arbejder vi med rene data. Jeg ventede til efter at have vist fabriksmetoden, så vi kan bruge den i den endelige kode. Den nye Retrieve-metode ser sådan ud:
private static IEnumerable<T> Retrieve(Func<IDataRecord, T> factory,
string sql, Action<SqlParameterCollection> addParameters)
{
//ConnectionString is a private static property in the data layer
// You can implement it to read from a config file or elsewhere
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return factory(rdr);
rdr.Close();
}
}
}
Og nu ville vi kalde den nye Retrieve()-metode sådan her:
public IEnumerable<Foo> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead of a full sql query
return Retrieve(Foo.FooFactory,
@"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
Denne sidste metode kan naturligvis udvides til at omfatte enhver yderligere forretningslogik, der er nødvendig. Det viser sig også, at denne kode er usædvanlig hurtig, fordi den udnytter de dovne evalueringsfunktioner i IEnumerable. Ulempen er, at det har en tendens til at skabe en masse kortlivede objekter, og det kan skade den transaktionelle ydeevne, du spurgte om. For at komme uden om dette bryder jeg nogle gange et godt n-tier og sender IDataRecord-objekterne direkte til præsentationsniveauet og undgår unødvendig objektoprettelse for poster, der simpelthen er bundet til en gitterkontrol med det samme.
Opdater/opret kode er ens, med den forskel, at du normalt kun ændrer én post ad gangen i stedet for mange.
Eller jeg kunne spare dig for at læse dette lange indlæg og bare fortælle dig at bruge Entity Framework;)