Пожалуйста помоги!
Справочная информация
У меня есть приложение WPF, которое обращается к базе данных SQL Server 2005. База данных работает локально на машине, на которой работает приложение.
Везде, где я использую Linq DataContext, я использую оператор using {} и передаю результат функции, которая возвращает объект SqlConnection, который был открыт и с его помощью была выполнена команда SqlCommand перед возвратом в конструктор DataContext.
// In the application code
using (DataContext db = new DataContext(GetConnection()))
{
... Code
}
где getConnection выглядит следующим образом (я убрал из функции «пустяк», чтобы сделать ее более читаемой, но дополнительных функций, которые отсутствуют).
// Function which gets an opened connection which is given back to the DataContext constructor
public static System.Data.SqlClient.SqlConnection GetConnection()
{
System.Data.SqlClient.SqlConnection Conn = new System.Data.SqlClient.SqlConnection(/* The connection string */);
if ( Conn != null )
{
try
{
Conn.Open();
}
catch (System.Data.SqlClient.SqlException SDSCSEx)
{
/* Error Handling */
}
using (System.Data.SqlClient.SqlCommand SetCmd = new System.Data.SqlClient.SqlCommand())
{
SetCmd.Connection = Conn;
SetCmd.CommandType = System.Data.CommandType.Text;
string CurrentUserID = System.String.Empty;
SetCmd.CommandText = "DECLARE @B VARBINARY(36); SET @B = CAST('" + CurrentUserID + "' AS VARBINARY(36)); SET CONTEXT_INFO @B";
try
{
SetCmd.ExecuteNonQuery();
}
catch (System.Exception)
{
/* Error Handling */
}
}
return Conn;
}
Я не думаю, что приложение, являющееся WPF, имеет какое-либо отношение к моей проблеме.
Проблема, с которой я столкнулся
Несмотря на то, что SqlConnection удаляется вместе с DataContext в Sql Server Management studio, я все еще могу видеть множество открытых соединений с:
status : 'Sleeping'
command : 'AWAITING COMMAND'
last SQL Transact Command Batch : DECLARE @B VARBINARY(36); SET @B = CAST('GUID' AS VARBINARY(36)); SET CONTEXT_INFO @B
В конце концов, пул соединений израсходован, и приложение не может продолжить работу.
Поэтому я могу только сделать вывод, что выполнение SQLCommand каким-то образом для установки Context_Info означает, что соединение не удаляется при удалении DataContext.
Может ли кто-нибудь заметить что-нибудь очевидное, что могло бы помешать закрытию и удалению соединений при удалении DataContext, которым они используются?





Из MSDN (DataContext Constructor (IDbConnection)):
If you provide an open connection, the DataContext will not close it. Therefore, do not instantiate a DataContext with an open connection unless you have a good reason to do this.
В общем, похоже, что ваши соединения ждут, пока сборщик мусора завершит их, прежде чем они будут выпущены. Если у вас много кода, который делает это, одним из подходов может быть переопределение Dispose() в частичном классе контекста данных и закрытие соединения - просто убедитесь, что документально зафиксировано, что контекст данных принимает на себя владение соединением!
protected override void Dispose(bool disposing)
{
if (disposing && this.Connection != null && this.Connection.State == ConnectionState.Open)
{
this.Connection.Close();
this.Connection.Dispose();
}
base.Dispose(disposing);
}
Лично я с радостью предоставил бы ему (обычный контекст данных, без взлома выше) открытое соединение, пока я "использовал" соединение (позволяя мне выполнять несколько операций), т.е.
using(var conn = GetConnection())
{
// snip: some stuff involving conn
using(var ctx = new FooContext(conn))
{
// snip: some stuff involving ctx
}
// snip: some more stuff involving conn
}
Dispose должен закрыть соединения, как указывает MSDN:
If the SqlConnection goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling Close or Dispose. Close and Dispose are functionally equivalent. If the connection pooling value Pooling is set to true or yes, the underlying connection is returned back to the connection pool. On the other hand, if Pooling is set to false or no, the underlying connection to the server is closed.
Я предполагаю, что ваша проблема как-то связана с GetContext().
SqlProvider, используемый LINQ DataContext, закрывает соединение SQL (через SqlConnectionManager.DisposeConnection) только в том случае, если оно открывало его. Если вы передадите уже открытый объект SqlConnection конструктору DataContext, он не закроет его за вас. Таким образом, вы должны написать:
using (SqlConnection conn = GetConnection())
using (DataContext db = new DataContext(conn))
{
... Code
}
Я думаю, что соединение, на которое больше не ссылаются, ожидает, пока сборщик мусора полностью избавится от него.
Решение:
Создайте свой собственный класс DataContext, производный от автоматически сгенерированного. (переименуйте базовый, чтобы вам не пришлось менять какой-либо другой код).
В производном DataContext добавьте функцию Dispose (). В этом - избавьтесь от внутренней связи.
Что ж, спасибо за помощь, ребята, теперь это решено ..
По сути, я взял элементы из большинства ответов выше и реализовал конструктор DataContext, как указано выше (я уже перегрузил конструкторы, поэтому это не было большим изменением).
// Variable for storing the connection passed to the constructor
private System.Data.SqlClient.SqlConnection _Connection;
public DataContext(System.Data.SqlClient.SqlConnection Connection) : base(Connection)
{
// Only set the reference if the connection is Valid and Open during construction
if (Connection != null)
{
if (Connection.State == System.Data.ConnectionState.Open)
{
_Connection = Connection;
}
}
}
protected override void Dispose(bool disposing)
{
// Only try closing the connection if it was opened during construction
if (_Connection!= null)
{
_Connection.Close();
_Connection.Dispose();
}
base.Dispose(disposing);
}
Причина, по которой это делается, а не некоторые из приведенных выше предложений, заключается в том, что доступ к this.Connection в методе удаления вызывает ObjectDisposedException.
И вышеперечисленное работает так хорошо, как я надеялся!
Эй, это то, что я сказал. Я должен быть умным.
У меня возникла такая же проблема при использовании Entity Framework. Мой ObjectContext был обернут вокруг блока using.
Соединение было установлено, когда я позвонил SaveChanges(), но после того, как оператор using вышел за рамки, я заметил, что в SQL Management Studio все еще есть "AWAITING COMMAND" для .NET SQL Client.
Похоже, это связано с поведением провайдера ADO.NET, у которого по умолчанию включен пул соединений.
Из "Использование пула соединений с SQL Server" на MSDN (выделено мной):
Connection pooling reduces the number of times that new connections need to be opened. The pooler maintains ownership of the physical connection. It manages connections by keeping alive a set of active connections for each given connection configuration. Whenever a user calls
Openon a connection, the pooler looks to see if there is an available connection in the pool. If a pooled connection is available, it returns it to the caller instead of opening a new connection. When the application callsCloseon the connection, the pooler returns it to the pooled set of active connections instead of actually closing it. Once the connection is returned to the pool, it is ready to be reused on the nextOpencall.
Также ClearAllPools и ClearPool кажутся полезными для явного закрытия всех объединенных соединений, если это необходимо.
Вы можете просто добавить частичный класс для расширения автоматически сгенерированного контекста данных; нет необходимости в подклассе.