Let's consider two scenarios.
1. Scenario no. 1. In this scenario we have two lifecycles of the page:
ObjectContainer cont = createObjectContainer(); // get a new container in some way |
IObjectScope scope = globalScope; // let's assume that this scope is stored somewhere in the Page |
object theObject = getObject(scope, ..); // get an object from the database |
cont.CopyFrom(scope, "MyName", theObject, null);// copy this object to the container |
Page.Session["MyContainer"] = cont; // store the container in a session variable |
// here goes some code that copies theObject to the Page's controls |
// restore the container from the session variable |
ObjectContainer cont = Page.Session["MyContainer"] as ObjectContainer; |
IObjectScope scope = globalScope; |
scope.Transaction.Begin(); |
cont.Transaction.Begin(); |
object theObject = cont.NamedList("MyName")[0]; // restore the object from the container |
// some code that updates theObject using client's data |
cont.Transaction.Commit(); // *** at this point the object in the database is unchanged yet |
cont.CopyTo(scope, ObjectContainer.Verify.Changed); |
scope.Transaction.Commit(); // the changes go to the database |
2. Scenario no. 2.
This scenario is nearly identical to the first one - with one exception: all this happens in a single lifecycle of the Page. In this case committing the container's transaction (the line marked with ***) saves changes to the database. This save, for some reasons, additionally changes some data in the database. And because of this, the following: cont.CopyTo(scope,....) fails (OptimisticVerificationFails).
What should I do to be able to use this code in both scenarios?
Regards
Tomasz
16 Answers, 1 is accepted
You should use ObjectContainer.CommitChanges. This returns an update script for your container that you can send back and Apply. Now the container has the real content from the DB and you can continue.
Jan Blessenohl
the Telerik team
As I understand, you suggest something like this:
ObjectContainer cont = Page.Session["MyContainer"] as ObjectContainer; |
IObjectScope scope = globalScope; // there is no active scope's transaction now |
cont.Transaction.Begin(); |
object theObject = cont.NamedList("MyName")[0]; |
// ... |
cont.Transaction.Commit(); // below lines instead of cont.CopyTo and scope.Transaction.Commit |
ObjectContainer.ChangeSet chSet = ObjectContainer.CommitChanges(cont.GetChanges(ObjectContainer.Verify.Changed), ObjectContainer.Verify.Changed, scope, true, true); |
cont.Apply(chSet); |
In the first scenario it works - but CommitChanges begins and commits the scope's transaction. It's not good because I want to do some additional database updates in the same transaction (e.g. CommitChanges changes some object's data that should trigger modification of access rights to this object). In this case it is impossible - I must do it in a separate transaction.
In my original scenario this part of the code is like this:
cont.CopyTo(scope, ObjectContainer.Verify.Changed); |
additionalSave(scope, ...); // |
scope.Transaction.Commit(); |
In the second scenario - problem remains exactly the same: cont.Transaction.Commit changes data in the database and CommitChanges fails with OptimisticVerificationFails.
The main goal of the presented code is to implement the algorithm:
- an object is presented in a preview mode
- the user clicks Edit; this loads the object from the database again and stores it in a container (that is stored in a session variable); then the page is switched to the edit mode
- the user enters some changes and clicks Save; this retrieves the object from the container, applies the changes and saves it to the database - being able to check whether the object hasn't been modified meantime.
The second scenario is for implementing some functions that change the displayed object while in preview mode (e.g. a button "Change status to 'Approved'"). In this case I must check out this object, do changes and check it in - and the best way to do it is to use the existing methods of switching to the edit mode and saving the object to the database.
Regards
Tomasz
Let me explain what the container know. To fulfill the optimistical concurrency control the container keeps stamps of all objects in the container. If you change the object in the database twice without refreshing the container stamps this will always lead to a CC exception. After the first commit the content of the container must be refreshed. This can be done by using CommitChanges and Apply or you can just call copyFrom again for all objects that you have in the container, or just start with a new container for the second cycle.
Greetings,
Jan Blessenohl
the Telerik team
Hi,
All you've written is OK and clear. But first of all, you haven't answered my questions. The first question is:
Whether I've correctly understood your intention, whether the sequence of statements:
- start the container's transaction
- update my object inside the container
- commit this transaction
- commit changes to the scope
- apply changes back to the container
is correct?
The next, more important question is:
Why committing the container's transaction doesn't update the database in the first scenario (where creating and filling the container is done in one lifecycle of the page and updating the container and the database is done in another lifecycle) but DOES update the database in the second scenario, where all this is done during the same lifecycle?
That is the core question.
Regards
Tomasz
1. Yes.
2. My guess is that you are changing the object twice once in the container, once in the scope. In the CopyTo we are now comparing if the original values of the object in the container are the same as the values of the object in the scope. If you did some changes to the object in the scope there is a conflict that we are reporting.
Kind regards,
Jan Blessenohl
the Telerik team
I know perfectly what is the reason of the raised exeception (see my first post). But the question is not about it - the question is:
Why commiting container's transaction in the first scenario doesn't save changes to the database but in the second scenario it DOES?
My suspection is that in the first case the container is absolutely off-line (because all scopes that existed when it was created vanished at the end of the first lifecycle of the page) but in the second one knows this and that, tries to be a clever boy :-), finds the scope that contains the modified object and uses this scope to save data to the database.
I'd like to repeat, that the only difference between the scenarios is that the first one is performed in two subsequent lifecycles of a web page and the second one is performed in a single lifecycle.
I'm pretty sure that I must found some kind of workaround of this issue (e.g. not using (in the second scenario only) a container but using directly the scope) but I'd like to know not only what happens in my application but also - why.
Regards
Tomasz
The container is always absolutely disconnected. The only difference I can see is that it is serialized in one case.
I am a little confused now. You said that you see an OptimisticVerificationException that prevents the database update. Is the exception thrown in the CopyTo or the scope.Transaction.Commit? Which object is bound to the exception? The exceptions has a ConflictingObject method where you can find out which object has been changed. Did you change this object in the scope and in the container?
Sincerely yours,
Jan Blessenohl
the Telerik team
The problem is not - why the exception is raised. It is raised because "container.Transaction.Commit()" saves data to the database and while saving the data are additionally modified by an update trigger.
The problem is: why "container.Transaction.Commit()" saves data to the database? And why it once does it but another time - doesn't?
Regards
Tomasz
The container.Transaction.Commit does never make a commit to the database, it sets only a flag on the container itself that you are allowed to call copyTo or CommitChanges. The changes are stored inside the db only if you call scope.Transaction.Commit or container.CommitChanges.
Regards,
Jan Blessenohl
the Telerik team
OK, but I have seen database changing after container.Transaction.Commit().
I must make a carefull review of my code and, maybe, prepare some simplified but complete example. But it will take some time.
Regards
Tomasz
You should have access to the OpenAccess sources. If you set a breakpoint in OpenAccessPersistenceManagerImp.InternalCommit() you should get an idea when we are writing.
Best wishes,
Jan Blessenohl
the Telerik team
well, I've got back to the problem after some break :-)
You have written that "The container.Transaction.Commit does never make a commit to the database". But if it's the property DBConnection is filled and AutoSync is set to True - it does. And this is the point:
- I've been using the ObjectContextProvider class and its GetNewObjectContainer method (OK, I agree that it's my fault; I guess it is designed not for this user case)
- this method is setting these two properies as mentioned
- when used in two lifecycles scenario - in the second one the db connection is lost, of course, and therefore commiting the container does not save data to the database
- when used in a single lifecycle - the db connection is still active and AutoSync = True forces saving data to the database
I've replaced GetNewObjectContainer method with a plain "new ObjectContainer()" and the single lifecycle scenario has started to work as expected.
Regards
Tomasz
Ups, you are right, DBConnection and autosync are artifacts that are not well tested. The recommended way is to use the explicit API:
ObjectContainer.CommitChanges()
or
container.CopyTo()
This helps you also to see the exact places where the sync is done.
Greetings,
Jan Blessenohl
the Telerik team
I think it's not the same topic and you should start a new thread.
But this is the real problem and I'm also interested, how to solve it.
Regards
Tomasz
Please have a look in your other thread located here. I suggest we continue the discussion there as this thread is not really related to the question itself.
Greetings,Petar
the Telerik team