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

本文关键字:连接 事务 UnintentionalCodeFirstException 上下文 共享 EF | 更新日期: 2023-09-27 18:05:59

上一个版本和问题在下面作为附加上下文提供。改进后的问题提法和问题可以如下:

    在EF 6.1.0数据库优先和。net 4.5.2中,如何在不做分布式事务的情况下在多个上下文之间共享事务?

看起来我需要在多个上下文之间共享一个连接,但是到目前为止,我一直在看的代码示例和教程并没有那么富有成效。问题似乎是围绕如何定义连接对象和事务对象类型的功能组合,以便在构建对象上下文时也构建和找到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;

在示例中还有其他关于如何创建共享连接和事务的方法,但正如所写的,罪魁祸首似乎是,如果我在前面的代码片段中提供连接字符串("…"部分)作为dummyContext.Database.Connection.ConnectionString,我将得到一个异常。


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

我已经读了不少关于这个(例如这个)和一些教程的其他SO帖子。他们没有帮忙。

我有一个奇怪的问题,因为它看起来我没有构造函数重载定义为在其他教程和帖子。也就是说,使用链接的教程链接,我不能编写new BloggingContext(conn, contextOwnsConnection: false))并使用共享连接和外部事务。

如果我写

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

并像在教程中一样使用它,我从下面的T4模板生成的代码

中得到一个异常
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    throw new UnintentionalCodeFirstException();
}

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

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

<注1:看起来根本原因就像Arthur (EF开发团队)在这篇博客文章中所说的那样:不要错误地使用代码优先。也就是说,在数据库优先开发中,框架寻找连接字符串中定义的类关系映射。在我的连接字符串是什么可疑的…?

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

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

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服务,然后运行此代码-它应该运行良好。然后运行另一个不共享相同连接的代码,您应该会看到提示升级即将发生但服务不可用的错误。