Tato odpověď se zaměřuje hlavně na operace „vybrat“ vs. aktualizace/vytvoření/smazání. Myslím, že je vzácnější aktualizovat více než jeden nebo několik záznamů najednou, a tak si také myslím, že 'výběr' je místo, kde se úzká místa obvykle vyskytují. To znamená, že musíte znát svou aplikaci (profil). Nejlepší místo, kde se můžete zaměřit na čas optimalizace, je téměř vždy na úrovni databáze v samotných dotazech, spíše než na klientský kód. Klientský kód je pouze instalatérství:není to hlavní síla vaší aplikace. Nicméně, protože instalatérství má tendenci být znovu používáno v mnoha různých aplikacích, sympatizuji s touhou dostat se co nejblíže k optimálnímu stavu, a proto mám hodně co říct k tomu, jak tento kód vytvořit.
Mám obecnou metodu pro výběr dotazů/postupů v mé datové vrstvě, která vypadá asi takto:
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();
}
}
}
A to mi umožňuje napsat metody veřejné datové vrstvy, které k přidání parametrů používají anonymní metody. Zobrazený kód funguje s .Net 2.0+, ale může být napsán ještě kratší pomocí .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;
}
);
}
Právě tady se zastavím, abych vás mohl znovu odkázat na výše uvedený kód, který používá anonymní metodu pro vytváření parametrů.
Toto je velmi čistý kód v tom, že umisťuje definici dotazu a vytváření parametrů na stejné místo, přičemž vám stále umožňuje abstrahovat standardní databázové připojení/volací kód někam, kde je více znovu použitelné. Nemyslím si, že tato technika je pokryta žádnou z odrážek ve vaší otázce a je také zatraceně rychlá. Myslím, že toto pokrývá podstatu vaší otázky.
Chci však pokračovat ve vysvětlování toho, jak to všechno do sebe zapadá. Zbytek je poměrně jednoduchý, ale je také snadné to hodit na seznam nebo podobně a udělat něco špatně, což nakonec poškodí výkon. Obchodní vrstva pak používá továrnu k překladu výsledků dotazů na objekty (c# 3.0 nebo novější):
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]
//...
};
}
}
Namísto toho, abyste je nechali žít ve své třídě, můžete je také všechny seskupit do statické třídy speciálně určené k tomu, aby obsahovala tovární metody.
Potřebuji provést jednu změnu původní metody načítání. Tato metoda „vydává“ stále stejný objekt a ne vždy to tak dobře funguje. Co chceme udělat jinak, aby to fungovalo, je vynutit kopii objektu reprezentovaného aktuálním záznamem, takže když čtečka zmutuje pro další záznam, pracujeme s čistými daty. Čekal jsem, dokud jsem neukázal tovární metodu, abychom ji mohli použít ve finálním kódu. Nová metoda Retrieve vypadá takto:
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();
}
}
}
A nyní bychom tuto novou metodu Retrieve() nazvali takto:
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;
}
);
}
Tuto poslední metodu lze samozřejmě rozšířit tak, aby zahrnovala jakoukoli další potřebnou obchodní logiku. Ukázalo se také, že tento kód je výjimečně rychlý, protože využívá funkce líného hodnocení IEnumerable. Nevýhodou je, že má tendenci vytvářet mnoho objektů s krátkou životností, což může poškodit transakční výkon, na který jste se ptali. Abych to obešel, někdy porušuji dobrou n-vrstvu a předávám objekty IDataRecord přímo do prezentační vrstvy a vyhýbám se zbytečnému vytváření objektů pro záznamy, které jsou jednoduše okamžitě svázány s ovládacím prvkem mřížky.
Aktualizace/Vytvoření kódu je podobná, s tím rozdílem, že obvykle měníte pouze jeden záznam najednou, nikoli mnoho.
Nebo bych vás mohl ušetřit čtením tohoto dlouhého příspěvku a říct vám, abyste používali Entity Framework;)