初始化数据库时出现 SqlException,但仅限于 Azure 且仅限于 CF 迁移

SqlException while initializing database, but only on Azure and only with CF Migrations

当针对 Azure SQL 服务器数据库 运行 初始化时,我得到一个 SqlExceptionThe server was not found or was not accessible.

这是我的 Context 相关部分的代码:

Private Sub New(Connection As DbConnection)
  MyBase.New(Connection, True)

  Database.SetInitializer(New CreateDatabaseIfNotExists(Of Context))
  Database.SetInitializer(New MigrateDatabaseToLatestVersion(Of Context, Migrations.Configuration))

  Try
    Me.Database.Initialize(False)

  Catch ex As Exception
    HttpContext.Current.Response.Write(ex.ToString)
    HttpContext.Current.Response.End()

  End Try
End Sub

Public Shared Function Create() As Context
  Return New Context(DbConnection)
End Function

这是完整的异常堆栈跟踪:

 1. System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server) ---> System.ComponentModel.Win32Exception (0x80004005): Access is denied
 2.    at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, DbConnectionPool pool, String accessToken, Boolean applyTransientFaultHandling, SqlAuthenticationProviderManager sqlAuthProviderManager)
 3.    at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
 4.    at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
 5.    at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
 6.    at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
 7.    at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
 8.    at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
 9.    at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
