Ответ 1
В этом ответе основное внимание уделяется операциям "select" vs update/create/delete. Я думаю, что реже обновлять более чем одну или несколько записей за раз, и поэтому я также думаю, что "выбор" - это то место, где узкие места имеют тенденцию возникать. Тем не менее, вам нужно знать ваше приложение (профиль). Лучшее место для фокусировки вашего времени оптимизации почти всегда находится на уровне базы данных в самих запросах, а не на клиентском коде. Клиентский код - это всего лишь сантехника: это не основная сила вашего приложения. Однако, поскольку plumbing, как правило, повторно используется во многих различных приложениях, я сочувствую желанию максимально приблизить его к оптимальному, и поэтому у меня есть много возможностей сказать, как построить этот код.
У меня есть общий метод для выбора запросов/процедур в моем слое данных, который выглядит примерно так:
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;
}
);
}
Я остановлюсь прямо здесь, чтобы снова указать на код, который находится выше, который использует анонимный метод для создания параметров.
Это очень чистый код, поскольку он ставит определение запроса и создание параметров в одном и том же месте, все еще позволяя абстрагироваться от кода подключения/вызова кода шаблона к чему-то более пригодному для повторного использования. Я не думаю, что эта техника покрыта какой-либо из пулевых точек в вашем вопросе, и это тоже довольно быстро. Я думаю, что это касается вопроса вашего вопроса.
Я хочу продолжить, однако, объяснить, как все это сочетается. Остальное довольно прямолинейно, но также легко бросить это в список или схожий и получить что-то не так, что в конечном итоге усугубляет производительность. Поэтому, продвигаясь дальше, бизнес-уровень использует factory для преобразования результатов запроса в объекты (С# 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]
//...
};
}
}
Вместо того, чтобы жить в своем классе, вы также можете объединить их все вместе в статический класс, специально предназначенный для хранения методов factory.
Мне нужно внести одно изменение в исходный метод извлечения. Этот метод "возвращает" один и тот же объект снова и снова, и это не всегда так хорошо работает. То, что мы хотим сделать по-другому, чтобы заставить его работать, - заставить копию объекта, представленного текущей записью, так что, когда читатель мутирует для следующей записи, мы работаем с чистыми данными. Я ждал, пока не покажу метод factory, поэтому мы можем использовать его в конечном коде. Новый метод 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;)