如果我创建了一个数据库,然后在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代码本身可以正常工作,但不在我的设置中,以下代码演示了崩溃。如果取消注释手动抛出,则代码将正常工作,否则不会。
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中创建()如果例外。此代码仅在app start上运行。
答案 0 :(得分:3)
在SQL Server有时间让数据库为同步做好准备之前,正在调用对 SqlSyncScopeProvisioning
的调用。我无法找到关于时间量的参考,但是在创建数据库之后,会发生几个触发器来准备数据库。
工作代码: 针对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
}
答案 1 :(得分:1)
再次阅读您的问题后,我注意到您使用了SqlSyncScopeProvisioning
这是一个异步操作。您创建的数据库很可能最终完成配置 - 但由于它是 Asynchronous ,它在最终确定之前返回。所以下一个命令会触发,因为它尚未完成。
更可能没有分配的完成时间。
此外,您应该真正使用using command
。它实现了IDisposable
,它将帮助处理内存分配,这将有助于资源管理。
请注意以上述方式实现原始SQL,它可能会受到 SQL Injection 的影响。
您可能想要遵循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;
}
}