Будь осторожен здесь. Вы должны всегда иметь оператор using для любого локального объекта, который реализует IDisposable. Это включает не только связи и читателей, но и команду. Но иногда это может быть хитро точно , где , что идет с использованием оператора. Если вы не будете осторожны, это может вызвать проблемы. Например, в коде, который следует за оператором using, ваш читатель закроется, прежде чем вы когда-либо сможете его использовать:
DataReader MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
return rdr;
}
}
}
Вместо этого у вас есть четыре варианта. Один из них - дождаться создания блока using, пока вы не вызовете функцию:
DataReader MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
return cmd.ExecuteReader();
}
}
using (var rdr = MyQuery())
{
while (rdr.Read())
{
//...
}
}
Конечно, вы все равно должны быть осторожны со своим подключением, и это означает, что не забывайте писать блок using везде, где вы используете функцию.
Второй вариант - просто обработать результаты запроса в самом методе, но это нарушает отделение вашего уровня данных от остальной части программы. Третий вариант - чтобы ваша функция MyQuery () принимала аргумент типа Action, который вы можете вызвать внутри цикла while (rdr.Read ()), но это просто неудобно.
Я обычно предпочитаю четвертый вариант: превратить читатель данных в IEnumerable, например так:
IEnumerable<IDataRecord> MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
}
}
}
Теперь все будет закрыто правильно, и код, который обрабатывает все это, находится в одном месте. Вы также получите приятный бонус: результаты вашего запроса будут хорошо работать с любым из операторов linq.
Наконец, что-то новое, с чем я играю в следующий раз, когда мне удается создать совершенно новый проект, который сочетает в себе IEnumerable с передачей аргумента делегата:
//part of the data layer
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
//DL.ConnectionString is a private static property in the data layer
// depending on the project needs, it can be implementing to read from a config file or elsewhere
using (var cn = new SqlConnection(DL.ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
}
}
}
А потом я буду использовать его в слое данных следующим образом:
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead, and provide overloads for commandtypes.
return Retrieve(
"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", p =>
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}