Here is my the test case :
[Test, Explicit]
public void SaveDeleteSaveThrowsTest()
{
Produit produit = new Produit { Libelle = "Test" };
using (ISession session = this.SessionProvider.OpenSession())
{
session.FlushMode = FlushMode.Auto;
using (ITransaction transaction = session.BeginTransaction())
{
session.SaveOrUpdate(produit);
transaction.Commit();
}
using (ITransaction transaction = session.BeginTransaction())
{
session.Delete(produit);
transaction.Commit();
}
using (ITransaction transaction = session.BeginTransaction())
{
session.SaveOrUpdate(produit);
Assert.Throws(typeof(StaleStateException), transaction.Commit);
}
}
}
The Ids are generated by HiLo.
If I assign 0 to the Id of the entity before saving it the 2nd time it works in this simple case but doesn't work in more complex scenarios where I have a one to many relation (I get the exception "collection owner not associated with session" when trying to delete the parent entity).
Is there a way to make it work ? (save, delete the save again the same entity)
Don't you use lazy loading in many-to-* relationship?
The problem is you are at first loading the entity, the close session and try to manipulate with (already detached) entity. In such a case sub-entities are proxies attached to closed session. You have to tell NHibernate to re-initialize the proxies: for each sub-entity call NHibernateUtil.Initialize.
Try Merge instead of SaveOrUpdate. It looks up the record in the database (additional select before insert or update). Note that Merge has a return value which returns the persistent instance while the given instance is still transient. You may need to clean the session or create a new session to make it work.
Try calling Session.Save or Session.Lock on the deleted produit object.
However, you should reconsider your design to avoid this problem in the first place. I would keep track of ids to be deleted in a separate collection then perform the deletes when the transaction is committed.
Related
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.
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 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 am using SaveChanges() method as given below:
objAdbContext of Database A
objBdbContext of Database B
Updating table of DB A as given below
public string SaveA()
{
//Some stuff
objAdbContext.SaveChanges();
string result=UpdateDatabaseB(some parameters)
//Some stuff
}
public string UpdateDatabaseB(some parameters)
{
//Some stuff
objBdbContext.SaveChanges();
return "Success";
}
This case Database B is not getting updated. Is it correct way of updating multiple databases?
Both are independent Databases and How to implement TransactionScope in this case?
Try this:
using (TransactionScope scope = new TransactionScope())
{
// Save changes but maintain context1 current state.
context1.SaveChanges(SaveOptions.DetectChangesBeforeSave);
// Save changes but maintain context2 current state.
context2.SaveChanges(SaveOptions.DetectChangesBeforeSave);
// Commit succeeded since we got here, then completes the transaction.
scope.Complete();
// Now it is safe to update context state.
context1.AcceptAllChanges();
context2.AcceptAllChanges();
}
This sample was taken from this blog post:
Managing Transactions with Entity Framework 4
I know that for multi part writes, I should be using transactions in nhibernate. However what about for simple read and writes (1 part) ... I've read that it's good practice to always use transactions. Is this required?
Should I do the following for a simple read ?? or can I just drop the transcaction part all togather ?
public PrinterJob RetrievePrinterJobById(Guid id)
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
var printerJob2 = (PrinterJob) session.Get(typeof (PrinterJob), id);
transaction.Commit();
return printerJob2;
}
}
}
or
public PrinterJob RetrievePrinterJobById(Guid id)
{
using (ISession session = sessionFactory.OpenSession())
{
return (PrinterJob) session.Get(typeof (PrinterJob), id);
}
}
What about for simple writes?
public void AddPrintJob(PrinterJob printerJob)
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
session.Save(printerJob);
transaction.Commit();
}
}
}
Best recommendation would be to always use a transaction. This link from the NHProf documentation, best explains why.
When we don't define our own
transactions, it falls back into
implicit transaction mode, where every
statement to the database runs in its
own transaction, resulting in a large
performance cost (database time to
build and tear down transactions), and
reduced consistency.
Even if we are only reading data, we
should use a transaction, because
using transactions ensures that we get
consistent results from the database.
NHibernate assumes that all access to
the database is done under a
transaction, and strongly discourages
any use of the session without a
transaction.
(BTW, if you are doing serious NHibernate work, consider trying out NHProf).