Этот ответ сфокусирован в основном на операциях «выбор» и «обновление / создание / удаление». Я думаю, что реже обновлять более одной или нескольких записей одновременно, и поэтому я также думаю, что «выбор» - это то место, где обычно возникают узкие места. Тем не менее, вам нужно знать ваше приложение (профиль). Лучшее место, чтобы сконцентрировать ваше время оптимизации - почти всегда на уровне базы данных в самих запросах, а не в коде клиента. Код клиента - это всего лишь сантехника: это не основная сила вашего приложения. Однако, поскольку сантехника имеет тенденцию повторно использоваться во многих различных приложениях, я сочувствую желанию приблизить ее к оптимальной, насколько это возможно, и поэтому у меня есть, что сказать по поводу того, как построить этот код.
У меня есть общий метод для выбора запросов / процедур в моем слое данных, который выглядит примерно так:
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();
}
}
}
И это позволяет мне писать общедоступные методы слоя данных, которые используют анонимные методы для добавления параметров. Показанный код работает с .Net 2.0+, но может быть написан еще короче, используя .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;
}
);
}
Я остановлюсь прямо здесь, чтобы я мог снова указать вам на код выше, который использует анонимный метод для создания параметров.
Это очень чистый код, поскольку он помещает определение запроса и создание параметра в одно и то же место, позволяя при этом абстрагировать исходный код подключения / вызова базы данных в более подходящее для повторного использования место. Я не думаю, что эта техника покрыта какой-либо из пулевых точек в вашем вопросе, и она оказывается слишком быстрой. Я думаю, что это охватывает суть вашего вопроса.
Однако я хочу продолжить, чтобы объяснить, как все это сочетается. Все остальное довольно просто, но также легко бросить это в список или тому подобное и получить что-то не так, что в конечном итоге снижает производительность. Итак, двигаясь дальше, бизнес-уровень затем использует фабрику для преобразования результатов запроса в объекты (c # 3.0 или новее):
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]
//...
};
}
}
Вместо того, чтобы использовать их в своем классе, вы также можете сгруппировать их все вместе в статический класс, специально предназначенный для хранения фабричных методов.
Мне нужно сделать одно изменение в исходном методе получения. Этот метод «возвращает» один и тот же объект снова и снова, и это не всегда работает так хорошо. Чтобы заставить его работать по-другому, нам нужно заставить копию объекта, представленного текущей записью, сделать так, чтобы при чтении следующей записи читатель работал с чистыми данными. Я подождал, пока не покажу фабричный метод, чтобы мы могли использовать его в конечном коде. Новый метод Retrieve выглядит следующим образом:
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();
}
}
}
А теперь мы бы назвали этот новый метод Retrieve () следующим образом:
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;
}
);
}
Очевидно, что этот последний метод может быть расширен для включения любой необходимой дополнительной бизнес-логики. Также оказывается, что этот код исключительно быстрый, потому что он использует преимущества ленивых функций оценки IEnumerable. Недостатком является то, что он имеет тенденцию создавать много недолговечных объектов, и это может снизить производительность транзакций, о которой вы спрашивали. Чтобы обойти это, я иногда нарушаю хороший n-уровень и передаю объекты IDataRecord непосредственно на уровень представления и избегаю ненужного создания объектов для записей, которые просто связаны с элементом управления сеткой сразу.
Обновление / создание кода аналогично, с той разницей, что вы обычно меняете только одну запись за раз, а не многие.
Или я мог бы спасти вас, читая этот длинный пост, и просто сказать вам использовать Entity Framework;)