Entity framework Breeze-EF、TransactionScope和BeforeSaveEntities中的开放连接
通过查看Breeze源代码,我看到ContextProvider在最终调用BeforeSaveEntities之前打开了一个连接 问:我想知道原因是什么?因为在某些情况下,这将导致不必要的事务升级。解释如下 ContextProvider.cs:Entity framework Breeze-EF、TransactionScope和BeforeSaveEntities中的开放连接,entity-framework,transactions,breeze,Entity Framework,Transactions,Breeze,通过查看Breeze源代码,我看到ContextProvider在最终调用BeforeSaveEntities之前打开了一个连接 问:我想知道原因是什么?因为在某些情况下,这将导致不必要的事务升级。解释如下 ContextProvider.cs: private void OpenAndSave(SaveWorkState saveWorkState) { OpenDbConnection(); // ensure connection is available for B
private void OpenAndSave(SaveWorkState saveWorkState) {
OpenDbConnection(); // ensure connection is available for BeforeSaveEntities
saveWorkState.BeforeSave();
SaveChangesCore(saveWorkState);
saveWorkState.AfterSave();
}
protected override void OpenDbConnection() {
var ec = ObjectContext.Connection as EntityConnection;
if (ec.State == ConnectionState.Closed) ec.Open();
}
EFContextProvider.cs:
private void OpenAndSave(SaveWorkState saveWorkState) {
OpenDbConnection(); // ensure connection is available for BeforeSaveEntities
saveWorkState.BeforeSave();
SaveChangesCore(saveWorkState);
saveWorkState.AfterSave();
}
protected override void OpenDbConnection() {
var ec = ObjectContext.Connection as EntityConnection;
if (ec.State == ConnectionState.Closed) ec.Open();
}
如果在事务中保存,连接已打开的事实会导致事务升级为分布式事务,如果在BeforeSaveEntities中创建另一个DbContext以在数据库中执行其他保存操作(如审核或任何其他我们想要执行的工作)并尝试将其登记到当前事务中
为了澄清我想说的话:
1) 在非breeze场景中,以下代码不会将事务升级为分布式事务:
using(TransactionScope ts1 = new TransactionScope(TransactionScopeOption.Required))
{
using(TransactionScope ts2 = new TransactionScope(TransactionScopeOption.Required))
{
MyContext2.SaveChanges(); /* Opens & closes a connection */
ts2.Complete();
}
MyContext1.SaveChanges(); /* Opens & closes a connection */
ts1.Complete();
}
2) 对于breeze,如果我总结代码的操作顺序,我们会得到:
// Breeze opens the connection
OpenDbConnection(); /* Opens a connection for the breeze context */
// Breeze creates a TransactionScope
using(TransactionScope ts1 = new TransactionScope(TransactionScopeOption.Required))
{
// BeforeSaveEntities is called
// In BeforeSaveEntities, we decide to create/update/delete some other entities,
// but want these operations to be part of the same transaction.
// So, we create our own context, do our work and save it with a TransactionScope.
using(TransactionScope tsInBeforeSaveEntities = new TransactionScope(TransactionScopeOption.Required))
{
// ISSUE IS HERE:
// The following code with cause the transaction to be promoted to a
// distributed transaction, because another connection is already open by breeze.
MyContextInBeforeSaveEntities.SaveChanges(); /* Opens & closes a connection */
tsInBeforeSaveEntities.Complete();
}
// BeforeSaveEntities terminates
// Breeze saves the changes & complete its transaction.
[BreezeContext].SaveChanges(); /* Uses the already open connection */
ts1.Complete();
}
如果Breeze在调用BeforeSaveEntities之前没有调用OpenDbConnection(),那么就不会出现升级事务的问题
我防止事务升级的解决方法是在重写BeforeSaveEntities时关闭并重新打开Breeze的连接,但这有点令人讨厌
protected override bool BeforeSaveEntity(EntityInfo entityInfo)
{
ObjectContext.Connection.Close();
// Create my DbContext, my TransactionScope and call SaveChanges
ObjectContext.Connection.Open();
return base.BeforeSaveEntity(entityInfo);
}
我不完全确定我是否理解您的用例或DTC升级的原因。如果您使用的是完全相同的连接字符串,并且使用的是最新版本的SQL Server或Oracle(注意:像SQL Server 2005这样的旧数据库确实存在一些意外DTC升级问题),则不应进行DTC升级。或者我不明白你的问题
在save操作开始之前创建和打开连接的理由是确保在BeforeSave和AfterSave方法中完成的任何“额外”工作与save的其余部分属于同一事务。这是非常深思熟虑的。不过,也许我没抓住你的重点 如果使用默认构造函数创建一个新的
DbContext
,它将创建一个新的DbConnection
。由于您同时拥有两个数据库连接,因此TransactionScope
会将其升级为分布式事务
解决方案是使用相同的DbConnection创建第二个DbContext。EFContextProvider
的EntityConnection
属性允许:
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(
Dictionary<Type, List<EntityInfo>> saveMap)
{
var secondContext = new MyDbContext(EntityConnection);
// use secondContext for business rule validations,
// e.g. compare new values to existing values
}
保存实体之前的受保护覆盖字典(
字典(保存映射)
{
var secondContext=新的MyDbContext(EntityConnection);
//使用secondContext进行业务规则验证,
//例如,将新值与现有值进行比较
}
这样,就使用了相同的底层DbConnection,因此不会发生分布式事务
有一点关于DBContext是如何由Breeze创建的