I am having an issue with using TransactionScope that has has two Entity Framework ObjectContexts initialized in it, one for database X and one for database Y on the same server.
The first context "cersCtx" creates a new "Contact" object. The second context "coreCtx" attempts to find an Account.
On the call on the second context to find the account
account = coreCtx.Accounts.SingleOrDefault(p => p.Email == email && !p.Voided);
I get the following error:
The MSDTC transaction manager was unable to pull the transaction from the source transaction manager due to communication problems. Possible causes are: a firewall is present and it doesn't have an exception for the MSDTC process, the two machines cannot find each other by their NetBIOS names, or the support for network transactions is not enabled for one of the two transaction managers. (Exception from HRESULT: 0x8004D02B)
The odd thing is, when I go into MSDTC, I am seeing there is transactions that are committed but most often they are Aborting.
Anyone have any ideas on how I can resolve this issue?
Here is an abbreivated sample of code that produces this issue.
public void CreateContact(string email)
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
using (CERSEntities cersCtx = new CERSEntities())
{
Contact contact = new Contact();
contact.Email = email;
cersCtx.Contacts.AddObject(contact);
cersCtx.SaveChanges();
}
CoreModel.Account account = null;
using (CoreModel.CoreEntities coreCtx = new CoreModel.CoreEntities())
{
account = coreCtx.Accounts.SingleOrDefault(p => p.Email == email && !p.Voided);
}
scope.Complete();
}
}
Related
It is using Entity Framework Core to update database.
dbContextTransaction.Commit(); is working fine, and after this it is some file operation with bad result. And then it throws an error, so it tries to roll back using dbContextTransaction.Rollback(); but results in:
This SqlTransaction has completed; it is no longer usable.
DbContext dbContext = scope.ServiceProvider.GetService<DbContext>();
IDbContextTransaction dbContextTransaction = dbContext.Database.BeginTransaction();
try
{
IOrderDao orderDao = scope.ServiceProvider.GetService<IOrderDao>();
IItemSoldPriceDao itemSoldPriceDao = scope.ServiceProvider.GetService<IItemSoldPriceDao>();
ItemSoldPrice itemSoldPrice = new ItemSoldPrice
{
...
};
itemSoldPriceDao.AddItemSoldPrice(itemSoldPrice);
order.SoldPriceCaptured = true;
dbContext.SaveChanges();
dbContextTransaction.Commit();
//... some other file operation throws out error
throw new Exception("aaa");
}
catch (Exception ex)
{
CommonLog.Error("CaptureSoldPrice", ex);
dbContextTransaction.Rollback();
}
After a transaction is committed, it cannot be rolled back?
When using Entity Framework, explicit transactions are only required when you want to link the success or failure of operations against the DbContext with other operations outside of the scope of the DbContext. All operations within a DbContext prior to SaveChanges are already grouped within a transaction. So for instance saving entities across two or more tables within a DbContext do not require setting up an explicit transaction, they will both be committed or rolled back together if EF cannot save one or the other.
When using an explicit transaction, the Commit() call should be the last operation for what forms essentially a unit of work. It will be the last operation to determine whether everything in the transaction scope is successful or not. So as a general rule, all operations, whether Database-based, file based, or such should register with and listen to the success or failure of the transaction.
An example of using a transaction: Say we have a system that accesses two databases via two separate DbContexts. One is an order system that tracks orders and has a record for a Customer and one is a CRM that tracks customer information. When we accept a new order from a new customer we check the CRM system for a customer and create a customer record in both systems if it is someone new.
using (var orderContext = new OrderDbContext())
{
var transaction = orderContext.Database.BeginTransaction();
try
{
var order = new Order
{
// TODO: Populate order details..
}
if(customerDetails == null && registerNewCustomer) // Where customerDetails = details loaded if an existing customer logged in and authenticated...
{
using(var crmContext = new CrmDbContext())
{
crmContext.Database.UseTransaction(transaction);
var customer = new Customer
{
UserName = orderDetails.CustomerEmailAddress,
FirstName = orderDetails.CustomerFirstName,
LastName = orderDetails.CustomerLastName,
Address = orderDetails.BillingAddress
};
crmContext.Customers.Add(customer);
crmContext.SaveChanges();
var orderCustomer = new Orders.Customer
{
CustomerId = customer.CustomerId,
FirstName = customer.FirstName,
LastName = customer.LastName
}
orderContext.Customers.Add(orderCustomer);
}
}
order.CustomerId = crmContext.Customers
.Select(c => c.CustomerId)
.Single(c => c.UserName == customerDetails.UserName);
orderContext.Orders.Add(order);
orderContext.SaveChanges();
transaction.Commit();
}
catch(Exception ex)
{
// TODO: Log exception....
transaction.Rollback();
}
}
The order DB customer is just a thin wrapper of the CRM customer where we would go for all of the customer details. The CRM customer manages the Customer IDs which would correlate to an Order Customer record. This is by no means a production code type example, but rather just to outline how a Transaction might coordinate multiple operations when used.
In this way if there is any exception raised at any point, such as after a new Customer record has been created and saved, all saved changes will be rolled back and we can inspect any logged exception details along with recorded values to determine what went wrong.
When dealing with combinations of DbContext operations and other operations that we might want to support a rolling back process flow on failure then we can leverage constructs like the TransactionScope wrapper. However this should be used with caution and only in cases where you explicitly need to marry these operations rather than attempting to use the pattern as a standard across all operations. In most cases you will not need explicit transactions.
I have pretty much standard EF 6.1 'create object in a database' code wrapped in transaction scope. For whatever reason the data persists in db after the transaction fails (to complete).
Code:
using (var db = this.Container.Resolve<SharedDataEntities>()) // << new instance of DbContext
{
using (TransactionScope ts = new TransactionScope())
{
SubscriptionTypes st = this.SubscriptionType.Value;
if (st == SubscriptionTypes.Lite && this.ProTrial)
st = SubscriptionTypes.ProTrial;
Domain domain = new Domain()
{
Address = this.Address.Trim(),
AdminUserId = (Guid)user.ProviderUserKey,
AdminUserName = user.UserName,
Description = this.Description.TrimSafe(),
DomainKey = Guid.NewGuid(),
Enabled = !masterSettings.DomainEnableControlled.Value,
Name = this.Name.Trim(),
SubscriptionType = (int)st,
Timezone = this.Timezone,
Website = this.Website.TrimSafe(),
IsPrivate = this.IsPrivate
};
foreach (var countryId in this.Countries)
{
domain.DomainCountries.Add(new DomainCountry() { CountryId = countryId, Domain = domain });
}
db.Domains.Add(domain);
db.SaveChanges(); // << This is the Saving that should not be commited until we call 'ts.Complete()'
this.ResendActivation(domain); // << This is where the Exception occurs
using (TransactionScope ts2 = new TransactionScope(TransactionScopeOption.Suppress))
{
this.DomainMembership.CreateDomainUser(domain.Id, (Guid)user.ProviderUserKey, user.UserName, DomainRoles.DomainSuperAdmin | DomainRoles.Driver);
ts2.Complete();
}
this.Enabled = domain.Enabled;
ts.Complete(); // << Transaction commit never happens
}
}
After SaveChanges() exception is thrown inside ResendActivation(...) so the changes should not be saved. However the records stay in database.
There is no other TransactionScope wrapping the code that I've pasted, it's triggered by an MVC Action call.
after more investigations, turns out that something - probably Entity Framework upgrade or database update process had put
Enlist=false;
into the database connection string. That effectively stops EF from picking up Transaction Scope.
So the solution is to set it to true, or remove it, I think by default it's true
Try using the transaction from the db instance it self, db.Database.BeginTransaction() if I recall it correctly instead of using the transaction scope.
using (var ts = db.Database.BeginTransaction())
{
..
}
Assuming that db is your entity framework context.
Context class by default support transactions. but with every new instance of context class, a new transaction will be created. This new transaction is a nested transaction and it will get committed once the SaveChanges() on the associated context class gets called.
In the given code it seems, we are calling a method that is responsible for creating a domain user i.e. CreateDomainUser and perhaps that has its own context object. thus this problem.
If this is the case(that this method has own context) the perhaps we don't even need TransactionScope here. We can simply pass the same context(that we are using before this call) to the function that is creating the domain user. We can then check for result of both operations and if they are successful, we simply need to call SaveChanges() method.
TransactionScope is usually needed when we are mixing ADO.NET calls with Entity framework. We can use it with context also, but it would be an overkill as the context class already has a transaction and we can simply use the same context to manage the transaction. If this is the case in the above code then the trick to fix the issue is to let the context class know that you want to use it with your own transaction. Since the transaction gets associated with the connection object in the scope, we need to use the context class with the connection that is being associated with the transaction scope. Also, we need to let the context class know that it cannot own the connection as it is being owned by the calling code. so we can do something like:
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
}
scope.Complete();
}
See: http://msdn.microsoft.com/en-us/data/dn456843.aspx
You should be able to use TransactionScope with EF, I know our projects do. However, I think you want the EF context instantiated inside the transaction scope -- that is, I believe you need to swap the outermost / first two using statements, as the answer from #rahuls suggests.
Even if it works the other way ... if you have a service / app / business layer method that needs to update several tables, and you want those updates to be atomic, you'd need to do it this way. So for the sake of consistency (and your own sanity), I would recommend transaction scope first, context second.
I have an old ObjectContext and few new DbContext in my project (i.e. BoundedContext for different purposes).
Some time I need to commit changes from few of them in one transactions. In some cases I need to persist data from ObjectContext and DbContext.
In EF 5.0 to avoid of MSDC I write some wraper
public class ContextUnitOfWork
{
List<IContext> ContextList;
public ContextUnitOfWork()
{
ContextList = new List<IContext>();
}
public void RegisterContext(IContext Context)
{
ContextList.Add(Context);
}
public bool IsDisposed
{
get
{
return ContextList.Any(x => x.IsDisposed);
}
}
public bool HasChangedEntities
{
get
{
return ContextList.Any(x => x.HasChangedEntities);
}
}
public void Commit()
{
bool HasDbContext = ContextList.OfType<System.Data.Entity.DbContext>().Any();
try
{
if (HasDbContext)
{
ContextList.ForEach(x =>
{
if (x is System.Data.Entity.DbContext)
{
(x as System.Data.Entity.DbContext).Database.Connection.Open();
}
else if (x is System.Data.Objects.ObjectContext)
{
((System.Data.Objects.ObjectContext)x).Connection.Open();
}
});
}
using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required,
new System.Transactions.TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
ContextList.ForEach(x => x.Commit());
scope.Complete();
}
}
catch (System.Data.UpdateException uex)
{
var ErrorList = uex.StateEntries.Select(x => x.Entity).ToList();
}
finally
{
if (HasDbContext)
{
ContextList.ForEach(x =>
{
if (x is System.Data.Entity.DbContext)
{
(x as System.Data.Entity.DbContext).Database.Connection.Close();
}
else if (x is System.Data.Objects.ObjectContext)
{
((System.Data.Objects.ObjectContext)x).Connection.Close();
}
});
};
}
}
}
But in EntityFramework 6.0.1 it doesn't work. ObjectContext commit successfully, but when DbContext call SaveChanges() an Exception of type EntityException with text
"The underlying provider failed on EnlistTransaction." And Inner Expection contains {"Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool."}
Any Idea to commit contexts in one transaction and avoid MDTC exception?
You are attempting to run everything in a local transaction which is very tricky even with multiple contexts of the same type. The reason for this is that you cannot have multiple open connections with the same local transaction. And very often a new connection will be opened for the next context if the previous context is still alive. This will trigger a promotion of the local transaction to a distributed transaction.
My experience with EF is that it only re-uses the current connection when the connectionstring (the normal one inside the entityconnectionstring) is EXACTLY identical. If there is a single difference, the transaction will be promoted to a distributed transaction, which must be enabled by the system, which in your case, it is not.
Also, if you are already executing a query, and are still reading results from that query, that starting another query at the same time, will (of course) require another connection, and therefore, the local transaction will be promoted to a distributed transaction.
Can you check if the connection strings are identical? I would still be surprised if current connection is re-used though.
Does anyone know of any cases when using a transaction scope the transaction is escalated to the DTC when multiple connections are NOT open.
I am aware that if I open multiple connections(no matter what connection string) within a transaction scope that the transaction will most likely be promoted to the DTC.
Knowing this I have gone to great lengths to make sure there is only ONE connection opened within my transactions.
However, I have a client where they are getting the exception
An error has occurred. Csla.DataPortalException: DataPortal.Update failed (The underlying provider failed on Open.) ---> Csla.Reflection.CallMethodException: EditableCategory.DataPortal_Update method call failed ---> System.Data.EntityException: The underlying provider failed on Open. ---> System.Transactions.TransactionManagerCommunicationException: Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool. ---> System.Runtime.InteropServices.COMException: The transaction manager has disabled its support for remote/network transactions.
Again, I am pretty sure there is only one connection opened within the scope. Take a look.
protected override void DataPortal_Update()
{
using (System.Transactions.TransactionScope ts = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, System.Transactions.TransactionManager.MaximumTimeout))
{
//get the dal manager he knows which dal implementation to use
using (var dalMgr = DataAccess.BusinessObjectsDalFactory.GetManager())
{
//get the category dal implementation
var ecDal = dalMgr.GetProvider<DataAccess.BusinessObjectDalInterfaces.ICategoryDAL>();
//assume all the data is good at this point so use bypassproperty checks
using (BypassPropertyChecks)
{
var catData = new Models.Category { CategoryId = CategoryId, CategoryName = CategoryName, LastChanged = TimeStamp };
ecDal.UpdateCategory(catData);
TimeStamp = catData.LastChanged;
}
}
ts.Complete();
}
base.DataPortal_Update();
}
public class DalManager : Core.Sebring.DataAccess.IBusinessObjectsDalManager {private static string _typeMask = typeof(DalManager).FullName.Replace("DalManager", #"{0}");
public T GetProvider<T>() where T : class
{
var typeName = string.Format(_typeMask, typeof(T).Name.Substring(1));
var type = Type.GetType(typeName);
if (type != null)
return Activator.CreateInstance(type) as T;
else
throw new NotImplementedException(typeName);
}
public Csla.Data.DbContextManager<DataContext> ConnectionManager { get; private set; }
public DalManager()
{
ConnectionManager = Csla.Data.DbContextManager<DataContext>.GetManager();
}
public void Dispose()
{
ConnectionManager.Dispose();
ConnectionManager = null;
}
public void UpdateDataBase()
{
DatabaseUpgrader.PerformUpgrade();
}
}
public void UpdateCategory(Models.Category catData)
{
if (catData == null) return;
using (var cntx = DbContextManager<DataContext>.GetManager())
{
var cat = cntx.DbContext.Set<Category>().FirstOrDefault(c => c.CategoryId == catData.CategoryId);
if (cat == null) return;
if (!cat.LastChanged.Matches(catData.LastChanged))
throw new ConcurrencyException(cat.GetType().ToString());
cat.CategoryName = catData.CategoryName;
//cntx.DbContext.ChangeTracker.DetectChanges();
cntx.DbContext.Entry<Category>(cat).State = System.Data.EntityState.Modified;
cntx.DbContext.SaveChanges();
catData.LastChanged = cat.LastChanged;
}
}
The code for DBContextManager is available, but in short it just makes certain there is only one DBContext, and hence one connection opened. Am I overlooking something? I guess its possible that something is up with DBConextManager, so I have posted in the CSLA forums as well(DBContextManager is a part of CSLA). But has anyone run into scenarios where they are sure one connection is opened within the transaction scope and the transaction is escalated to the DTC?
Of course I cannot reproduce the exception on my local dev machine OR any of our QA machines.
Any help is appreciated.
Thanks.
Entity Framework can randomly try to open a new connection when doing transactions with System.Transactions.TransactionScope
Try adding a finally statement and dispose your transaction, also call your dbContext and manually close the connection , this will lesser the ammount of times the transaction gets escalated but it might still happen:
finally
{
cntx.Database.Connection.Close();
transaction.Dispose();
}
Its a known "bug" you can find more here :
http://petermeinl.wordpress.com/2011/03/13/avoiding-unwanted-escalation-to-distributed-transactions/
public void SomeMethod1()
{
using (TemplateEntities ctx = new TemplateEntities())
{
//do something in this ctx
}
}
public void SomeMethod2()
{
using (TemplateEntities ctx = new TemplateEntities())
{
//do something else in this ctx
}
}
public void SomeMethod()
{
using (TemplateEntities ctx = new TemplateEntities())
{
using (TransactionScope tran = new TransactionScope())
{
SomeMethod1();
SomeMethod2();
var itemToDelete= (from x in ctx.Xxx
where x.Id==1
select x).Single();
ctx.Xxx.DeleteObject(itemToDelete);
ctx.SaveChanges();
tran.Complete();
}
}
}
What happens in SomeMethod is executed in a transaction even if there are more contexts?
I am using POCO.
If you use TransactionScope with multiple ObjectContext instances the transaction will be promoted to distributed and whole operation (SomeMethod) will be handled still as atomic. But distributed transaction requires additional NT service and its dependecies. The service is called Microsoft Distributed Transaction Coordinator (MSDTC). This service has to run on all involved servers (application server and database server). In network scenario service requires some additional configuration. For communication RPC ports have to be opened in firewalls.
Ultimately the database doesn't know about data-contexts, so simply: the rules of transactions apply. Being a serializable transaction, things like read locks and key-range-locks will be issued and honoured. As always, there is a risk of complication from deadlocks, but ultimately it should work. Note that all the contexts involved should enlist as required.