【问题标题】:SqlCommand Create Database race condition in C#SqlCommand 在 C# 中创建数据库竞争条件
【发布时间】:2013-12-17 21:09:58
【问题描述】:

如果我创建一个数据库,然后尝试在 2 秒内使用 MS Sync Framework 连接到它,则会抛出异常“无法打开数据库...”

添加

Thread.Sleep(4000);

将导致代码正常工作,但我不希望设置一个硬时间,因为此代码将在平板电脑和其他弱硬件上运行,因此生成数据库的时间可能会有很大差异。

我可以在创建后检查数据库是否存在并且它总是立即存在,因此条件等待(旋转)不是一个选项。

using (SqlConnection masterConnection = new SqlConnection(masterConnectionString)) {
    using (SqlCommand sqlCommand = new SqlCommand(string.Format("IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '{0}') CREATE DATABASE {0}", databaseName), masterConnection)) {
        masterConnection.Open();
        sqlCommand.ExecuteNonQuery();
    }
}

bool databaseExist = false;
while (!databaseExist) {
    using (SqlConnection masterConnection = new SqlConnection(masterConnectionString)) {
        using (SqlCommand verifySqlCommand = new SqlCommand(string.Format("SELECT database_id FROM sys.databases WHERE name = '{0}'", databaseName), masterConnection)) {
            masterConnection.Open();
            databaseExist = (int)verifySqlCommand.ExecuteScalar() > 0; // Always true
        }
    }
    Thread.Sleep(1000);
}
...
using (SqlConnection sqlConnection = new SqlConnection(CONNECTION_STRING)) {
    SqlSyncScopeProvisioning sqlSyncScopeProvisioning = new SqlSyncScopeProvisioning(sqlConnection); // throw ("Cannot open database") db doesn't exist 
}

问题似乎出在 SqlConnection 上,第一个 SqlConnection 可以看到数据库存在,但第二个(新的)在最初的几秒钟内看不到它存在(SqlConnection.Open() 失败)。

堆栈跟踪

at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.Open()
at Microsoft.Synchronization.Data.SyncUtil.TryOpenConnection(IDbConnection connection)
at Microsoft.Synchronization.Data.SyncUtil.OpenConnection(IDbConnection connection)
at Microsoft.Synchronization.Data.SqlServer.SqlEditionHelper.GetEdition(SqlConnection connection)
at Microsoft.Synchronization.Data.SqlServer.SqlSyncScopeProvisioning.set_Connection(SqlConnection value)
at Microsoft.Synchronization.Data.SqlServer.SqlSyncScopeProvisioning..ctor(SqlConnection connection, DbSyncScopeDescription scopeDescription, SqlSyncScopeProvisioningType provisioningType, Boolean expectConnection)
at Microsoft.Synchronization.Data.SqlServer.SqlSyncScopeProvisioning..ctor(SqlConnection connection)

Mark II 基于@CharlieBrown 的回答

@CharlieBrown 发布的代码本身可以工作,但在我的设置中不起作用,以下代码演示了崩溃。如果您取消注释手动 throw,则代码将正常工作,否则不会。

try {
    //If I manually throw this exception then code in catch will run fine, otherwise not.
    //throw new Exception("Cannot open database testDB"); 
    using (var sqlConnection = new SqlConnection(CONNECTION_STRING)) {
        SqlSyncScopeProvisioning sqlSyncScopeProvisioning = new SqlSyncScopeProvisioning(sqlConnection); // throw ("Cannot open database") db doesn't exist 
    }
} catch (Exception exception) {
    if (exception.Message.StartsWith("Cannot open database")) { // Database does not exist, try to create
        try {
            using (var masterConnection = new SqlConnection(masterConnectionString)) {
                using (var sqlCommand = new SqlCommand(string.Format("IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '{0}') CREATE DATABASE {0}", databaseName), masterConnection)) {
                    masterConnection.Open();
                    sqlCommand.ExecuteNonQuery();
                }
            }
            using (var sqlConnection = new SqlConnection(CONNECTION_STRING)) {
                SqlSyncScopeProvisioning sqlSyncScopeProvisioning = new SqlSyncScopeProvisioning(sqlConnection); // throw ("Cannot open database") db doesn't exist 
            }
        } catch (Exception ex) { // Always end up here
            // ex = System.Data.SqlClient.SqlException (0x80131904): Cannot open database "testDB" requested by the login. The login failed. Login failed for user ...
        }
    } else { // Other exception
        // Never hit
    }
}

编辑

根据使用建议更新了代码,添加了堆栈跟踪。 添加了另一个代码示例来演示问题。

解决方案

在与@CharlieBrown 交谈后,似乎没有解决此问题的方法,因此我将通过始终运行 SqlCommand 来创建数据库(如果不存在)来解决它,而不是尝试使用数据库然后在 catch 中创建() 如果异常。此代码仅在应用启动时运行。

