Возrotation DataReader из DataLayer в инструкции Using
У нас есть много кода слоя данных, который следует за этой общей схемой:
public DataTable GetSomeData(string filter) { string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter"; DataTable result = new DataTable(); using (SqlConnection cn = new SqlConnection(GetConnectionString())) using (SqlCommand cmd = new SqlCommand(sql, cn)) { cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter; result.Load(cmd.ExecuteReader()); } return result; }
Я думаю, мы можем сделать немного лучше. Моя главная жалоба прямо сейчас заключается в том, что она заставляет все записи загружаться в память даже для больших наборов. Я хотел бы иметь возможность использовать способность DataReader поддерживать только одну запись в ram одновременно, но если я верну DataReader напрямую, соединение будет отключено при выходе из блока использования.
Как я могу улучшить это, чтобы позволить возвращать одну строку за раз?
- Является ли DbContext таким же, как DataContext?
- Entity Framework .Remove () vs. .DeleteObject ()
- Спящий режим и без ПК
- Заполнение TreeView из базы данных
- c #, работающий с Entity Framework на многопоточном сервере
- Как сравнивать только даты без времени в типах DateTime в Linq to SQL с Entity Framework?
- Можно ли использовать дерби из apache в Eclipse теперь, когда они перестали разрабатывать плагин дерби для Eclipse?
- Лучшая практика для поддержания сеанса mgo
- EF Code-First Индивидуальное отношение: множественность недействительна в Role * in relationship
- Измельчение базы данных в node.js?
- Каков наилучший способ переноса Django DB из SQLite в MySQL?
- JPA - самый быстрый способ игнорировать поле во время сохранения?
- Массовое удаление в LINQ для объектов
Еще раз, акт составления моих мыслей по этому вопросу раскрывает ответ. В частности, последнее предложение, в котором я написал «по одной строке за раз». Я понял, что мне все равно, что это datareader, если я могу перечислить его подряд за строкой. Это привело меня к следующему:
public IEnumerable GetSomeData(string filter) { string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter"; using (SqlConnection cn = new SqlConnection(GetConnectionString())) using (SqlCommand cmd = new SqlCommand(sql, cn)) { cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter; cn.Open(); using (IDataReader rdr = cmd.ExecuteReader()) { while (rdr.Read()) { yield return (IDataRecord)rdr; } } } }
Это будет работать еще лучше, как только мы перейдем к 3.5 и можем начать использовать другие операторы linq для результатов, и мне это нравится, потому что он заставляет нас задуматься о «конвейере» между каждым уровнем для запросов, которые возвращают много Результаты.
Нижняя сторона заключается в том, что для читателей, имеющих более одного набора результатов, будет неудобно, но это чрезвычайно редко.
Обновить
Поскольку я впервые начал играть с этим шаблоном в 2009 году, я узнал, что лучше всего, если я также сделаю его Func
возвращаемым типом IEnumerable
и добавлю параметр Func
чтобы преобразовать состояние DataReader в бизнес-объекты в петля. В противном случае могут возникать проблемы с ленивой итерацией, так что каждый раз вы видите последний объект в запросе.
То, что вы хотите, – это поддерживаемый шаблон, вам придется использовать
cmd.ExecuteReader(CommandBehavior.CloseConnection);
и удалите оба using()
using () из метода GetSomeData (). Обеспечение безопасности исключений должно предоставляться вызывающим абонентом, гарантируя закрытие устройства.
В такие моменты я нахожу, что лямбды могут быть полезны. Подумайте об этом, вместо слоя данных, дающего нам данные, дадим слой данных наш метод обработки данных:
public void GetSomeData(string filter, Action processor) { ... using (IDataReader reader = cmd.ExecuteReader()) { processor(reader); } }
Тогда бизнес-уровень будет называть его:
GetSomeData("my filter", (IDataReader reader) => { while (reader.Read()) { ... } });
Ключевым является ключевое слово yield
.
Как и в оригинальном ответе Джоэля, немного больше конкретизировалось:
public IEnumerable Get(string query, Action parameterizer, Func selector) { using (var conn = new T()) //your connection object { using (var cmd = conn.CreateCommand()) { if (parameterizer != null) parameterizer(cmd); cmd.CommandText = query; cmd.Connection.ConnectionString = _connectionString; cmd.Connection.Open(); using (var r = cmd.ExecuteReader()) while (r.Read()) yield return selector(r); } } }
И у меня есть этот метод расширения:
public static void Parameterize(this IDbCommand command, string name, object value) { var parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = value; command.Parameters.Add(parameter); }
Поэтому я звоню:
foreach(var user in Get(query, cmd => cmd.Parameterize("saved", 1), userSelector)) { }
Это полностью общее, подходит для любой модели, соответствующей интерфейсам ado.net. Объекты соединения и считывания расположены после enums коллекции. Во всяком случае, заполнение DataTable
с использованием IDataAdapter
Fill
IDataAdapter
может быть быстрее, чем DataTable.Load
Я никогда не был большим поклонником того, чтобы слой данных возвращал общий объект данных, поскольку это в значительной степени растворяет всю суть того, что код разделен на свой собственный уровень (как вы можете отключить слои данных, если интерфейс не определен? ).
Я считаю, что лучше всего для всех таких функций вернуть список пользовательских объектов, которые вы создаете сами, а в ваших данных позже вы вызываете свою процедуру / запрос в datareader и повторяете это, создавая список.
Это упростит работу в целом (несмотря на начальное время создания пользовательских classов), упрощает обработку вашего соединения (так как вы не будете возвращать какие-либо связанные с ним объекты) и должны быть быстрее. Единственный недостаток – все будет загружено в память, как вы упомянули, но я бы не подумал, что это будет причиной беспокойства (если бы это было так, я бы подумал, что запрос нужно будет скорректировать).