跨多个上下文共享EF中的连接和事务(UnintentionalCodeFirstException) [英] Sharing a connection and a transaction in EF across multiple contexts (UnintentionalCodeFirstException)

查看:166
本文介绍了跨多个上下文共享EF中的连接和事务(UnintentionalCodeFirstException)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面提供了以前的版本和问题作为补充内容。改进后的问题表述和问题如下:

The previous version and question are provided as an added context below. The improved problem formulation and question could be as follows:


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

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

For that it looks like I need to share a connection between the multiple contexts, but the code examples and tutorials I've been looking at thus far haven't been that fruitful. The problem looks like is hovering around on how to define a functioning combination of a connection object and transaction object types so that EF database first object metadata is also built and found when constructing the object contexts.

也就是说,我想做类似于EF 6.n教程此处。一些示例代码可能是

That is, I would like to do akin to what has been described in the EF 6.n tutorials here. Some example code could be

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 我会得到一个例外。

In the examples there are also other methods as to how to create a shared connection and a transaction, but as written, the culprit seem to be that if, say, I provide the connectiong string in (the "..." part) the previous snippet as dummyContext.Database.Connection.ConnectionString I'll get just an exception.

我不确定我是不是在读错误的资料或者当我尝试在多个EF上下文中共享事务时,如果我的代码中还有其他错误。

I'm not sure if I'm just reading the wrong sources or if there's something else that's wrong in my code when I try to share a transaction across multiple EF contexts. How could it be done?

我已经读过很多其他有关此的文章(例如)和一些教程。他们没有帮助。

I've read quite a few other SO posts regarding this (e.g. this) and some tutorials. They did not help.

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

I have a strange problem in that it looks I don't have the constructor overloads defined as in other tutorials and posts. That is, taking the linked tutorial link, I can't write new BloggingContext(conn, contextOwnsConnection: false)) and use a shared connection and an external transaction.

然后如果我写

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

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

and use it like in the tutorials, I get an exception from the following line from the following T4 template generated code

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

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

I'm using .NET 4.5.2 and EF 6.1.0. I'ved constructed the edmx from an existing database and generated the code from there. In this particular situation I'm using Task Parallel threads to load dozens of SQL Server Master Data Services staging tables (yes, a big model) and to call the associated procedures (provided by the MDS one per table). MDS has its own compensation logic in case staging to some of the tables fails, but rolling back a transaction should be doable too. It just looks like I have a (strange) problem with my EF.

<附录: Steve建议使用直接TransactionScope。如果没有共享连接,则需要进行分布式交易,因此我无法选择此选项。然后,如果我尝试为上下文提供共享连接(教程中显示的某些选项,请一个这里,我遇到了缺少构造函数的问题,当我定义一个时,我得到了代码中引用的异常。总的来说,这感觉很奇怪,也许我的做法有问题。有关生成 DbContext 和相关类的信息。

<Addendum: Steve suggested using straight TransactionScope. Without a shared connection that would require a distributed transaction, which isn't an option I can choose. Then if I try to provide a shared connection for the contexts (some options shown in the tutorials, one here I have the problem of "missing constructors". When I define one, I get the exception I refer in the code. All in all, this feels quite strange. Maybe there's something wrong in how I go about generating the DbContext and related classes.

<注1:就像根本原因一样,来自EF开发人员团队的亚瑟(Arthur)在此博客文章中不要错误地使用Code First 。也就是说,在数据库优先开发中,框架会寻求连接字符串中定义的类关系映射。字符串,即..?

<Note 1: It looks like the root cause is as in this blog post by Arthur (of EF developer team) Don't use Code First by mistake. That is, in database first development the framework seeks for the class-relational mappings as defined in the connection string. Something fishy in my connection string that is..?

推荐答案

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

Because of connection pool, using multiple EF DbContext which are using exactly the same connection string, under same transaction scope will usually not result in DTC escalation (unless you disabled pooling in connection string), because the same connection will be reused for them both (from the pool). Anyway, you can reuse the same connection in your case like this (I assume you already added constructor which accepts DbConnection and flag indicating if context owns connection):

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不知道在哪里寻找元数据,甚至根本不知道它应该在寻找它-所以它

This works and does not throw UnintentionalCodeFirstExce‌​ption because you pass EntityConnection. This connection has information about EDMX metadata and that is what database first needs. When you pass plain SqlConnection - EF has no idea where to look for metadata and actually doesn't even know it should look for it - so it immediately assumes you are doing code-first.

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

Note that I pass EF connection string in code above. If you have some plain SqlConnection which you obtained by some other means, outside EF, this will not work, because connection string is needed. But, it's still possible because EntityConnection has constructor which accepts plain DbConnection. However, then you should pass reference to metadata yourself. If you are interested in this - I can provide code example of how to do that.

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

To check that you indeed prevent escalation in all cases - disable pooling (Pooling=false in connection string) and stop DTC service, then run this code - it should run fine. Then run another code which does not share the same connection and you should observe error indicating escalation was about to happen but service is not available.

这篇关于跨多个上下文共享EF中的连接和事务(UnintentionalCodeFirstException)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