【问题讨论】:

  • 我认为您可能在这里遗漏了一个问题...
  • 如何让代码在不睡觉的情况下立即正常工作/访问数据库。
  • 或智能旋转/等待它。前任。虽然(不存在){sleep()}
  • 我的建议就是你刚才提到的;使用while 循环查看数据库是否存在,如果存在,则连接到它。
  • 但是这样做表明如果使用相同的连接,数据库确实存在。我会尝试检查它是否存在于第二个连接上。

标签: c# sql .net microsoft-sync-framework sqlcommand


【解决方案1】:

您对SqlSyncScopeProvisioning 的调用在 SQL Server 有时间让数据库准备好进行同步之前被调用。我找不到关于临时时间量的参考,但是在创建数据库之后,会发生几个触发器来准备数据库。

工作代码: 针对 LocalDb、Sql Server 2008r2、Sql Server 2012 测试

var connectionString = @"Data Source=(LocalDb)\v11.0;Initial Catalog=master;Integrated Security=True;";
var databaseName = "TestRepl";

using (var masterConnection = new SqlConnection(connectionString)) {
    using (var sqlCommand = new SqlCommand(string.Format("IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '{0}') CREATE DATABASE {0}", databaseName), masterConnection)) {
        masterConnection.Open();
        sqlCommand.ExecuteNonQuery();
    }
}

var syncConnectionString = string.Format(@"Data Source=(LocalDb)\v11.0;Initial Catalog={0};Integrated Security=True;", databaseName);
using(var sqlConnection = new SqlConnection(syncConnectionString)){
    SqlSyncScopeProvisioning sqlSyncScopeProvisioning = new SqlSyncScopeProvisioning(sqlConnection); // throw ("Cannot open database") db doesn't exist 
}

【讨论】:

  • 是的,我尝试在创建后重置它们,但对这个问题没有帮助。我没有在上面显示代码以简化代码量。
  • @Charlie - 配置是为同步准备数据库的部分,而不是创建数据库
  • @JuneT 正确。我说的是在配置数据库之前所花费的时间。我会更新答案以更清楚。
  • @CharlieBrown 编辑后你的代码很好地展示了这个问题,但该代码回答了什么?
  • @Brent 在我的机器上,它可以正常工作而不会引发异常。
【解决方案2】:

更新:


再次阅读您的问题后,我注意到您使用了SqlSyncScopeProvisioning,这是一个异步操作。您的数据库虽然已创建,但更有可能完成配置 - 但由于它是异步,它在完成之前返回。所以下一个命令触发,因为它还没有完成。

很可能没有分配的完成时间。

您还应该真正使用using command。它实现了IDisposable,这将有助于处理内存分配,这将有助于资源管理。

请记住以上述方式执行原始 SQL,它可能容易受到 SQL 注入的影响。

您可能想听从 Scott Chamberlain 的建议,使用 parameters

private static void CreateSQLDatabase(string dbName)
{
     string createDb = "CREATE DATABASE @DatabaseName";
     using(SqlCommand buildSqlCommand = new SqlCommand(createDb, connectionForSql))
     {
          buildSqlCommand.Parameters.Add("@DatabaseName", dbName);
          connectionForSQL.Open();
          buildSqlCommand.ExecuteNonQuery();
     }
}   

您还需要确保数据库在创建之前不存在,您可能还需要在继续之前检查它是否确实成功创建和配置。

重要提示:“可能容易受到 SQL 注入”的影响

using(SqlConnection connectionForSQL = new SqlConnection(@"Server=localhost; Integrated Security=SSPI; Database=master"))
{
     string verifyQuery = string.Format("SELECT database_id FROM sys.databases WHERE NAME = '{0}'", dbName);
     using(SqlCommand verifySqlCommand = new SqlCommand(verifyQuery, connectionForSQL))
     {
        connectionForSQL.Open();
        int databaseId = (int)verifySqlCommand.ExecuteScalar();
        return databaseId > 0;
     }
}

【讨论】:

  • 他应该使用using,但你确实应该使用参数(他也应该如此)。
  • 很高兴它帮助了@Brent
  • 目前实际上不工作,我可以调用 IsDatabaseInExistence 它将返回 true,我可以等待 1000 毫秒,如果我不等待更长时间,仍然打开连接会失败。
  • Greg,你的 CreateSQLDatabase 方法对 Sql Injection 是开放的,这正是@ScottChamberlain 所指出的。
  • @greg - SqlSyncScopeProvisioning 不是异步的。同样,实际的供应发生在您调用 Apply() 时。
猜你喜欢
  • 2012-04-08
  • 2019-03-02
  • 2013-08-12
  • 2010-12-07
  • 1970-01-01
  • 2013-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多