Grails Transactions and the Session - grails
Imagine you have the following controller in a Grails 2.5.5 application:
def index() {
bookService.method()
Book bako = Book.findById(4)
System.out.println(bako.title);
}
And inside the bookService (with Grails default transaction management) you have the following method:
class BookService
def method() {
Book bako = Book.findById(4)
System.out.println(bako.title);
// MANUAL UPDATE OF DB HAPPENS HERE
Book bako = Book.findById(4)
System.out.println(bako.title);
}
}
And that your db does have a Book with id 4 called "The Lord of The Rings".
If you then set breakpoints on all System.out.println() and, after the first findById has been executed, you manually edit the title of that object to "Harry Potter and the Goblet of Fire", I expected:
Both findById(4) in bookService.method() to read the same value, after all, they were performed in isolation in the same transaction, and if that transaction read it in that state, the second one should read it too.
The findById(4) performed in the controller to already see the new state of the object, after the manual update. After all, the first transaction has already commited, and this second find, even if done outside a service, should create a new transaction.
However, the output will always be the object in the same state it was at the beginning.
Update: The output is:
The Lord Of The Rings
The Lord Of The Rings
The Lord Of The Rings
If, in any case, you modifiy the controller, in order to:
def index() {
bookService.method()
Book.withNewTransaction {
Book bako = Book.findById(4)
System.out.println(bako.title);
}
}
The result is still the same.
Update: The output is:
The Lord Of The Rings
The Lord Of The Rings
The Lord Of The Rings
Only if you modify it to:
def index() {
bookService.method()
Book.withNewSession {
Book bako = Book.findById(4)
System.out.println(bako.title);
}
}
does the correct behavior ensue.
Update: The output is:
The Lord Of The Rings
The Lord Of The Rings
Harry Potter and the Goblet of Fire
Can someone explain me why:
Just being outside the transaction that read an object in a certain state is not enough to read fresh data;
Even forcing a new transaction is not enough to read the object with its most up to date state;
Why is the new session what allows us to do so.
Firstly Book bako = Book.findById(4) findById should be used in rare cases refer to Book.get(1L) Book.load(1L) Book.read(1L)
You are firing up a query to look for id when you could have just run .get
Actual issue
After much talk, no matter how much a service is transactional. If you decide to update the DB using mysql manually. You will break hibernate cache. You can try disabling - first / second level cache. First being if you declared caching in your domain class mapping.
This is really unwise and will have application impact. The reality is a transaction service should be doing the updates for you. If you need to manually update the database. Stop the app update / start the app. It is really that simple
There is a reason why I have been trying to push you down the route of attempting this scenario using an example project.
Why ?
Because it helps answer any speculation. I have taken a copy of your sample project and added some actual record updates to the demo.
https://github.com/vahidhedayati/grails-transactions
I have also made a pull request on your version so you can merge it and test it locally.
Basically flush:true not required. .get(id) not required.
As you can see from the results below In service after .save() on method 1 the results was updated. In controller it returned the correct result using method() after service returned it.
-- transactions.Book : 1 1 added
| Server running. Browse to http://localhost:8080/transactions
2016-09-05 18:12:48,520 [http-bio-8080-exec-4] DEBUG hibernate.SQL - select book0_.id as id1_0_0_, book0_.version as version2_0_0_, book0_.title as title3_0_0_ from book book0_ where book0_.id=?
--method1: before update: ------------------------------> TITLE_SET_BY_BOOTSTRAP
--method1 before get: ---------------------------------> New title from method1
method1 after get: ----------------------------------> New title from method1
2016-09-05 18:12:48,618 [http-bio-8080-exec-4] DEBUG hibernate.SQL - update book set version=?, title=? where id=? and version=?
After service1 call 1 New title from method1
--method2 update: ------------------------------> New title from method1
2016-09-05 18:12:48,687 [http-bio-8080-exec-4] DEBUG hibernate.SQL - update book set version=?, title=? where id=? and version=?
--method2 before get: --------------------------> New title from method2
method2 after get: ----------------------------> New title from method2
After service call 2 New title from method2
--method3 before update: ---------------------------> New title from method2
2016-09-05 18:12:48,795 [http-bio-8080-exec-4] DEBUG hibernate.SQL - update book set version=?, title=? where id=? and version=?
--method3 updated before get: -------------------------> New title from method3
--method3 after get: -----------------------------------> New title from method3
After service call 3 New title from method3
After reviewing the user issue ages back and having understood that they were manually updating DB record then expecting screen to show the same result.
In short if you have no cache enabled in the application then yes it should all work. If you have some form of Hibernate cache or ehcache enabled then it is likely you will be looking at some cache object. I had suggested restarting application to ensure you have latest. But if you simply:
wrap a
DomainClass.withNewTransaction {
//get the latest copy
DomainClass clazz = DomainClass.get(recordId)
println "-- ${clazz.value}"
}
This should ensure you are getting the very latest from the DB, it isn't going to be speed efficient but if you are expecting manual db updates you could always ensure the latest is above..
Related
Time on page calculated only for specific segment in Adobe Analytics
Goal I would like to see what is the time on page for user who is logged in. Eliminate from reports time, while user was not logged in. To have ability to distinguish between time on page while user is not logged in and time on page while he is logged in. Setup Let's say we have: Traffic variable User logged in as a prop1 where is true or false. Traffic variable Time from previous event as a prop2 in seconds eVar1 duplicating prop1 | expire after event5 eVar2 duplicating prop2 | expire after event5 event4 - User logged in event5 - User logged out Time between events From an article about measuring time between events (https://experienceleaguecommunities.adobe.com/t5/adobe-analytics-questions/calculate-time-between-success-events/qaq-p/302787) if (s.events && (s.events + ",").indexOf("event4,") > -1) { s.prop2 = "start" } if (s.events && (s.events + ",").indexOf("event5,") > -1) { s.prop2 = "stop" } s.prop2 = s.getTimeToComplete(s.prop2, "TTC", 0); s.getTimeToComplete = new Function("v", "cn", "e", "var s=this,d=new Date,x=d,k;if(!s.ttcr){e=e?e:0;if(v=='start'||v=='stop')s.ttcr=1;x.setTime(x.getTime()+e* 86400000);if(v=='start'){s.c_w(cn,d.getTime(),e?x:0);return '';}if(v=='stop'){k=s.c_r(cn);if(!s.c_w(cn,'',d)||!k)return '';v=(d.getTime()-k)/1000;var td=86400,th=3600,tm=60,r=5,u,un;if(v>td){u=td;un='days';}else if(v>th){u=th;un='hours';}else if(v>tm){r=2;u=tm;un='minutes';}else{r=.2;u=1;un='seconds';}v=v*r/u;return (Math.round(v)/r)+' '+un;}}return '';"); Time spent overview From adobe docs (https://docs.adobe.com/content/help/en/analytics/components/metrics/time-spent.html) A “sequence” is a consecutive set of hits where a given variable contains the same value (whether by being set, spread forward, or persisted). For example, prop1 “A” has two sequences: hits 1 & 2 and hit 6. Values on the last hit of the visit do not start a new sequence because the last hit has no time spent. Average time spent on site uses sequences in the denominator. So I guess I will uses prop1 as a denominator for logged in user state to count time between event in prop2 properly. Problem I am not pretty sure, If this approach is enough to correctly measure time spent only while user is logged in. I would appreciate some hints, how to set up eVars correctly or if I understand sequence denominator correctly. I also set up eVars with terminating event5, but I am not sure, If this leads to desired behavior. If you also solve this problem before, please can you lead me, how you define your segment or condition in reports.
GetTimeBetweenEvents plugin should do a job. However, it seems like it was rewritten, I have found in documentation example calls also using Launch plugins extension: https://docs.adobe.com/content/help/en/analytics/implementation/vars/plugins/gettimebetweenevents.html From Adobe documentation Install the plug-in using AppMeasurement Copy and paste the following code anywhere in the AppMeasurement file after the Analytics tracking object is instantiated (using s_gi ). Preserving comments and version numbers of the code in your implementation helps Adobe with troubleshooting any potential issues. /******************************************* BEGIN CODE TO DEPLOY *******************************************/ /* Adobe Consulting Plugin: getTimeBetweenEvents v2.1 (Requires formatTime and inList plug-ins) */ s.getTimeBetweenEvents=function(ste,rt,stp,res,cn,etd,fmt,bml,rte){var s=this;if("string"===typeof ste&&"undefined"!==typeof rt&&"string"===typeof stp&&"undefined"!==typeof res){cn=cn?cn:"s_tbe";etd=isNaN(etd)?1:Number(etd);var f=!1,g=!1,n=!1, p=ste.split(","),q=stp.split(",");rte=rte?rte.split(","):[];for(var h=s.c_r(cn),k,v=new Date,r=v.getTime(),c=new Date,a=0; a<rte.length;++a)s.inList(s.events,rte[a])&&(n=!0);c.setTime(c.getTime()+864E5*etd);for(a=0;a<p.length&&!f&&(f=s.inList(s.events,p[a]),!0!==f);++a);for(a=0;a<q.length&&!g&&(g=s.inList(s.events,q[a]),!0!==g);++a);1===p.length&&1===q.length&&ste===stp&&f&&g?(h&&(k=(r-h)/1E3),s.c_w(cn,r,etd?c:0)):(!f||1!=rt&&h||s.c_w(cn,r,etd?c:0),g&&h&&(k=(v.getTime()-h)/1E3,!0===res&&(n=!0)));!0===n&&(c.setDate( c.getDate()-1),s.c_w(cn,"",c));return k?s.formatTime(k,fmt,bml):""}}; /* Adobe Consulting Plugin: formatTime v1.1 (Requires inList plug-in) */ s.formatTime=function(ns,tf,bml){var s=this;if(!("undefined"===typeof ns||isNaN(ns)||0>Number(ns))){if("string"===typeof tf&&"d"===tf||("string"!==typeof tf||!s.inList("h,m,s",tf))&&86400<=ns){tf=86400;var d="days";bml=isNaN(bml)?1:tf/(bml*tf)} else"string"===typeof tf&&"h"===tf||("string"!==typeof tf||!s.inList("m,s",tf))&&3600<=ns?(tf=3600,d="hours", bml=isNaN(bml)?4: tf/(bml*tf)):"string"===typeof tf&&"m"===tf||("string"!==typeof tf||!s.inList("s",tf))&&60<=ns?(tf=60,d="minutes",bml=isNaN(bml)?2: tf/(bml*tf)):(tf=1,d="seconds",bml=isNaN(bml)?.2:tf/bml);ns=Math.round(ns*bml/tf)/bml+" "+d;0===ns.indexOf("1 ")&&(ns=ns.substring(0,ns.length-1));return ns}}; /* Adobe Consulting Plugin: inList v2.1 */ s.inList=function(lv,vtc,d,cc){if("string"!==typeof vtc)return!1;if("string"===typeof lv)lv=lv.split(d||",");else if("object"!== typeof lv)return!1;d=0;for(var e=lv.length;d<e;d++)if(1==cc&&vtc===lv[d]||vtc.toLowerCase()===lv[d].toLowerCase())return!0;return!1}; /******************************************** END CODE TO DEPLOY ********************************************/ Then your eVar may looks like: s.eVar1 = s.getTimeBetweenEvents("event1", true, "event2", true, "", 0, "s", 2, "event3");
"EntityNotFoundException: Unable to load RELATIONSHIP with id" when saving RelationshipEntity (with huge generated cypher query)
I am using spring-data-neo4j 4.2.0.M1 and neo4j-ogm 2.0.4 with neo4j 3.1.0-M04. The application is generally working, except for one case where I try to save a collection of modified RelationshipEntities. The code is sth. like this: List<Relationship> updatedRelationships = new ArrayList<>(); for(Relationship relationship : modifiedRelationships) { relationship = relationshipRepository.load(relationship); relationship.setValue("value"); updatedRelationships.add(relationship); } relationshipRepository.save(relationships); The RelationshipEntity is annotated with #RelationshipEntity and has a few properties in addition to the #StartNode and #EndNode. Only the property mentioned above is changed though. The RelationshipEntity is loaded inside the loop because I previously noticed lost information (namely value of other properties) when executing this. Note that the above mentioned code is executes for many RelationshipEntities in succession. Each relationship (probably) occurs only once, but start and end nodes probably occur several times. To my knowledge, no relationship is deleted though. The exception I get is: Caused by: org.neo4j.kernel.api.exceptions.EntityNotFoundException: Unable to load RELATIONSHIP with id 20683203. at org.neo4j.kernel.impl.api.store.DiskLayer.relationshipVisit(DiskLayer.java:432) at org.neo4j.kernel.impl.api.store.CacheLayer.relationshipVisit(CacheLayer.java:326) at org.neo4j.kernel.impl.api.StateHandlingStatementOperations.relationshipVisit(StateHandlingStatementOperations.java:1409) at org.neo4j.kernel.impl.api.ConstraintEnforcingEntityOperations.relationshipVisit(ConstraintEnforcingEntityOperations.java:416) at org.neo4j.kernel.impl.api.OperationsFacade.relationshipVisit(OperationsFacade.java:493) at org.neo4j.kernel.impl.factory.GraphDatabaseFacade.getRelationshipById(GraphDatabaseFacade.java:300) ... 104 common frames omitted The query that is executed before (which is probably the "save" query) is huge and exceed the character limit here (sth. like 200k characters). Apparently the query touches where more relationships than necessary (from business logic point-of-view) since only about 30 entities are actually saved. I would assume that the resulting query (or queries if updates are done per entity) are rather brief. 2016-08-28 20:16:33,007 I [pool-4-thread-1 ] (EmbeddedRequest.java:155) Request: START r=rel({relIds}) FOREACH (row in filter(row in {rows} where row.relId = id(r)) | SET r += row.props) RETURN ID(r) as ref, ID(r) as id, {type} as type with params {relIds=[13744338, 19099951, 12570789, 12570785, 13744377, 13648126, 12570765, 20627727, 13744356, 20627724, 12570760, 19263773, 19257628, 20113678, 19099932, 19259756, 18796874, 13783174, 19097972, 19083644, 19099970, 19097921, 19077446, 19263810, 13744312, 20568405, 20904270, 19097937, 12570827, 20627779, 20648258, 12570816, 20683195, 19259812, 20683194, 20683193, 20683192, 19083690, 20683186, 20683191, 19259819, 18819471, 20683178, 20683177, 12570669, 20683176, 19276210, 19933607, 20683171, 18844038, 19100089, 20683174, 20683173, 20683163, 20683162, 20683161, 13744242, 19257729, 12570649, 20683165, 20683164, 19087754, 21703141, 12570641, 8341711, 19259796, 8704051, 19915155, 19261851, 13783062, 13783063, 19091955, 18182597, 19276276, 19276275, 20623852, 20607468, 20623853, 19100155, 19233277, 13783048, 19261946, 12570719, 21789101, 12570718, 19075526, 19259842, 19257807, 12570707, 13715516, 19098061, 19261908, 20683208, 20683215, 19100118, 20683212, 20683203, 19276254, 20683201, 20683207, 19091934, 20683206, 19261915, 19097639, 19101736, 19101749, 18821129, 19097659, 19124284, 13662709, 13744628, 19052549, 19089427, 13744612, 19265563, 19251300, 19089509, 19251298, 20631665, 19251305, 19265642, 13744513, 19261558, 19261511, 19265606, 19081291, 18903113, 18903114, 19251273, 8341775, 12597685, 13744548, 19081308, 18725021, 18725020, 19273892, 19099808, 19089572, 19097772, 13744449, 13683011, 18178177, 19273905, 19093694, 18178231, 19124358, 20633756, 13744502, 19081356, 18651311, 19093661, 20562171, 19263725, 20625639, 19099901, 20631774, 20676819, 18651383, 20676822, 20676821, 20676820, 19097811, 19099862, 13744428, 20631751, 18178280, 18668312, 19100453, 19088171, 20708148, 19143487, 19088184, 19094334, 18668349, 13744883, 19145485, 20607750, 19094301, 19086108, 13744792, 20611958, 19143528, 13662849, 13744829, 12571346, 20611918, 20611919, 18811753, 19100506, 13744813, 19084195, 13662806, 20708275, 19098546, 20612001, 13744752, 20708253, 12595823, 20611976, 19147673, 19258343, 19274725, 19084262, 19082212, 19096548, 20591606, 19086317, 13662720, 8348332, 19274738, 8348329, 19096571, 21703569, 19440630, 13744654, 21824427, 13744701, 19258320, 20612032, 19086296, 19080158, 19282466, 19145249, 19261996, 20607539, 12596170, 19282472, 18776588, 19100208, 12596183, 18182658, 19233341, 19278395, 19096126, 19098115, 20640284, 18844217, 19255810, 19259919, 19257864, 20623892, 19091980, 19933697, 19282450, 19100180, 19261981, 12596219, 12596113, 19255924, 20707949, 12596118, 19098228, 18704970, 12596122, 19278458, 19096190, 19278456, 19253826, 19278412, 13745087, 19100241, 13745066, 18704995, 19278500, 13744981, 5954519, 19094199, 19143356, 13744970, 12598116, 18840242, 13745006, 18676445, 18008789, 19096298, 18676426, 20607724, 13744906, 13755199, 19094227, 12596419, 19098918, 19256621, 19090736, 21075287, 19100929, 21851496, 20876568, 13681912, 12596463, 12596465, 19090704, 10951825, 12596471, 13681897, 13753581, 19094814, 12596352, 21703948, 21695756, 18699605, 19256693, 18818378, 12596376, 19090755, 19256647, 13681844, 19082583, 18836839, 18699621, 12596409, 20618681, 21544395, 19916202, 12596299, 12596310, 19436940, 19099014, 19094918, 19916170, 13681782, 12596335, 20680073, 13681762, 13681763, 19099028, 19094938, 21081473, 13681682, 20680177, 12596242, 19099126, 19500540, 21081496, 10492993, 19099087, 21081517, 19099094, 21704112, 19098665, 18680849, 12596685, 12596689, 19274804, 20648995, 19137597, 21048411, 19088387, 19262470, 20657183, 19086357, 19258397, 18680869, 12596731, 19088413, 19272807, 19274848, 19272811, 12596622, 18811984, 15797667, 19096694, 19082357, 19262579, 19274875, 19137604, 12596642, 19274830, 19098696, 13682107, 12596651, 19096655, 20632650, 19088474, 19274845, 19262555, 19100834, 13682007, 19098794, 19100851, 12596565, 20556972, 19254450, 20597926, 12598622, 20597925, 20649114, 19100800, 13682036, 19100806, 12596582, 18703539, 20638856, 20598010, 18703582, 19094763, 19100905, 19096808, 20634857, 20597991, 5877179, 5877178, 20597977, 5877181, 19098822, 12596527, 12596532, 19199781, 19265313, 19261228, 20625200, 19257134, 20625201, 18714376, 19085108, 19253054, 19253048, 19265339, 20637459, 20637456, 19085074, 21081974, 8316482, 20598534, 18714402, 19107685, 19253090, 20615029, 19097462, 19263346, 20621152, 19263352, 19259207, 13729470, 19085140, 20688830, 19251116, 19259304, 13678173, 20615087, 12596830, 19097474, 21082087, 12596840, 19263368, 19251093, 8701488, 19267475, 8349384, 12572165, 8349360, 12596751, 19077119, 12596765, 20625380, 19077057, 19089350, 21825447, 21702567, 13682208, 12596785, 8316559, 18178020, 19253207, 20688847, 12596788, 19267536, 20688838, 12570558, 19232295, 12570550, 13783001, 20643352, 20694547, 19095051, 20643338, 19232272, 12570505, 20641280, 20694529, 20641284, 19099164, 20821624, 20821626, 20631165, 20821619, 12570606, 19439229, 12570601, 18820674, 19232327, 12570588, 20694621, 20641362, 20119134, 20631115, 20680264, 20618831, 19093080, 18824862, 19256994, 7325670, 20821668, 19257017, 13782863, 16494427, 20620952, 19256967, 20637331, 18030271, 8267731, 19256977, 20670095, 19099360, 20637433, 19261170, 19265276, 20907749, 18822910, 20621021, 19099339, 19252938, 19936961, 19099345, 19109599, 19257048], rows=[omitted] I've tried to load the relationship with that id directly, but none exists. The same code executes fine for other RelationshipEntities but repeatedly fails for either this or one of a handful other ones. Any ideas as to what could cause this or how this can be better debugged?
I think I somehow solved this with the following steps: Replaced saving the RelationshipEntity with saving the modified NodeEntity Making such modifying operations sequential (previously this could happen in parallel) Encapsulate the modifying operation in a transaction Fixed a bug where in the same transaction the same entity was saved twice (without changing again in the meantime) Fetching the entity again at the beginning of the transaction in order to have the latest state available Since I was prettty much in the dark about this topic until it finally worked, I am not sure if all of the steps actually helped solving this. It may actually have been only a subset. What I can see though now is, that the huge update queries are now smaller (albeit still quite big) but actually seem to contain "real" updates instead of mostly "null" properties. I assume that previously it didn't really contain an update and was instead overriding properties with "null". The fact that this is now working is probably related to the fact, that the entity is now updated before beginning to modify it and that no other modifying operation can run in parallel.
I had the same problem. For me it was simply the neo4j-ogm-embedded-driver version I had to include in my pom. The one I defined overwrote the one defined in spring-data-neo4j.
If you only save the relationshipEntity,you could only using next snippet: List<Relationship> updatedRelationships = new ArrayList<>(); for(Relationship relationship : modifiedRelationships) { relationship = relationshipRepository.load(relationship); relationship.setValue("value"); updatedRelationships.add(relationship); } relationshipRepository.save(updatedRelationships,0); it would save the related properties on relationshipEntity and meanwhile ignore any related entities.
How can I track the current number of viewers of an item?
I have an iPhone app, where I want to show how many people are currently viewing an item as such: I'm doing that by running this transaction when people enter a view (Rubymotion code below, but functions exactly like the Firebase iOS SDK): listing_reference[:listings][self.id][:viewing_amount].transaction do |data| data.value = data.value.to_i + 1 FTransactionResult.successWithValue(data) end And when they exit the view: listing_reference[:listings][self.id][:viewing_amount].transaction do |data| data.value = data.value.to_i + - FTransactionResult.successWithValue(data) end It works fine most of the time, but sometimes things go wrong. The app crashes, people loose connectivity or similar things. I've been looking at "onDisconnect" to solve this - https://firebase.google.com/docs/reference/ios/firebasedatabase/interface_f_i_r_database_reference#method-detail - but from what I can see, there's no "inDisconnectRunTransaction". How can I make sure that the viewing amount on the listing gets decremented no matter what?
A Firebase Database transaction runs as a compare-and-set operation: given the current value of a node, your code specifies the new value. This requires at least one round-trip between the client and server, which means that it is inherently unsuitable for onDisconnect() operations. The onDisconnect() handler is instead a simple set() operation: you specify when you attach the handler, what write operation you want to happen when the servers detects that the client has disconnected (either cleanly or as in your problem case involuntarily). The solution is (as is often the case with NoSQL databases) to use a data model that deals with the situation gracefully. In your case it seems most natural to not store the count of viewers, but instead the uid of each viewer: itemViewers $itemId uid_1: true uid_2: true uid_3: true Now you can get the number of viewers with a simple value listener: ref.child('itemViewers').child(itemId).on('value', function(snapshot) { console.log(snapshot.numChildren()); }); And use the following onDisconnect() to clean up: ref.child('itemViewers').child(itemId).child(authData.uid).remove(); Both code snippets are in JavaScript syntax, because I only noticed you're using Swift after typing them.
Box::info showing "refreshEx"
I have a method that displays a validation result using the syntax Box::info(message,title); However, the first time I run the code it displays the correct title, but the message refreshEx. Debugging the code the message that is being used is correct, Valid Account Number, but what displays is refreshEx. If I rerun the process the correct message is displayed, this only happens the first time. Just in case it matters the flow is Form - DoValidation method creates Class to call... Class - public AccountValidation method that calls... - private displayValidation method that contains this code Thanks...
I have seen this error (unfortunately), in an AX 2009 installation, launched from code behind a button in a form: if(HIEItemOrderSetup.RMAvailable < HIEItemOrderSetup.RMQuantity) { ok = DialogButton::Ok == box::okCancel("#HIE848",DialogButton::Ok,"#HIE849"); } As far as I can tell it only occurs when you have a breakpoint on your form, when you are updating it. Removing the breakpoint will show the original message or at least this is what I have found.
If the message contains some fields from the database, try to execute a reread() or refresh() or refreshEx() method (depending on the context) to the datasource before showing the value through the info box. May be the cached data is not refreshed after an update or insert. EDIT: If you are specting a return parameter from an Event, don't forget that this is an async process. An example on MSDN: http://msdn.microsoft.com/en-us/library/gg843664.aspx
Why is NHibernate persisting new objects in the wrong order of creation even inside a transaction?
In my asp.net mvc app I have a Log object which tracks what happens in my forum. when a comment is added inside a transaction the following happens: A new comment is added to the submissions table A new log entry with a reference to the id of that comment is added to the logs table Finally the transaction is committed and no exceptions are generated. But it turns out that when the transaction is committed, NHibernate first inserts the Log record and then the Comment record, therefore saving the log record with an incorrect comment id which is 0. Here is some code to show what is happening: using (IAtomicTransaction Transaction = UnitOfWork.BeginTransaction()) { try { SubmissionRepository.AddComment(Comment, ParentSubmission); LogRepository.AddCommentEntry(Comment); Transaction.Commit(); } catch { Transaction.Rollback(); throw; } } UnitOfWork and AtomicTransaction are just wrappers around the ISession and ITransaction objects from the NHibernate APIs. And here is the log generated which confirms the problem: 2011-02-09 14:42:05,631 [15] DEBUG NHibernate.SQL - INSERT INTO logs (version, created_at, updated_at, comment_id) VALUES (?p0, ?p1, ?p2, ?p3); ?p0 = 9/02/2011 2:42:05 PM, ?p1 = 9/02/2011 3:41:52 AM, ?p2 = NULL, ?p3 = 0 2011-02-09 14:42:05,647 [15] DEBUG NHibernate.SQL - SELECT LAST_INSERT_ID() 2011-02-09 14:42:05,647 [15] DEBUG NHibernate.SQL - INSERT INTO submissions (version, created_at, updated_at, body) VALUES (?p0, ?p1, ?p2, ?p3);?p0 = 9/02/2011 2:42:05 PM, ?p1 = 9/02/2011 3:41:52 AM, ?p2 = 9/02/2011 3:41:52 AM, ?p3 = 'dfgdfgd dfg df' 2011-02-09 14:42:05,647 [15] DEBUG NHibernate.SQL - SELECT LAST_INSERT_ID() What is the solution to this problem? Update: So it turned out that if I call Flush after every insert then the order will be correct. using (IAtomicTransaction Transaction = UnitOfWork.BeginTransaction()) { try { SubmissionRepository.AddComment(Comment, ParentSubmission); UnitOfWork.CurrentSession.Flush(); LogRepository.AddCommentEntry(Comment); UnitOfWork.CurrentSession.Flush(); Transaction.Commit(); } catch { Transaction.Rollback(); throw; } }
Sure, when you try to add a comment and a log entry in a single transaction the ID of Comment isn't yet generated, unless you use "Manual ID assignment" approach or flush your session to persist changes you made. In your original post there is no "Flush" after you add the comment and you want to add the log for that comment (for which ID isn't defined yet). Not sure about why Nhibernate takes such a reverse order but seems like its at least an ID generation issue. Another thing that caught my attention is that you use Comment and Args.Comment side by side. What is difference? Anyway, if Comment is somewhat your domain entity, you have to: Insert it; Make sure it is attached to the current ISession (you should have the session-attached Comment entity object available); Use exatly that entity (which is in repository already) for log entry (NHibernate will take care of relations).