I try to understand how EF creates DB requests from object manipulations in code. My test scenario is simple:
using(var context = new Context())
{
var entity = context.Entities.First();
entity.A = "TST";
entity.B = "WrongValue";
context.SaveChanges();
}
My idea is to test, how EF deal with transaction.
Change A with correct value and change B with wrong value (non existing FK)
I track what happening in SQL Server DB.
I execute code and nothing change in DB, that was expected.
Strange part is that there are two independant SQL request and I don't understand how EF revert first one.
Both Entity Framework and EntityFramework core code are open source. You can check the code at
Entity Framework - Link
Entity Framework Core - Link
If you see the internal code of Save method (pasted the code snapshot below) then you can validate that it internally it creates a transaction if an external transaction is not provided.
internal int SaveChangesInternal(SaveOptions options, bool executeInExistingTransaction)
{
AsyncMonitor.EnsureNotEntered();
PrepareToSaveChanges(options);
var entriesAffected = 0;
// if there are no changes to save, perform fast exit to avoid interacting with or starting of new transactions
if (ObjectStateManager.HasChanges())
{
if (executeInExistingTransaction)
{
entriesAffected = SaveChangesToStore(options, null, startLocalTransaction: false);
}
else
{
var executionStrategy = DbProviderServices.GetExecutionStrategy(Connection, MetadataWorkspace);
entriesAffected = executionStrategy.Execute(
() => SaveChangesToStore(options, executionStrategy, startLocalTransaction: true));
}
}
ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
return entriesAffected;
}
So your below code will internally wrapped inside a transaction which you can validate in SQL Profiler..
using(var context = new Context())
{
var entity = context.Entities.First();
entity.A = "TST";
entity.B = "WrongValue";
context.SaveChanges();
}
However, SQL profiler does not start logging transaction so you need to configure that in trace setting. See the below screenshot of SQL profiler new Trace setting, here, I have checked Show All events. After that Transaction category is being displayed. You can subscribe for Begin Tran, Commit Tran and Rollback Tran events to validate transaction statements. When you will run your scenario, you can see that Begin and Rollback should be logged.
Related
We are splitting our large DbContext into smaller contexts, each one taking care of a small domain bounded context. The contexts save operations are orchestrated by a unit of work as shown below.
The domain has two bounded contexts, Partners and Employees. The Unit Of Work manages two DbContexts, PartnerContext and EmployeeContext. We run all save operations within a transaction to ensure the operation is atomic.
A simplified version of the problem is available on github
public class UnitOfWork {
public Task SaveChanges(){
// EmployeeContext begins a transaction and shares it with other contexts
var strategy = employeeContext.Database.CreateExecutionStrategy();
return strategy.ExecuteAsync(async () =>
{
await using var transaction = await employeeContext.Database.BeginTransactionAsync();
await partnerContext.Database.UseTransactionAsync(transaction.GetDbTransaction());
await partnerContext.SaveChangesAsync();
await employeeContext.SaveChangesAsync();
await transaction.CommitAsync();
});
}
}
The following code works fine. Changes are all executed within one single transaction
var unitOfWork = new unitOfWork();
... perform updates to both contexts
await unitOfWork.SaveChanges();
However, the following code throws when attempting to save changes a second time.
var unitOfWork = new unitOfWork();
... perform updates to both contexts
await unitOfWork.SaveChanges(); <-- Work fine
... doing a bit more work
await unitOfWork.SaveChanges(); <-- Crashes
The line above fails with The connection is already in a transaction and cannot participate in another transaction. error message.
The resulting SQL log is:
**** The first save operation logs start here:
SET NOCOUNT ON;
INSERT INTO [Person] ([Discriminator], [ManagerId], [Name])
VALUES (#p0, #p1, #p2);
SELECT [Id]
FROM [Person]
WHERE ##ROWCOUNT = 1 AND [Id] = scope_identity();
Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Committing transaction.
Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Disposing transaction.
**** The second save operation logs start here:
Microsoft.EntityFrameworkCore.Database.Connection: Debug: Opening connection to database 'EF_DDD' on server 'localhost'.
Microsoft.EntityFrameworkCore.Database.Connection: Debug: Opened connection to database 'EF_DDD' on server 'localhost'.
Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Beginning transaction with isolation level 'Unspecified'.
Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Began transaction with isolation level 'ReadCommitted'.
Microsoft.EntityFrameworkCore.Database.Transaction: Debug: Disposing transaction.
Would anyone know the reason behind the second unitOfWork.SaveChanges() complaining about an open transaction despite the fact the first transaction was committed and disposed (as you can see from the logs above)?
Update
I removed all async code and the execution strategy (retry) to narrow the issue down, the code now looks like this:
static void Main(string[] args)
{
var employeeContext = new EmployeeContext(ConnectionString);
var partnersContext = new PartnersContext(employeeContext.Database.GetDbConnection());
var unitOfWork = new UnitOfWork();
unitOfWork.Update(employeeContext, partnersContext, 1);
unitOfWork.Update(employeeContext, partnersContext, 2);
}
public class UnitOfWork
{
public void Update(EmployeeContext employeeContext, PartnersContext partnerContext, int count)
{
partnerContext.Partners.Add(new Partner($"John Smith {count}"));
employeeContext.Persons.Add(new Person() { Name = $"Richard Keno {count}" });
using var trans = employeeContext.Database.BeginTransaction();
partnerContext.Database.UseTransaction(trans.GetDbTransaction());
partnerContext.SaveChanges();
employeeContext.SaveChanges();
trans.Commit();
}
}
The first one goes through, and the database is updated, but the second call fails with the error below.
Update 2
Using TransactionScope instead of BeginTransaction seems to work. The following code works and updates the database accordingly.
var strategy = employeeContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
partnerContext.Partners.Add(new Partner($"John Smith {count}"));
employeeContext.Persons.Add(new Person() { Name = $"Richard Keno {count}" });
await partnerContext.SaveChangesAsync();
await employeeContext.SaveChangesAsync();
scope.Complete();
});
Looks like you are hitting some internal EF Core 3 implementation defect/bug which has been fixed down the road, because the issue does not reproduce in the latest at this time EF Core 6.0 (but does reproduce in EF Core 3.1).
The problem is with cleanup of the shared underlying db connection and db transaction. It can be solved (which also helps the future EF Core versions) by disposing the EF Core transaction wrapper (IDbContextTransaction) returned by the UseTransaction{Async} call, e.g.
using var trans2 = await partnerContext.Database.UseTransactionAsync(transaction.GetDbTransaction());
or
using var trans2 = partnerContext.Database.UseTransaction(trans.GetDbTransaction());
Here is a very simplistic example of my current state:
using(var scope = new TransactionScope(TransactionScopeOption.Required, transactionScopeTimeout, TransactionScopeAsyncFlowOption.Enabled))
{
const string ConnectionString="server=localhost;username=;password=;enlist=false;Initial Catalog=blubber"
using(var myContext = new MyContext(ConnectionString))
{
var myTestObject = new TestObjectBuilder().WithId(1).Build();
myContext.Add(myTestObject);
myContext.SaveChanges();
}
// ...
using(var myContext = new MyContext(ConnectionString))
{
var myObj = myContext.MyObjects.Single(s => s.Id == 1); // This is the same object as above
}
}
I have a TransactionScope which will be used here, but in my connectionstring I explicitly say I don't wanna enlist my Entity Framework (6.2) Transactions.
The current behavior is that on the Single(s => s.Id == 1) Expression I get an error after 30 seconds (the default timeout) that the element could not be found.
First of all: Why do I not get a timeout-Exception or any SqlException? Second: Why is my data-row locked in the database?
Also via Sql Server Management Studio I can not query that exact row (only with the NOLOCK hint).
If I remove the TransactionScope or set the ISOLATION LEVEL to READ UNCOMMITED before the Single query everything works fine.
Also because of the Dispose of the transaction-scope at the end all data will be removed / rollbacked which also should not happen if I didn't enlist to this AmbientTransaction.
So my expected behavior is, that I get no lock and my data is persisted even the transactionscope is disposed and rollbacked. EF should ignore the transactionscope here.
Do I miss something critical here?
EDIT: I tried the same thing with NHibernate and here it works like a charm.
Will the below code rollback the changes if there are any exception while saving?
using (SampleEntities context = new SampleEntities())
{
//Code Omitted
context.EmpPercAdjustments.AddRange(pp);
context.SampleJobs.AddRange(sampleJobs);
context.SaveChanges();
}
Or
Do I need to use transaction?
using (SampleEntities context = new SampleEntities())
{
//Code Omitted
using (System.Data.Entity.DbContextTransaction tranc = context.Database.BeginTransaction( ))
{
try
{
context.EmpPercAdjustments.AddRange(pp);
context.SampleJobs.AddRange(sampleJobs);
context.SaveChanges();
tranc.Commit();
}
catch (Exception ee)
{
tranc.Rollback();
}
}
}
Are there any advantages of using one over the others?
Yes it will rollback correctly.
In this case, you do not need to run an explicit transaction, because there is one already created by Entity Framework.
Creating a transaction by calling context.Database.BeginTransaction() is good, if you want f.e. to get the Id of just inserted record, something like this:
using (SampleEntities context = new SampleEntities())
{
using (System.Data.Entity.DbContextTransaction trans = context.Database.BeginTransaction( ))
{
context.SampleJobs.Add(newJob);
context.SaveChanges();
var jobId = newJob.Id;
//do other things, then commit or rollback
trans.Commit();
}
}
In this case, after calling SaveChanges(), the changes made on context objects are applied (so you can read database generated Id of added object in your scope), but they still have to be commited or rolled back, because changes are only dirty written.
Defining an explicit transaction can also be useful, if you have multiple methods that can modify context objects, but you want to have a final say, if changes they made will be all commited or not.
I have just read about TransactionScope. It is very good and informative.
First of all, I was wondering if I really need transactions in MVC 4 / EF 6+. The reason for that is we always invoke DbContext.SaveChanges() to save changes. I'm wondering if SaveChanges() is something that simulates transaction close meaning if I invoke SaveChanges() I commit a transactions.
On the other hand, if I need transactions, then how to implement TransactionScope in MVC / EF applications. My scenario is something similar to the steps below:
I save valid record in database
I save a copy of an old and a new record in another table which is sort of archived version of the original table
I save user's activity in another table
I also provided code. As you can see if something goes wrong in the middle I have inconsistent data. I would be grateful for some examples on how to use TransactionScope. I may also need more to save in other tables. I would like to be certain either I save everything or nothing, such that I save everything if transaction is OK or roll back anything that happened up to the problem.
Thanks.
[HttpPost]
public ActionResult Edit(ApplicationViewModel viewmodel)
{
using(MyDbCOntext dbContext = new MyDbContext())
{
if(!MoselState.IsValid)
return View(application);
// Copy old data from database and assign to an object
ApplicationArchive applicationOld = CopyApplicationFromDB(db, viewmodel.ApplicationID);
// Update model
if (TryUpdateModel(applicationNew), null, null, new string[] { "ApplicationID" })
{
try
{
dbContext.Entry(userToUpdate).State = EntityState.Modified;
dbContext.SaveChanges();
// Archive old application
ApplicationArchive applicationNew = CopyApplicationFromDB(db, viewmodel.ApplicationID);
try
{
dbContext.ApplicationsArchive.Add(applicationOld);
dbCOntext.ApplicationsArchive.Add(applicationNew);
dbContext.SaveChanges();
// Register user activity
string username = GetUserNameFromCookie();
UserActivity useractivity = new UserActivity() { UserName = username, activity = "edit", Table = "application" };
try
{
dbContext.UserActivities.Add(useractivity);
dbContext.SaveChanges();
return RedirectView("Index");
}
}
}
catch
{
ModelState.AddModelError("", "Cannot update this application");
}
}
//
return View(application);
}
}
You need to wrap your database operation within a DbContextTransaction. See this link for Entity Framework transaction examples:
https://msdn.microsoft.com/en-us/data/dn456843.aspx
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.