Det här svaret fokuserar huvudsakligen på "välj" kontra uppdatering/skapa/ta bort operationer. Jag tror att det är sällsyntare att uppdatera mer än en eller några poster åt gången, och därför tror jag också att "select" är där flaskhalsarna tenderar att uppstå. Som sagt, du behöver känna till din ansökan (profil). Det bästa stället att fokusera din optimeringstid på är nästan alltid på databasnivån i själva frågorna, snarare än klientkoden. Klientkoden är bara VVS:det är inte huvudkraften i din app. Men eftersom VVS tenderar att återanvändas i många olika appar, sympatiserar jag med önskan att få det så nära optimalt som möjligt, och därför har jag mycket att säga om hur man bygger den koden.
Jag har en generisk metod för att välja frågor/procedurer i mitt datalager som ser ut ungefär så här:
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();
}
}
}
Och det låter mig skriva offentliga datalagermetoder som använder anonyma metoder för att lägga till parametrarna. Koden som visas fungerar med .Net 2.0+, men kan skrivas ännu kortare 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;
}
);
}
Jag ska sluta här så att jag kan peka dig igen till koden precis ovan som använder den anonyma metoden för att skapa parameter.
Detta är mycket ren kod, eftersom den placerar frågedefinitionen och parameterskapandet på samma ställe samtidigt som det låter dig abstrahera databasanslutningen/anropskoden till någonstans som är mer återanvändbar. Jag tror inte att den här tekniken täcks av någon av punkterna i din fråga, och den råkar vara ganska snabb också. Jag tror att det här täcker kärnan i din fråga.
Jag vill dock fortsätta att förklara hur allt hänger ihop. Resten är ganska okomplicerat, men det är också lätt att kasta detta till en lista eller liknande och få saker fel, vilket i slutändan skadar prestandan. Så när vi går vidare använder affärslagret sedan en fabrik för att översätta frågeresultat till objekt (c# 3.0 eller senare):
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]
//...
};
}
}
Istället för att ha dessa levande i sin klass, kan du också gruppera dem alla till en statisk klass speciellt avsedd att hålla fabriksmetoderna.
Jag måste göra en ändring av den ursprungliga återställningsmetoden. Den metoden "ger" samma objekt om och om igen, och det här fungerar inte alltid så bra. Vad vi vill göra annorlunda för att få det att fungera är att tvinga fram en kopia av objektet som representeras av den aktuella posten, så att när läsaren muterar för nästa post arbetar vi med ren data. Jag väntade tills efter att ha visat fabriksmetoden så att vi kan använda den i den slutliga koden. Den nya Retrieve-metoden ser ut så här:
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();
}
}
}
Och nu skulle vi kalla den nya Retrieve()-metoden så här:
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;
}
);
}
Uppenbarligen kan denna sista metod utökas till att inkludera eventuell ytterligare affärslogik som behövs. Det visar sig också att den här koden är exceptionellt snabb, eftersom den drar fördel av de lata utvärderingsfunktionerna i IEnumerable. Nackdelen är att det tenderar att skapa många kortlivade objekt, och det kan skada transaktionsprestanda du frågade om. För att komma runt detta bryter jag ibland bra n-tier och skickar IDataRecord-objekten direkt till presentationsnivån och undviker onödigt objektskapande för poster som helt enkelt är bundna till en rutnätskontroll direkt.
Uppdatera/Skapa kod är liknande, med skillnaden att du vanligtvis bara ändrar en post åt gången istället för många.
Eller så kan jag rädda dig från att läsa det här långa inlägget och bara säga åt dig att använda Entity Framework;)