10.    at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
11.    at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
12.    at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
13.    at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
14.    at System.Data.SqlClient.SqlConnection.Open()
15.    at System.Data.Entity.Infrastructure.Interception.DbConnectionDispatcher.<>c.<Open>b__13_0(DbConnection t, DbConnectionInterceptionContext c)
16.    at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext](TTarget target, Action`2 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
17.    at System.Data.Entity.Infrastructure.Interception.DbConnectionDispatcher.Open(DbConnection connection, DbInterceptionContext interceptionContext)
18.    at System.Data.Entity.SqlServer.SqlProviderServices.<>c__DisplayClass60_0.<UsingConnection>b__0()
19.    at System.Data.Entity.Infrastructure.DbExecutionStrategy.<>c__DisplayClass17_0.<Execute>b__0()
20.    at System.Data.Entity.Infrastructure.DbExecutionStrategy.Execute[TResult](Func`1 operation)
21.    at System.Data.Entity.Infrastructure.DbExecutionStrategy.Execute(Action operation)
22.    at System.Data.Entity.SqlServer.SqlProviderServices.UsingConnection(DbConnection sqlConnection, Action`1 act)
23.    at System.Data.Entity.SqlServer.SqlProviderServices.UsingMasterConnection(DbConnection sqlConnection, Action`1 act)
24.    at System.Data.Entity.SqlServer.SqlProviderServices.CreateDatabaseFromScript(Nullable`1 commandTimeout, DbConnection sqlConnection, String createDatabaseScript)
25.    at System.Data.Entity.SqlServer.SqlProviderServices.DbCreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
26.    at System.Data.Entity.Core.Common.DbProviderServices.CreateDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
27.    at System.Data.Entity.Core.Objects.ObjectContext.CreateDatabase()
28.    at System.Data.Entity.Migrations.Utilities.DatabaseCreator.Create(DbConnection connection)
29.    at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
30.    at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
31.    at System.Data.Entity.MigrateDatabaseToLatestVersion`2.InitializeDatabase(TContext context)
32.    at System.Data.Entity.Internal.InternalContext.<>c__DisplayClass66_0`1.<CreateInitializationAction>b__0()
33.    at System.Data.Entity.Internal.InternalContext.PerformInitializationAction(Action action)
34.    at System.Data.Entity.Internal.InternalContext.PerformDatabaseInitialization()
35.    at System.Data.Entity.Internal.LazyInternalContext.<>c.<InitializeDatabase>b__58_0(InternalContext c)
36.    at System.Data.Entity.Internal.RetryAction`1.PerformAction(TInput input)
37.    at System.Data.Entity.Internal.LazyInternalContext.InitializeDatabaseAction(Action`1 action)
38.    at System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
39.    at System.Data.Entity.Internal.InternalContext.Initialize()
40.    at Website.Db.Context..ctor(DbConnection Connection) in D:\Dev\Application\__Legacy\Website\Db\Context.vb:line 24
41. ClientConnectionId:00000000-0000-0000-0000-000000000000
42. Error Number:5,State:0,Class:20

现在,当我在初始化尝试之前插入一些测试代码时,例如:

Public Shared Function Create() As Context
  Return New Context(DbConnection)
End Function

Private Sub New(Connection As DbConnection)
  MyBase.New(Connection, True)

  Database.SetInitializer(New CreateDatabaseIfNotExists(Of Context))
  Database.SetInitializer(New MigrateDatabaseToLatestVersion(Of Context, Migrations.Configuration))

  Dim sConnectionString As String = Connection.ConnectionString

  HttpContext.Current.Response.Write($"Connection string: {sConnectionString}")
  HttpContext.Current.Response.Write("<br />")
  HttpContext.Current.Response.Write($"Can connect: {Db.Connection.CanConnect(sConnectionString)}")
  HttpContext.Current.Response.End()

  Try
    Me.Database.Initialize(False)

  Catch ex As Exception
    HttpContext.Current.Response.Write(ex.ToString)
    HttpContext.Current.Response.End()

  End Try
End Sub

...我的字符串是正确的,连接顺利通过。只有当我尝试通过我的上下文连接时它才会失败(即 Me.Database.Initialize(False))。

这是我的 CanConnect 代码:

Public Shared Function CanConnect(ConnectionString As String) As Boolean
  CanConnect = True

  Try
    Utils.ExecuteNonQuery("SELECT * FROM INFORMATION_SCHEMA.TABLES", ConnectionString)

  Catch ex As Exception
    CanConnect = False

  End Try
End Function

Public Shared Function ExecuteNonQuery(CommandText As String, ConnectionString As String) As Integer
  Using oSqlCnn As New SqlConnection(ConnectionString)
    Using oSqlCmd As SqlCommand = oSqlCnn.CreateCommand
      oSqlCmd.CommandType = CommandType.Text
      oSqlCmd.CommandText = CommandText
      oSqlCnn.Open()

      Return oSqlCmd.ExecuteNonQuery
    End Using
  End Using
End Function

因此,普通 ADO.NET 连接可以通过,而 EF Code First 连接则不能——两者都使用相同的连接字符串。很奇怪。

更神秘的是,所有这些在我连接到本地安装的 SQLEXPRESS 实例的开发机器上运行良好。我的上下文在此处连接和应用迁移没有问题。它只在 Azure 上失败。

搜索没有显示任何内容,大部分只是关于如何正确配置迁移方案的建议。正如我的开发机器所证明的那样,我已经这样做了。

我简要地查看了 SqlException 的成员,我想我可能会得到连接字符串并检查它的准确性,但这不是一个选择。

为什么此连接尝试仅在上下文中且仅在 Azure 上失败?我怎样才能找到它以便修复它?

--编辑--

这是我的连接字符串(为安全起见进行了清理):

Server=tcp:some.database.windows.net,1433;Initial Catalog=somedb;Persist Security Info=False;User ID=username;Password=password;

--编辑--

有些进展停滞不前,但新信息使情况变得更加模糊。

根据 this answer,我正在等待 Azure SQL 工程师的确认,System.Data.SqlClient 首先尝试 TCP 连接(假设它被指示通过连接字符串中的 tcp: 前缀)。如果第一次尝试失败,客户端将回退到命名管道。

这似乎就是这种情况下发生的情况,因为堆栈跟踪指示命名管道失败(@AlwaysLearning 在评论中注意到这一点很有帮助)。当然,Azure SQL 不支持命名管道,因此此时会出现连接失败。

所以我一开始以为我们离解决方案又近了一步,直到我遇到这个:对服务器 sys.event_log 的查询显示几乎一致的成功连接记录。

真的很奇怪。

--编辑--

在我的本地开发机器上禁用命名管道的测试没有显示任何内容。仅在 TCP 上连接就顺利通过。

所以缩小了一点。失败仅通过 EF6 发生,并且仅发生在 Azure 上。 (但连接日志显示成功。)

这真让人头疼。

--编辑--

我已经能够确认日志中的成功连接来自我测试的 ADO.NET 个连接(以上)。

失败的连接不会写入日志,因为无法找到服务器来记录它们(至少是命名管道尝试)。这仍然是个大谜。

--编辑--

我在 EntityFramework repo 上开了一个问题并发布了一个 repro 项目:

--编辑--

线索不断涌现,但它们继续指向各个不同的方向。

我在同一台服务器上有另一个数据库,其网站使用与我在这里使用的完全相同的迁移代码。该应用程序工作正常,连接字符串几乎相同。

令人困惑。但是这个问题是服务器层面的,不是数据库层面的。

是否可以在连接字符串到达​​数据库之前拦截它,以验证它在堆栈中没有被破坏?

问题已解决

精明的开发人员(不是我)会在重现项目的提交 3c20e41Context class 中注意到我在两个不同的地方构建了两个单独的连接字符串。当时,我误以为 CF Migrations 只对 design-time 操作使用默认构造函数,例如Add-MigrationUpdate-Database

事实证明,这显然是不是的情况。 DbMigrator 运行时代码执行确实会循环回调用默认构造函数(有一个隐藏的线索:请参阅上面堆栈跟踪中的第 31 行)。默认构造函数是我获取 design-time 连接字符串的地方。这就是 Azure-based 连接尝试失败的原因——因为它找不到 design-time 字符串提供的服务器名称。顺其自然。

现在检查提交 d45888b 中修复的代码。只有一个连接字符串源,如果 Configuration.ConnectionStringsNothing,则该源仅构建 design-time 字符串。 (我以前遇到过这种情况,这就是我构建 two-string 概念的原因——我只是没有深入了解它。)

这就是它在我的本地开发机器上运行良好的原因——因为我使用的是 design-time 字符串。

我只是偶然发现了这一切。我正在制定另一个测试方案,我在 design-time string builder 中混淆了用户名和密码值。连接失败报告表明这次登录失败,显示尝试的用户名。等一下!那是我的密码!呸

唯一可以生成 design-time 字符串的方法(在那个版本的代码下)是在调用默认构造函数的过程中。根据我之前的理解,这不是在运行时发生的。

生活和学习。

这是直接的代码,以防在遥远的将来的某个时候我决定取消回购协议。可能,但不太可能。我宁愿把它作为有问题的编程实践的纪念品。


旧上下文(损坏)

Imports System.Data.Common
Imports System.Data.Entity
Imports System.Data.SqlClient
Imports System.Reflection
Imports DbConnectionTest.Db.Models

Namespace Db
  Public Class Context
    Inherits DbContext

    Public Sub New()
      MyBase.New(Utils.DesignTimeConnectionString) ' <--
    End Sub

    Private Sub New(Connection As DbConnection)
      MyBase.New(Connection, True)

      Database.SetInitializer(New CreateDatabaseIfNotExists(Of Context))
      Database.SetInitializer(New MigrateDatabaseToLatestVersion(Of Context, Migrations.Configuration))

      Me.Database.Initialize(False)
    End Sub

    Public Shared Function Create() As Context
      Return New Context(New SqlConnection(Utils.RunTimeConnectionString)) ' <--
    End Function

    Protected Overrides Sub OnModelCreating(Builder As DbModelBuilder)
      Builder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly)
      MyBase.OnModelCreating(Builder)
    End Sub

    Protected Overrides Sub Dispose(Disposing As Boolean)
      MyBase.Dispose(Disposing)
    End Sub

    Public Overridable Property Customers As DbSet(Of Customer)
    Public Overridable Property Invoices As DbSet(Of Invoice)
  End Class
End Namespace

新实用程序(正在运行)

Imports System
Imports System.Configuration
Imports System.Data
Imports System.Data.SqlClient

Namespace Db
  Friend Class Utils
    Friend Shared ReadOnly Property DbConnectionString() As String
      Get
        If ConfigurationManager.ConnectionStrings Is Nothing Then
          With New SqlConnectionStringBuilder
            .MultipleActiveResultSets = True
            .PersistSecurityInfo = False
            .IntegratedSecurity = False
            .InitialCatalog = DB_NAME
            .DataSource = Environment.MachineName
            .Password = ""
            .UserID = ""

            DbConnectionString = .ConnectionString
          End With
        Else
          DbConnectionString = ConfigurationManager.ConnectionStrings(DB_NAME).ConnectionString
        End If
      End Get
    End Property

    Friend Const DB_NAME As String = "DbConnectionTest"
  End Class
End Namespace