问题
By looking at the Breeze source code, I see that the ContextProvider opens a connection before eventually calling BeforeSaveEntities.
Question: I wonder what is the reason for that? Because in some scenarios this will cause unwanted transaction promotion. Explanations below.
ContextProvider.cs:
private void OpenAndSave(SaveWorkState saveWorkState) {
OpenDbConnection(); // ensure connection is available for BeforeSaveEntities
saveWorkState.BeforeSave();
SaveChangesCore(saveWorkState);
saveWorkState.AfterSave();
}
EFContextProvider.cs:
protected override void OpenDbConnection() {
var ec = ObjectContext.Connection as EntityConnection;
if (ec.State == ConnectionState.Closed) ec.Open();
}
If we are saving within a transaction, the fact that the connection is already open causes the transaction to be promoted to a distributed transaction if in BeforeSaveEntities we create another DbContext to do some other save operation in the database (such as auditing or any other work we want to do) and try to enlist it with the current transaction.
To clarify what I'm trying to say:
1) In a non-breeze scenario the following code doesn't promote the transaction to a distributed transaction:
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) With breeze, if I summarize the sequence of operations of the code, we get:
// 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();
}
If Breeze didn't called OpenDbConnection() before calling BeforeSaveEntities, we wouldn't have the issue of the promoted transaction.
My workaround to prevent transaction promoting is to close and re-open the connection for Breeze when I override BeforeSaveEntities, but this is kind of nasty.
protected override bool BeforeSaveEntity(EntityInfo entityInfo)
{
ObjectContext.Connection.Close();
// Create my DbContext, my TransactionScope and call SaveChanges
ObjectContext.Connection.Open();
return base.BeforeSaveEntity(entityInfo);
}
回答1:
If you create a new DbContext
using the default constructor, it will create a new DbConnection
. Since you then have two database connections in flight at the same time, that's what causes the TransactionScope
to promote it to a distributed transaction.
The solution is to create your second DbContext using the same DbConnection. The EntityConnection
property of EFContextProvider
is there to allow this:
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
}
This way, the same underlying DbConnection is used, so no distributed transaction occurs.
This SO post has a bit more about how DbContexts are created by Breeze.
回答2:
Not entirely sure I understand your use case or why DTC promotion is occurring. DTC promotion should not occur if you are using the exact same connection string and are using a recent version of SQL Server or Oracle ( Note: Older databases like SQL Server 2005 do have some issues with unintended DTC promotion). Or maybe I just don't understand your question.
The rational for creating and opening the connection before the beginning of the save operation is to insure that any "extra" work done within the BeforeSave and AfterSave methods are part of the same transaction as the rest of the save. This was very deliberate. But again, maybe I'm missing your point.
来源:https://stackoverflow.com/questions/25163189/breeze-ef-transactionscope-and-the-open-connection-in-beforesaveentities