Поэтому я много читал о том, что SqlDataReaders не утилизируются должным образом в.Net - и я сражался с "Истекло время ожидания". Период ожидания истекал до получения соединения из пула. Возможно, это произошло из-за того, что все объединенные соединения были использованы и максимальный размер пула был достигнут "ошибка на пару дней. Очевидно, я мог бы увеличить максимальный размер пула до 30 000, но это не касается реальной проблемы.
Когда я просматриваю код, я выполняю следующий SQL-запрос:
select * from sys.dm_os_performance_counters
where counter_name ='User Connections'
После
cmd.Connection.Open();
line, пользовательские подключения увеличиваются на 1. Однако он НИКОГДА не возвращается, если я не переработаю пул приложений на веб-сервере (после чего все активные подключения к базе данных с сайта будут убиты).
Вот мой код:
public static DataTable SPExecuteDataTable(string[] ConnectionData, params object[] args)
{
SqlConnection conn = null;
SqlCommand cmd = null;
SqlDataReader dr = null;
try
{
conn = new SqlConnection(ConnectionData[1]);
cmd = new SqlCommand(ConnectionData[0], new SqlConnection(ConnectionData[1]));
cmd.CommandType = CommandType.StoredProcedure;
for (int i = 0; i < args.Length; i++)
{
SqlParameter Param = new SqlParameter(ConnectionData[i + 2], DBNullIfNull(args[i]));
cmd.Parameters.Add(Param);
}
cmd.Connection.Open();
DataTable dt = new DataTable();
using (dr = cmd.ExecuteReader())
{
if (dr != null)
dt.Load(dr);
else
dt = null;
}
return dt;
}
catch (Exception e)
{
Exception x = new Exception(String.Format("DataAccess.SPExecuteDataTable() {0}", e.Message));
throw x;
}
finally
{
conn.Close();
cmd.Connection.Close();
dr.Close();
conn.Dispose();
cmd.Dispose();
dr.Dispose();
}
До сих пор я пытался явно закрыть соединения (например, в моем блоке finally), но это не работает. Я также пробовал использовать такие утверждения:
using (SqlDataReader dr = blah blah blah)
{
//code here
}
Но это тоже не работает. Что не так с моим кодом, здесь?
Решение:
Используйте DataTables! Чтобы не допустить, чтобы уровни доступа, не связанные с доступом к данным вашего приложения, были связаны с базой данных, просто сделайте это на своем уровне доступа к данным:
using (SqlDataReader dr = cmd.ExecuteReader())
{
if (dr != null)
dt.Load(dr);
else
dt = null;
}
return dt;
Затем вы можете манипулировать dt, как бы вы ни хотели, в остальной части вашего решения, и соединения УЖЕ БЫЛИ правильно утилизированы. Работает как шарм и, к счастью, код для datatable и datareader очень схожи, поэтому изменение приложения таким образом было относительно безболезненным.
Предпочтительной практикой является обматывание соединений, команд и считывателей при using
блоков:
using(SqlConnection conn = new SqlConnection(ConnectionData[1])
{
using(SqlCommand cmd = new SqlCommand(ConnectionData[0], conn)
{ // ^-- re-use connection - see comment below
cmd.CommandType = CommandType.StoredProcedure;
for (int i = 0; i < args.Length; i++)
{
SqlParameter Param = new SqlParameter(ConnectionData[i + 2], DBNullIfNull(args[i]));
cmd.Parameters.Add(Param);
}
cmd.Connection.Open();
DataTable dt = new DataTable();
using (dr = cmd.ExecuteReader())
{
if (dr != null)
dt.Load(dr);
else
dt = null;
}
return dt;
}
}
таким образом они все закрываются и утилизируются должным образом.
Хотя я думаю, что суть вашей проблемы в том, что вы каждый раз создаете два соединения:
conn = new SqlConnection(ConnectionData[1]);
cmd = new SqlCommand(ConnectionData[0], new SqlConnection(ConnectionData[1]));
^---- creating a second connection
Наконец, вы теряете много потенциально ценных сведений (трассировка стека и т.д.), Создавая новое исключение и бросая его, а не повторно бросая исходное исключение:
catch (Exception e)
{
Exception x = new Exception(String.Format("DataAccess.SPExecuteDataTable() {0}", e.Message));
throw x;
}
Я бы либо допустил исходное исключение, либо включил исходное исключение как InnerException
:
catch (Exception e)
{
string message = String.Format("DataAccess.SPExecuteDataTable() {0}", e.Message);
Exception x = new Exception(message, e);
throw x;
}