Reputation: 5626
If the user under which my service is running doesn't have access to a database, EF will fail with the following exception:
System.Data.Entity.Core.ProviderIncompatibleException: An error occurred while getting provider information from the database. This can be caused by Entity Framework using an incorrect connection string. Check the inner exceptions for details and ensure that the connection string is correct.
Here's the inner exception (that has the specific details):
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)
The issue itself is pretty clear: the user doesn't have access. What isn't clear, though, is which database we're trying to open. When I have several databases (several DbContexts) being used in the span of one call, I want to know which database is rejecting my user, and I want to know it quickly and easily.
As such, I was looking for some way to override the opening of the connection, so that I can catch the exception and wrap it with more information. However, I don't see an easy way to do this, since DbContext
just uses a DbConnection
. How do I hook into this process to wrap the connection opening? Or is there a better way to get this information than what I'm trying to do?
The exception itself doesn't give me a useful stack trace, since my service is completely async
.
Upvotes: 4
Views: 531
Reputation: 15807
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();
}
}
You get the idea :) Good luck
Upvotes: 0
Reputation: 5771
The Inner Exception has most of the details you may require. The base exception in this case is an SqlException
try
{
//code that fires the error
}
catch (ProviderIncompatibleException ex)
{
SqlException baseException = ex.GetBaseException() as SqlException;
Console.WriteLine(baseException.Server);
}
You can log this additional information wherever you are catching the Exception.
Upvotes: 2