将事务传递给EF时,我们遇到一个奇怪的异常:
抛出异常:'System.InvalidOperationException' System.Data.dll中
附加信息:ExecuteReader要求命令具有 分配给命令的连接在a中时的事务 等待本地交易。命令的Transaction属性 尚未初始化。
this.DbContext = this.DbContextFactory.CreateContext<TContext>(connection);
this.DbContext.Database.UseTransaction(transaction);
此异常由EF捕获,因为只有在“抛出时断开”处于打开状态时才会显示该异常。这是预期的行为,还是我们做了一些可能出错的事情?
以下是调用堆栈的外观:
System.Data.dll!System.Data.SqlClient.SqlCommand.ValidateCommand(string method, bool async)
System.Data.dll!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior cmdBehavior, System.Data.SqlClient.RunBehavior runBehavior, bool returnStream, string method, System.Threading.Tasks.TaskCompletionSource<object> completion, int timeout, out System.Threading.Tasks.Task task, bool asyncWrite)
System.Data.dll!System.Data.SqlClient.SqlCommand.RunExecuteReader(System.Data.CommandBehavior cmdBehavior, System.Data.SqlClient.RunBehavior runBehavior, bool returnStream, string method)
System.Data.dll!System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior behavior, string method)
EntityFramework.dll!System.Data.Entity.Infrastructure.Interception.InternalDispatcher<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor>.Dispatch<System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>, System.Data.Common.DbDataReader>(System.Data.Common.DbCommand target, System.Func<System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>, System.Data.Common.DbDataReader> operation, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext, System.Action<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>> executing, System.Action<System.Data.Entity.Infrastructure.Interception.IDbCommandInterceptor, System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext<System.Data.Common.DbDataReader>> executed)
EntityFramework.dll!System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(System.Data.Common.DbCommand command, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext interceptionContext)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlVersionUtils.GetServerType(System.Data.Common.DbConnection connection)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.QueryForManifestToken(System.Data.Common.DbConnection conn)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken.AnonymousMethod__9(System.Data.Common.DbConnection conn)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.UsingConnection.AnonymousMethod__32()
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute.AnonymousMethod__0()
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute<object>(System.Func<object> operation)
EntityFramework.SqlServer.dll!System.Data.Entity.SqlServer.SqlProviderServices.GetDbProviderManifestToken(System.Data.Common.DbConnection connection)
EntityFramework.dll!System.Data.Entity.Core.Common.DbProviderServices.GetProviderManifestToken(System.Data.Common.DbConnection connection)
EntityFramework.dll!System.Data.Entity.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(System.Data.Entity.Core.Common.DbProviderServices providerServices, System.Data.Common.DbConnection connection)
mscorlib.dll!System.Collections.Concurrent.ConcurrentDictionary<System.Tuple<System.Type, string, string>, string>.GetOrAdd(System.Tuple<System.Type, string, string> key, System.Func<System.Tuple<System.Type, string, string>, string> valueFactory)
EntityFramework.dll!System.Data.Entity.Utilities.DbConnectionExtensions.GetProviderInfo(System.Data.Common.DbConnection connection, out System.Data.Entity.Core.Common.DbProviderManifest providerManifest)
EntityFramework.dll!System.Data.Entity.DbModelBuilder.Build(System.Data.Common.DbConnection providerConnection)
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.CreateModel(System.Data.Entity.Internal.LazyInternalContext internalContext)
EntityFramework.dll!System.Data.Entity.Internal.RetryLazy<System.Data.Entity.Internal.LazyInternalContext, System.Data.Entity.Infrastructure.DbCompiledModel>.GetValue(System.Data.Entity.Internal.LazyInternalContext input)
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
EntityFramework.dll!System.Data.Entity.Internal.LazyInternalContext.GetObjectContextWithoutDatabaseInitialization()
EntityFramework.dll!System.Data.Entity.Database.UseTransaction(System.Data.Common.DbTransaction transaction)
在会话的Open
方法中创建交易:
public virtual void Open(IsolationLevel isolation, string user)
{
ValidateState(false);
this.m_user = user;
this.m_connection = this.m_database.CreateConnection();
this.m_connection.Open();
if (IsolationLevel.Unspecified != isolation)
{
this.m_transaction = this.m_connection.BeginTransaction(isolation);
}
}
然后这个方法在支持EF的类中过度:
public override void Open(System.Data.IsolationLevel isolation, string user)
{
if (isolation == System.Data.IsolationLevel.Unspecified)
{
throw new InvalidOperationException("Isolation level 'Unspecified' is not supported");
}
base.Open(isolation, user);
this.DbContext = this.DbContextFactory.CreateContext<TContext>(this.Connection);
this.DbContext.Database.UseTransaction(this.Transaction);
}
答案 0 :(得分:3)
在初始化期间,DdContext
将确定 SQL Server 的版本。此过程将尝试连接到底层服务器并使用提供的连接查询 select cast (serverproperty ('EngineEdition') as int)
,或者在没有该连接的情况下,从配置的连接字符串创建一个新的连接。
这只会发生一次,在第一次初始化之后。
因此,如果您在应用程序生命周期中第一次使用 DbContext
是在事务中,这将导致 DbContext
尝试使用此连接并导致观察到的错误。
如果您确保第一次非事务性调用 DbContext
(用于查询的DbContext
构造和用法),您将避免这种行为。
private static object _syncRoot = new object();
private static bool _Initialized = false;
private MyDbContext(string connectionString) : base(connectionString)
{
Database.SetInitializer<MyDbContext>(null);
}
private MyDbContext(DbTransaction dbTransaction) : base(dbTransaction.Connection, false)
{
Database.SetInitializer<MyDbContext>(null);
Database.UseTransaction(dbTransaction);
}
public static MyDbContext Factory()
{
return new MyDbContext(Tools.GetDefaultConnectionString());
}
public static MyDbContext Factory(DbTransaction dbTransaction)
{
if(_Initialized==false)
{
lock(_syncRoot)
{
if(_Initialized==false)
{
using (MyDbContext tempDbContext = new MyDbContext(dbTransaction.Connection.ConnectionString))
using (System.Data.Common.DbTransaction temptransaction = tempDbContext.BeginTransaction())
{
var mySampleData = tempDbContext.OneRowTable.AsNoTracking().ToList();
temptransaction.Commit();
_Initialized = true;
}
}
}
}
MyDbContext finalContext = new MyDbContext(dbTransaction);
return finalContext;
}
这是一种解决方案,适用于您不能或不想在您的软件中使用 TransactionScope
的情况。
答案 1 :(得分:1)
问题出现在第一次访问底层 ObjectContext 时(尝试设置事务访问底层上下文)。使用 TransactionScope 很好;这个问题是在使用已经关联本地事务的连接时出现的。
虽然有点难看,但像下面这样的方法确实可以解决这个问题。确定 notCreateYet
等具体实现细节留作练习。
if (notCreatedYet)
{
// Create a second session/connection to not touch the incoming connection.
// Using a TransactionScope works and proper enlistment is done.
// Attempts to open a manual transaction prior to creation will fail.
using (new TransactionScope(TransactionScopeOption.RequiresNew))
using (var initConn = new SqlConnection(connection.ConnectionString))
{
initConn.Open();
var tempContext = this.DbContextFactory.CreateContext<TContext>(connection);
// Touch the object context.
if (tempContext is IObjectContextAdapter contextAdapter)
{
_ = contextAdapter.ObjectContext;
}
}
}
// Then later on this is fine.
this.DbContext = this.DbContextFactory.CreateContext<TContext>(connection);
this.DbContext.Database.UseTransaction(transaction);