Нам требуется программный доступ к службе SQL Server Express как части нашего приложения. В зависимости от того, что пытается сделать пользователь, нам может потребоваться присоединить базу данных, отсоединить базу данных, создать резервную копию и т. Д. Иногда служба может не запускаться до того, как мы попытаемся выполнить эти операции. Поэтому нам нужно убедиться, что сервис запущен. Здесь мы сталкиваемся с проблемами. Очевидно, ServiceController.WaitForStatus(ServiceControllerStatus.Running)
преждевременно возвращается для SQL Server Express. То, что действительно озадачивает, - то, что главная база данных, кажется, немедленно доступна, но не другие базы данных. Вот консольное приложение, чтобы продемонстрировать, о чем я говорю:
namespace ServiceTest
{
using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
class Program
{
private static readonly ServiceController controller = new ServiceController("MSSQL$SQLEXPRESS");
private static readonly Stopwatch stopWatch = new Stopwatch();
static void Main(string[] args)
{
stopWatch.Start();
EnsureStop();
Start();
OpenAndClose("master");
EnsureStop();
Start();
OpenAndClose("AdventureWorksLT");
Console.ReadLine();
}
private static void EnsureStop()
{
Console.WriteLine("EnsureStop enter, {0:N0}", stopWatch.ElapsedMilliseconds);
if (controller.Status != ServiceControllerStatus.Stopped)
{
controller.Stop();
controller.WaitForStatus(ServiceControllerStatus.Stopped);
Thread.Sleep(5000); // really, really make sure it stopped ... this has a problem too.
}
Console.WriteLine("EnsureStop exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
private static void Start()
{
Console.WriteLine("Start enter, {0:N0}", stopWatch.ElapsedMilliseconds);
controller.Start();
controller.WaitForStatus(ServiceControllerStatus.Running);
// Thread.Sleep(5000);
Console.WriteLine("Start exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
private static void OpenAndClose(string database)
{
Console.WriteLine("OpenAndClose enter, {0:N0}", stopWatch.ElapsedMilliseconds);
var connection = new SqlConnection(string.Format(@"Data Source=.\SQLEXPRESS;initial catalog={0};integrated security=SSPI", database));
connection.Open();
connection.Close();
Console.WriteLine("OpenAndClose exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
}
}
На моей машине это не получится, как написано. Обратите внимание, что подключение к «мастеру» не имеет проблем; только соединение с другой базой данных. (Вы можете изменить порядок соединений, чтобы убедиться в этом.) Если вы раскомментируете Thread.Sleep
в методе Start()
, он будет работать нормально.
Очевидно, я хочу избежать произвольного Thread.Sleep()
. Помимо запаха рангового кода, какое произвольное значение я бы там поставил? Единственное, о чем мы можем подумать, - это поместить несколько фиктивных соединений с нашей целевой базой данных в цикл while, перехватить сгенерированное SqlException и повторить попытку, пока он не заработает. Но я думаю, что должно быть более элегантное решение, чтобы знать, когда служба действительно готова к использованию. Есть идеи?
РЕДАКТИРОВАТЬ: На основе обратной связи, представленной ниже, я добавил проверку состояния базы данных. Тем не менее, все еще терпит неудачу. Похоже, даже государство ненадежно. Вот функция, которую я вызываю перед OpenAndClose (string):
private static void WaitForOnline(string database)
{
Console.WriteLine("WaitForOnline start, {0:N0}", stopWatch.ElapsedMilliseconds);
using (var connection = new SqlConnection(string.Format(@"Data Source=.\SQLEXPRESS;initial catal
using (var command = connection.CreateCommand())
{
connection.Open();
try
{
command.CommandText = "SELECT [state] FROM sys.databases WHERE [name] = @DatabaseName";
command.Parameters.AddWithValue("@DatabaseName", database);
byte databaseState = (byte)command.ExecuteScalar();
Console.WriteLine("databaseState = {0}", databaseState);
while (databaseState != OnlineState)
{
Thread.Sleep(500);
databaseState = (byte)command.ExecuteScalar();
Console.WriteLine("databaseState = {0}", databaseState);
}
}
finally
{
connection.Close();
}
}
Console.WriteLine("WaitForOnline exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
Я нашел другое обсуждение , посвященное аналогичной проблеме. По-видимому, решение состоит в том, чтобы проверить файлы sys.database_files рассматриваемой базы данных. Но это, конечно, проблема курицы и яйца. Есть еще идеи?