我对this question没有运气,所以我已经制作了这个简单的测试用例来证明这个问题。
在下面的代码中,在尝试使用连接之前是否可以检测到连接不可用?
SqlConnection c = new SqlConnection(myConnString);
c.Open(); // creates pool
setAppRole(c); // OK
c.Close(); // returns connection to pool
c = new SqlConnection(myConnString); // gets connection from pool
c.Open(); // ok... but wait for it...
// ??? How to detect KABOOM before it happens?
setAppRole(c); // KABOOM
KABOOM在Windows事件日志中显示为错误;
连接已被删除,因为打开它的主体随后假定了一个新的安全上下文,然后尝试在其模拟的安全上下文下重置连接。不支持此方案。请参阅联机丛书中的“模拟概述”。
...加上代码中的异常。
setAppRole是一种在连接上设置应用程序角色的简单方法。它类似于......
static void setAppRole(SqlConnection conn) {
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "exec sp_setapprole ";
cmd.CommandText += string.Format("@rolename='{0}'",myUser);
cmd.CommandText += string.Format(",@password='{0}'",myPassword);
cmd.ExecuteNonQuery();
}
}
在实际代码中,尝试在关闭连接之前使用 sp_unsetapprole ,但无法始终保证(继承错误的多线程应用程序)。在任何情况下,期望能够在导致它之前检测到kaboom似乎仍然是合理的。
答案 0 :(得分:4)
简而言之,它看起来并不像你能做到的那样简单。
我的第一个想法是运行这个SQL:
SELECT CASE WHEN USER = 'MyAppRole' THEN 1 ELSE 0 END
如果您使用SQL Server Management Studio,则可以正常工作,但是当您从C#代码运行它时会失败。问题是当调用sp_setapprole时没有发生错误,它实际上是在连接池调用sp_reset_connection时发生的。连接池在您第一次使用连接时调用它,并且无法在它之前进入。
所以我想你有四个选择:
class Program
{
static void Main(string[] args)
{
SqlConnection conn = new SqlConnection("Server=(local);Database=Test;UID=Scrap;PWD=password;");
setAppRole(conn);
conn.Close();
setAppRole(conn);
conn.Close();
}
static void setAppRole(SqlConnection conn)
{
for (int i = 0; i < 2; i++)
{
conn.Open();
try
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "exec sp_setapprole ";
cmd.CommandText += string.Format("@rolename='{0}'", "MyAppRole");
cmd.CommandText += string.Format(",@password='{0}'", "password1");
cmd.ExecuteNonQuery();
}
}
catch (SqlException ex)
{
if (i == 0 && ex.Number == 0)
{
conn.Close();
SqlConnection.ClearPool(conn);
continue;
}
else
{
throw;
}
}
return;
}
}
}
答案 1 :(得分:1)
它实际上是在连接池调用时发生的 sp_reset_connection的。第一次使用时,连接池会调用此方法 一个连接,没有办法进入它。
基于Martin Brown's answer,您可以尝试将“Connection Reset = False”添加到连接字符串,作为“get in before”sp_reset_connection的方法。 (有关此设置的许多缺点的解释,请参阅“Working with “soiled” connections”。)
您的问题是known issue with connection pooling。建议的解决方法是禁用连接池...如果这是桌面应用程序,则可能值得考虑打开几个连接(也在the article linked above中解释)。
这些天(SQL 2005+)建议(在Application Roles and Connection Pooling下)是“利用您可以使用的new security mechanisms而不是应用程序角色”,例如EXECUTE AS。
答案 2 :(得分:0)
我不确定你的问题,但我认为如果你创建新的连接对象而不是重用它们,你会避免它。所以不要做
c.Open();
blabla;
c.Close();
c.Open();
kaboom...
您将执行以下操作:
using (new SqlConnection ...)
{
c.Open();
blabla;
}
using (new SqlConnection ... )
{
c.Open();
no kaboom?
}
(请原谅伪代码......我的eeepc上的键盘是不可能使用的......)
答案 3 :(得分:0)
是否有办法清除所有连接池。 SqlPools.Clear等等。
您可以尝试捕获异常并创建一个新连接,这应该强制池创建一个完整的新连接。
答案 4 :(得分:0)
我还发布了这个以回应您之前的问题。在调用sp_setapprole时,你应该在完成后调用sp_unsetapprole,我在那里提出的解决方案将帮助你:
Detecting unusable pooled SqlConnections
看起来你正在调用sp_setapprole但是没有调用sp_unsetapprole然后让连接只返回池。
我建议使用一个结构(或类,如果你必须在方法中使用它)和IDisposable的实现,它将为你处理这个:
public struct ConnectionManager : IDisposable
{
// The backing for the connection.
private SqlConnection connection;
// The connection.
public SqlConnection Connection { get { return connection; } }
public void Dispose()
{
// If there is no connection, get out.
if (connection == null)
{
// Get out.
return;
}
// Make sure connection is cleaned up.
using (SqlConnection c = connection)
{
// See (1). Create the command for sp_unsetapprole
// and then execute.
using (SqlCommand command = ...)
{
// Execute the command.
command.ExecuteNonQuery();
}
}
}
public ConnectionManager Release()
{
// Create a copy to return.
ConnectionManager retVal = this;
// Set the connection to null.
retVal.connection = null;
// Return the copy.
return retVal;
}
public static ConnectionManager Create()
{
// Create the return value, use a using statement.
using (ConnectionManager cm = new ConnectionManager())
{
// Create the connection and assign here.
// See (2).
cm.connection = ...
// Create the command to call sp_setapprole here.
using (SqlCommand command = ...)
{
// Execute the command.
command.ExecuteNonQuery();
// Return the connection, but call release
// so the connection is still live on return.
return cm.Release();
}
}
}
}
客户端代码如下所示:
using (ConnectionManager cm = ConnectionManager.Create())
{
// Get the SqlConnection for use.
// No need for a using statement, when Dispose is
// called on the connection manager, the connection will be
// closed.
SqlConnection connection = cm.Connection;
// Use connection appropriately.
}
答案 5 :(得分:0)
您可以检查c.State(ConnectionState对象),它应该是以下之一:
System.Data.ConnectionState.Broken
System.Data.ConnectionState.Closed
System.Data.ConnectionState.Connecting
System.Data.ConnectionState.Executing
System.Data.ConnectionState.Fetching
System.Data.ConnectionState.Open
答案 6 :(得分:0)
@edg:你在评论中说,“......只有当它碰到实际的服务器并遇到安全问题时,如msg引用中所述。”
这指出了您的问题的根源:您 遇到安全问题,这似乎是不可避免的,因为调用代码假定的另一个标识不是用于打开连接的标识。这自然会产生安全日志条目。
由于身份更改是按设计进行的,因此解决方案可能是过滤安全日志。事件查看器具有“过滤器当前日志”操作,可以按关键字或偶数过滤。
+汤姆
答案 7 :(得分:-1)
尝试移动sp_unsetapprole
(它真的是sproc的名称吗?可能sp_dropapprole
是正确的吗?)到setAppRole()
并在添加app角色之前执行它。< / p>