在Open中包装Entity Framework异常

4

如果我的服务所运行的用户没有访问数据库的权限,那么EF将会报以下异常:

System.Data.Entity.Core.ProviderIncompatibleException:从数据库获取提供程序信息时发生错误。这可能是由于Entity Framework使用了错误的连接字符串引起的,请检查内部异常以获取详细信息,并确保连接字符串正确。

下面是包含具体细节的内部异常:

System.Data.Entity.Core.ProviderIncompatibleException: The provider did not return a ProviderManifestToken string. ---> System.Data.SqlClient.SqlException: Login failed for user '[redacted]'.
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlInternalConnectionTds.CompleteLogin(Boolean enlistOK)
   at System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean ignoreSniOpenTimeout, TimeoutTimer timeout, Boolean withFailover)
   at System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout)
   at System.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance)
   at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData)
   at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
   at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
   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.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.Open()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<>c__DisplayClass1.<Execute>b__0()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.SqlServer.SqlProviderServices.UsingConnection(DbConnection sqlConnection, Action`1 act)
   at System.Data.Entity.SqlServer.SqlProviderServices.UsingMasterConnection(DbConnection sqlConnection, Action`1 act)
   at System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken(DbConnection connection)
   at System.Data.Entity.Core.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection)
   --- End of inner exception stack trace ---
   at System.Data.Entity.Core.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection)
   at System.Data.Entity.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(DbProviderServices providerServices, DbConnection connection)

问题本身很明确: 用户没有访问权限。但是,不清楚我们正在尝试打开哪个数据库。当我在一个调用中使用多个数据库(多个DbContext)时,我想知道哪个数据库拒绝了我的用户,并且希望快速轻松地知道这一点。
因此,我正在寻找某种方法来覆盖连接的打开,以便我可以捕获异常并用更多信息进行包装。然而,我没有找到一个简单的方法来做到这一点,因为 DbContext 只是使用了 DbConnection。如何钩入这个过程来包装连接的打开呢?或者是否有更好的方法来获取我正在尝试做的这件事的信息?
由于我的服务完全是异步的,所以异常本身不会给我一个有用的堆栈跟踪。

为什么当上下文抛出此错误时,无法检查context.Database.Connection.ConnectionString? - Praveen Paulose
@PraveenPaulose:我怎么知道上下文何时抛出错误?我不知道在哪里捕获这个异常。 - zimdanen
@PraveenPaulose:或者你是指我收到了我上面发布的异常吗?这发生在服务的IErrorHandler中;我在这里没有足够的信息来知道使用了哪个DbContext - zimdanen
2个回答

2

内部异常包含您可能需要的大部分详细信息。在这种情况下,基础异常是SqlException。

try
{
    //code that fires the error
}
catch (ProviderIncompatibleException ex)
{
    SqlException baseException = ex.GetBaseException() as SqlException;
    Console.WriteLine(baseException.Server);
}

您可以在捕获异常的任何地方记录此额外信息。


这确实给了我所需的信息。我希望有一些更接近 DbContext 的东西可以集中管理,但是这让我可以将其放在 IErrorHandler 中并继续进行,所以谢谢! - zimdanen
等等,这给了我服务器,但没有数据库。有办法获取数据库吗? - zimdanen
dbContext.Database.Connection.ConnectionString? - Mihail Shishkov
@mcl:在异常发生时,我如何获取DbContext?否则,我该如何包装DbConnection的打开方式以便更早地访问异常? - zimdanen

0
  public class MyContext : DbContext
    {
        public MyContext() : base(new MyDbConnection(), true) {

        }
    }

    public class MyDbConnection : DbConnection
    {
        private SqlConnection connection;

        public MyDbConnection() {
            // init connection here
        }

        protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) {
            return connection.BeginTransaction();
        }

        public override void Close() {
            connection.Close();
        }

        public override void ChangeDatabase(string databaseName) {
            connection.ChangeDatabase(databaseName);
        }

        public override void Open() {
            try {
                connection.Open();
            }
            catch (Exception ex) {
                throw new MyCustomException(this.ConnectionString, ex);
            }
        }

        public override string ConnectionString {
            get {
                return this.connection.ConnectionString;
            }
            set {
                this.connection.ConnectionString = value;
            }
        }

        public override string Database {
            get {
                return this.connection.Database;
            }
        }

        public override ConnectionState State {
            get {
                return this.connection.State;
            }
        }

        public override string DataSource {
            get {
                return this.connection.DataSource;
            }
        }

        public override string ServerVersion {
            get {
                return this.connection.ServerVersion;
            }
        }

        protected override DbCommand CreateDbCommand() {
            return this.connection.CreateCommand();
        }

}

你懂的 :) 祝你好运


如果我这样组合,如何调用受保护的方法以提升它们?我猜反射是唯一的方法? - zimdanen
嗯...我选择了反射方法,但是遇到了一个问题,它实际上调用了OpenAsync,在打开完成之前返回了一个任务,所以即使我在调用周围包装异常,仍然无法捕获异常。 - zimdanen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接