在多个上下文中共享EF中的连接和事务(UnintentionalCodeFirstException)

以下版本和问题作为下面的附加上下文提供。 改进的问题表述和问题可以如下:

  • 如何在不执行分布式事务的情况下,首先在EF 6.1.0数据库和.NET 4.5.2中的多个上下文之间共享事务?

为此,我看起来需要在多个上下文之间共享连接,但到目前为止我一直在寻找的代码示例和教程并没有那么富有成效。 问题似乎是如何定义连接对象和事务对象类型的function组合,以便在构造对象上下文时也构建并找到EF数据库第一个对象元数据。

也就是说,我想类似于这里的EF 6.n教程中描述的内容。 一些示例代码可能是

int count1; int count2; using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { //How to define this connection so as not to run into UnintentionalCodeFirstException? //Creating a dummy context to obtain the connectiong string like so //dummyContext.Database.Connection.ConnectionString and then using the connection will be greeted with the aforementioned exception. using(var conn = new SqlConnection("...")) { using(var c1 = new SomeEntities(conn, contextOwnsConnection: false)) { //Use some stored procedures etc. count1 = await c1.SomeEntity1.CountAsync(); } using(var c2 = new SomeEntities(conn, contextOwnsConnection: false)) { //Use some stored procedures etc. count2 = await c2.SomeEntity21.CountAsync(); } } } int count = count1 + count2; 

在示例中还有关于如何创建共享连接和事务的其他方法,但是如上所述,罪魁祸首似乎是,例如,如果我在(“…”部分)中提供connectiong字符串,以前的片段为dummyContext.Database.Connection.ConnectionString我只会得到一个例外。


我不确定我是否只是在阅读错误的来源,或者当我尝试在多个EF上下文中共享事务时,我的代码中还有其他错误。 怎么可能呢?

我已经阅读了很多关于此的其他SOpost(例如这个 )和一些教程 。 他们没有帮助。

我有一个奇怪的问题,它看起来我没有像其他教程和post中定义的构造函数重载。 也就是说,通过链接的教程链接,我无法编写new BloggingContext(conn, contextOwnsConnection: false))并使用共享连接和外部事务。

如果我写的话

 public partial class SomeEntities: DbContext { public SomeEntities(DbConnection existingConnection, bool contextOwnsConnection): base(existingConnection, contextOwnsConnection) { } } 

并像在教程中一样使用它,我从以下T4模板生成的代码中得到以下行的exception

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } 

我使用的是.NET 4.5.2和EF 6.1.0。 我从现有数据库构造了edmx并从那里生成了代码。 在这种特殊情况下,我使用任务并行线程来加载许多SQL Server 主数据服务登台表(是的,一个大型模型)并调用相关的程序(由每个表的MDS提供)。 MDS有自己的补偿逻辑,以防某些表的暂存失败,但回滚事务也应该是可行的。 看起来我的EF有一个(奇怪的)问题。

<附录:史蒂夫建议使用直接TransactionScope。 没有需要分布式事务的共享连接,这是我可以选择的选项。 然后,如果我尝试为上下文提供共享连接(教程中显示的一些选项, 这里有一个“缺少构造函数”的问题。当我定义一个时,我得到我在代码中引用的exception。总而言之,这感觉很奇怪。也许我在生成DbContext和相关类的方法上有些不对劲。

<注1:看起来根本原因与Arthur(EF开发团队)的博客文章一样。 不要错误地使用Code First 。 也就是说,在数据库第一次开发中,框架寻求连接字符串中定义的类关系映射。 在我的连接字符串中有些可疑…

您是否尝试在事务范围中包装调用?

 using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted })) { // Do context work here context1.Derp(); context2.Derp(); // complete the transaction scope.Complete(); } 

由于连接池,使用多个使用完全相同的连接字符串的EF DbContext ,在相同的事务范围下通常不会导致DTC升级(除非您在连接字符串中禁用了池),因为同一连接将被重用于它们两者(从泳池)。 无论如何,你可以像这样在你的情况下重用相同的连接(我假设你已经添加了接受DbConnection构造函数和指示上下文是否拥有连接的标志):

 using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // important - use EF connection string here, // one that starts with "metadata=res://*/..." var efConnectionString = ConfigurationManager.ConnectionStrings["SomeEntities"].ConnectionString; // note EntityConnection, not SqlConnection using (var conn = new EntityConnection(efConnectionString)) { // important to prevent escalation await conn.OpenAsync(); using (var c1 = new SomeEntities(conn, contextOwnsConnection: false)) { //Use some stored procedures etc. count1 = await c1.SomeEntity1.CountAsync(); } using (var c2 = new SomeEntities(conn, contextOwnsConnection: false)) { //Use some stored procedures etc. count2 = await c2.SomeEntity21.CountAsync(); } } scope.Complete(); } 

这有效,并且不会抛出UnintentionalCodeFirstExce‌​ption因为您传递了EntityConnection 。 此连接包含有关EDMX元数据的信息,这是数据库首先需要的信息。 当你传递普通的SqlConnection – EF不知道在哪里寻找元数据,实际上甚至不知道它应该寻找它 – 所以它立即假设你在做代码优先。

请注意,我在上面的代码中传递了EF连接字符串。 如果你有一些普通的SqlConnection ,你通过其他方式获得,在EF之外,这将无法工作,因为需要连接字符串 。 但是,它仍然可能,因为EntityConnection具有接受普通DbConnection构造函数。 但是,您应该自己传递对元数据的引用。 如果您对此感兴趣 – 我可以提供如何执行此操作的代码示例。

要确保在所有情况下都确实阻止了升级 – 禁用池(连接字符串中的Pooling=false )并停止DTC服务,然后运行此代码 – 它应该运行正常。 然后运行另一个不共享相同连接的代码,您应该观察到错误,表明升级即将发生,但服务不可用。