以下版本和问题作为附加上下文提供。改进的问题表述和问题可以如下:
为此我看起来需要在多个上下文之间共享一个连接,但到目前为止我一直在寻找的代码示例和教程并没有那么富有成效。问题似乎是如何定义连接对象和事务对象类型的功能组合,以便在构造对象上下文时也构建和找到EF数据库第一个对象元数据。
也就是说,我想类似于EF 6.n教程here中描述的内容。一些示例代码可能是
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;
在示例中还有关于如何创建共享连接和事务的其他方法,但是如上所述,罪魁祸首似乎是,如果我提供了连接字符串("。 .." part)上一个代码段为dummyContext.Database.Connection.ConnectionString
我只会例外。
我不确定我是否只是在阅读错误的来源,或者当我尝试在多个EF上下文中共享事务时,我的代码中是否存在其他错误。怎么可能呢?
我已经阅读了相关的其他一些SO帖子(例如this)和一些tutorials。他们没有帮助。
我有一个奇怪的问题,因为它看起来我没有像其他教程和帖子中那样定义构造函数重载。也就是说,通过链接的教程链接,我无法编写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 Master Data Services登台表(是的,一个大型模型)并调用相关的程序(由每个表的MDS提供一个)。 MDS有自己的补偿逻辑,以防某些表的暂存失败,但回滚事务也应该是可行的。看起来我的EF有一个(奇怪的)问题。
<附录:史蒂夫建议使用直接的TransactionScope。没有需要分布式事务的共享连接,这不是我可以选择的选项。然后,如果我尝试为上下文提供共享连接(教程中显示的一些选项,一个here我有"缺少构造函数的问题"。当我定义一个时,我得到异常我在代码中提到。总而言之,这感觉很奇怪。也许我在如何生成DbContext
及相关类时出现了问题。
<注1:看起来根本原因与Arthur(EF开发团队)Don't use Code First by mistake的博客文章相同。也就是说,在数据库第一次开发中,框架寻求连接字符串中定义的类关系映射。在我的连接字符串中有些可疑......?
答案 0 :(得分:2)
您是否尝试在事务范围内包装调用?
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();
}
答案 1 :(得分:2)
由于连接池,使用多个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();
}
这有效,但不会抛出UnintentionalCodeFirstException
,因为您通过了EntityConnection
。此连接包含有关EDMX元数据的信息,这是数据库首先需要的信息。当您通过普通SqlConnection
时 - EF不知道在哪里查找元数据,实际上甚至不知道 应该寻找它 - 所以它立即假设您正在执行代码 - 第一
请注意,我在上面的代码中传递了EF连接字符串。如果你有一些普通的SqlConnection
,你通过其他方式获得,在EF之外,这将无法工作,因为需要连接 string 。但是,它仍然可能,因为EntityConnection
具有接受普通DbConnection
的构造函数。但是,您应该自己传递对元数据的引用。如果您对此感兴趣 - 我可以提供如何执行该操作的代码示例。
要确保在所有情况下确实阻止升级 - 禁用池(连接字符串中的Pooling=false
)并停止DTC服务,然后运行此代码 - 它应该运行正常。然后运行另一个不共享相同连接的代码,你应该观察到错误,表明升级即将发生,但服务不可用。