i am trying to retrieve data from database via nhibernate's CreateSQLQuery on a stored proceduure. Something like the following code.
then I am basically doing a session transaction commit, however the commit throws an "cannot update" exception. It is trying to execute a update statement on CustomEntityDao.
const string selectSQL = "EXEC GetDataSP #Id = :Id";
var query = Session.CreateSQLQuery(selectSQL);
query.SetString("Id", "10");
query.AddEntity(typeof (CustomEntityDao));
var entityList = query.List<CustomEntityDao>();
try
{
Session.Transaction.Commit();
}
catch (Exception ex)
{
throw ex;
}
My question is why are the entities been treated as modified, as you can see in the code I am only doing a query.
May be there is something else going on your code, what I can suggest is use NHibernate Profiler a trial version of which can be downloaded from www.nhprof.com and monitor the SQL commands that are being fired and notice what objects are being retrieved.
Also I dont understand why are you committing the transaction in the first place.
To solve this particular problem you could always use NHibernates StatelessSession which doesnt track entities or you can also use Session.Evict and ask Nhibernate to stop tracking particular objects.
Related
Execute the following server code, and then check the promotion table and task table in the database. The related fields have been updated correctly, which indicates that the transaction has been successfully committed.
using (ITransaction tx = session.BeginTransaction())
{
try
{
Promotion p = session.Get<Promotion>(request.PromotionId);
p.Status = PromotionStatus.Canceled;
foreach (Task task in p.Tasks)
{
if (task.AnnounceStatus == TaskAnnounceStatus.New)
{
task.AnnounceStatus = TaskAnnounceStatus.PromotionCanceled;
task.CancelTime = DateTime.Now;
//session.Update(task);
}
}
tx.Commit();
}
catch
{
tx.Rollback();
throw;
}
}
Then execute the following query(Query A), the data obtained is also the updated value. It looks like everything is very good.
tasks = session.Query<Task>().Where(p => p.AnnounceStatus == Model.TaskAnnounceStatus.New && p.ProcessStatus == Model.TaskProcessStatus.New).ToList();
However, if I execute a query on the task using the following code before committing the transaction, the result of the above query(Query A) will get the old unmodified value. At the same time, what you see in the database is still the correctly updated value.
Task task = session.Get<Task>(taskId);
So I modified the first piece of code and explicitly called the update method (see the code at the comment), and everything worked fine this time.
My guess is that Nhibernate's cache is causing the above problem. I use syscache2 to manage the second-level cache, the cache was set to ReadWrite, and use sessionFacotry.getCurrentSession to manage Nhibernate's session.
Hope someone can help me explain how this works.
You execute query session.Get<Task>(taskId); first. This loads the entity in first level cache.
Then in your transaction, you Get the Promotion entity. The Task is the IEnumerable property of it. As lazy load may be, your foreach loop iterate through Task entity with ID taskID - Modifies it - Updates it - Transaction is successful. As all this is happening inside the transaction, your initial entity returned by session.Get<Task>(taskId); is not updated. It still hold the old values.
Then, you again session.Query<Task>() outside the transaction. This time, NHibernate see that the entity with same identifier is already loaded in session cache (with session.Get<Task>(taskId); query), it does not load that entity again, it simply returns the entity already in session cache. As that entity hold the old values, you see the problem.
To confirm this, put all these queries inside the transaction block and check the result.
Alternatively, manage so scope of session properly.
Understand that your ISession is your Unit Of Work; scope it carefully.
In NHibernate 3.0, FlushMode.Auto does not work when running under an ambient transaction only (that is, without starting an NHibernate transaction). Should it?
using (TransactionScope scope = new TransactionScope())
{
ISession session = sessionFactory.OpenSession();
MappedEntity entity = new MappedEntity() { Name = "Entity", Value = 20 };
session.Save(entity);
entity.Value = 30;
session.SaveOrUpdate(entity);
// This returns one entity, when it should return none
var list = session.
CreateQuery("from MappedEntity where Value = 20").
List<MappedEntity>();
}
(Example shamelessly stolen from this related question)
In the NHibernate source I can see that's it's checking whether there's a transaction in progress (in SessionImpl.AutoFlushIfRequired), but the relevant method ( SessionImpl.TransactionInProgress) does not consider ambient transactions - unlike its cousin ConnectionManager.IsInActiveTransaction, which does consider ambient transactions.
Good news. Thanks to Jeff Sternal (who nicely identified the problem) I updated https://nhibernate.jira.com/browse/NH-3583 and thanks to the NH staff, there's already a fix and a pull request so in the upcoming release 4.1.x.x this ISSUE will be fixed.
You should use an explicit NHibernate transaction always.
using (TransactionScope scope = new TransactionScope())
using (ISession session = sessionFactory.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
//Do work here
transaction.Commit();
scope.Complete();
}
I see you also wrote in the NH dev list - while this can change in the future, that's how it works now.
The answer provided by Diego does not work in the case where you have an oracle database.
(releated question). The session.BeginTransaction will fail because the connection is already part of a transaction.
Looks like we have to write some code around this problem in our application (WCF,NHibernate, Oracle), but it just feels like something that NHibernate should provide out of the box.
So if anyone has a good answer, it would be really appreciated.
For me, I don't know the reason behind, but by forcing session flush before session is disposed seemed to worked for me. e.g.
using(session)
{
//do your work
session.Flush();
}
I verified that this works with my distributed transaction, since without doing this, I always get "transaction has aborted" when TransactionScope is disposed.
Even set session.FlushMode to Commit did NOT work for me.
I have a scenario that I commonly run into. It's simple to do with a
standard ADO Transaction, but not so much with NH (that I know of).
I have 2 tables to update. The first contains profile information
(Profile) and the other (Work) contains records changes that need to
be made and the status of those changes. For each update to the
Profile table, there will be an update to the status on the Work
table.
If the update to the Profile table fails, I need to update the
status on the Work table.
If the update to the Profile table succeeds, and the update to the
Work table fails, I need to rollback the transaction.
The problem is that I don't know if the update to the Profile table
failed until I commit the transaction. I tried to do a Flush on the
Profile to catch the exception so I could write the status to the Work
table, but then my Commit fails with the exception caused from the
Profile update.
How can I handle this? In a typical ADO Transaction, my first call
will throw, but I can catch and still update the other tables in the
transaction.
Here's sort of what my code looks like - pretty standard. This is not
my actual code, so please focus on the problem, not that I'm not
disposing my transaction or closing my session ;) :
try
{
ITransaction trans = _session.BeginTransaction();
var work = _repo.GetWork();
var profile = _repo.GetProfile(work.ProfileId);
try
{
profile.UpdateWithNewValues(work);
_session.SaveOrUpdate(profile);
_session.Flush();
work.Status = "Success";
}catch{
work.Status = "Failure";
}
_session.SaveOrUpdate(work);
trans.Commit();
}catch{
trans.Rollback();
}
I realize that Flush() is not going to work, but I don't know how else
to do this.
Some clarifications needed on your requirements.
1) >> If the update to the Profile table succeeds, and the update to the Work table fails, I need to rollback the transaction
I would have thought that Work is like an audit trail update and should not have failed if Profile update works. If this is the case, then you should not rollback your transaction. However, having said this, your code already comply to this requirement.
2) >>If the update to the Profile table fails, I need to update the status on the Work table.
If update fails, then you would have rolled back your transaction. You will not be able to update the Work table unless you have two separate transactions (one for both Profile and Work (as current) and then a separate one just for Work). Does this make sense to you?
I don't see a problem with having a trans.Commit prior to the flush. Here's an example (slightly modified too look like yours):
Profile profile;
Work work;
ITransaction tx;
try
{
session.SaveOrUpdate(profile);
work.Status = "Success";
session.SaveOrUpdate(work);
tx.Commit();
}
catch (Exception) // wroh oh...
{
try
{
work.Status = "Failure";
session.SaveOrUpdate(work);
tx.Commit();
}
catch (Exception)
{
if (!tx.WasRolledBack)
{
tx.Rollback();
session.Clear();
}
throw;
}
}
finally
{
if (session.IsOpen)
{
// Whatever happened, Flush/Persist at the end.
session.Flush();
}
}
This question is a bit of a dupe, but I still don't understand the best way to handle flushing.
I am migrating an existing code base, which contains a lot of code like the following:
private void btnSave_Click()
{
SaveForm();
ReloadList();
}
private void SaveForm()
{
var foo = FooRepository.Get(_editingFooId);
foo.Name = txtName.Text;
FooRepository.Save(foo);
}
private void ReloadList()
{
fooRepeater.DataSource = FooRepository.LoadAll();
fooRepeater.DataBind();
}
Now that I am changing the FooRepository to Nhibernate, what should I use for the FooRepository.Save method? Should the FooRepository always flush the session when the entity is saved?
I'm not sure if I understand your question, but here is what I think:
Think in "putting objects to the session" instead of "getting and storing data". NH will store all new and changed objects in the session without any special call to it.
Consider this scenarios:
Data change:
Get data from the database with any query. The entities are now in the NH session
Change entities by just changing property values
Commit the transaction. Changes are flushed and stored to the database.
Create a new object:
Call a constructor to create a new object
Store it to the database by calling "Save". It is in the session now.
You still can change the object after Save
Commit the changes. The latest state will be stored to the database.
If you work with detached entities, you also need Update or SaveOrUpdate to put detached entities to the session.
Of course you can configure NH to behave differently. But it works best if you follow this default behaviour.
It doesn't matter whether or not you explicitly flush the session between modifying a Foo entity and loading all Foos from the repository. NHibernate is smart enough to auto-flush itself if you have made changes in the session that may affect the results of the query you are trying to run.
Ideally I try to use one session per "unit of work". This means one cohesive piece of work which may involve several smaller steps. If you feel that you do not have a seam in your architecture where you can achieve this, then managing the session inside the repository will also work. Just be aware that you are missing out on some of the power that NHibernate provides you.
I'd vote up Stefan Moser's answer if I could - I'm still getting to grips with Nh myself but I think it's nice to be able to write code like this:
private void SaveForm()
{
using (var unitofwork = UnitOfWork.Start())
{
var foo = FooRepository.Get(_editingFooId);
var bar = BarRepository.Get(_barId);
foo.Name = txtName.Text;
bar.SomeOtherProperty = txtBlah.Text;
FooRepository.Save(foo);
BarRepository.Save(bar);
UnitOfWork.CommitChanges();
}
}
so this way either the whole action succeeds or it fails and rolls back, keeping flushing/transaction management outside of the Repositories.
I am performing a standard update in NHibernate to a single property. However on commit of the transaction the sql update seems to set all fields I have mapped on the table even though they have not changed. Surely this can't be normal behaviour in Nhibernate? Am I doing something wrong? Thanks
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var singleMeeting = session.Load<Meeting>(10193);
singleMeeting.Subject = "This is a test 2";
transaction.Commit();
}
}
This is the normal behavior. You can try adding dynamic-update="true" to your class definition to override this behavior.
Well. yes this is normal behaviour for NHibernate. You can use generated attribute for your properties to change the behaviour. Details on Ayende's blog.
Why is this default is because with dynamics you don't get your query plan cached. And usually you don't mind that you send few more bytes over high speed network connection between your application server and database. Unless you are saving long strings where this setting is perfectly appropriate.