NHibernate is on the cover of MSDN Magazine
A while ago I run a poll about what posts you would like me to do, and the most requested topic was handling NHibernate in a Desktop application. I started writing a blog post about it, but when it hit twenty pages, I thought better on that and decided that I might as well post that as an article. MSDN Magazine just did.
You can read the about Building a Desktop To-Do Application with NHibernate in the latest issue of MSDN Magazine.
And now that the article is out, I can start posting about other topic in the code base that are pretty interesting as well.
Comments
Really nice article ;)
Btw, AllowEditing + such way of background updates seems dangerous in more complex cases: AllowEditing (by its meaning) does not prevent e.g. fetches into shared Session (of course, this is intact if you have them).
Awesome article. I don't do any desktop development, but it solved many doubts I had about how one would do things with NH in that environment.
One thing it's not clear for me is: in which cases would you use this kind of development (NH directly accessing the db) versus doing through an application server? I thought connection handling was the main reason, but apparently NH can deal with that too.
The trick of serializing Configuration is quite neat.
Excellent! Thanks Ayende
I think the article is good in the theory but IMHO the code is really bad.
Managing the nhibernate session in the presenters?
Why do you do that???!
Terrific article! Thanks
Alex,
I am not following the problem that you present.
Alberto,
Common cases for desktop app is when you have a truly local app, one that is using a local database.
Common cases for app servers is when you have something that you need to share among multiple users, and you have stricter requirements on data, authorization, etc to allow direct DB access.
Jose,
I obviously disagree, I suggest posting a full code review.
I'll then be able to answer that in details
That totally makes sense, thanks.
@ayende excellent article.
will you post an article about Building a WEB To-Do Application with NHibernate. because it will be very very convenient for newbies like me..
Yes, it will be my pleasure to write a review.
IMO Effectus has things I like and that don't. So, I will review both.
AllowEditing prevents modifications of objects in Session, but does not prevents UI from reading them. On the other hand, there are operations like Session.Get <t, that, although being looking like read only operations, are actually modifying Session state. So AllowEditing must block them as well. But "AllowEditing" name actually does not indicate this.
I think it's not a good idea to execute such Save() method concurrently. Cloning the Session (must be very fast, but I'm not aware if this is supported by NH) & saving everything concurrently & safely must be a better option.
In this case there is no need for AllowEditing at all.
Alex,
The way I talked about concurrent save in the article, it is strictly to allow UI responsiveness, if you will look at the app you'll see that the appropriate window is essentially disabled during the save process.
The main reason to do that is to avoid hanging the UI. Cloning the session is possible, but it is a very bad idea, it means that the UI session is now in an inconsistent sate.
The process is only concurrent from the technical perspective.
That's absolutely clear.
What "inconsistent state" means here? May be you misunderstood me. I mean the following process:
Clone uiSession to bgSaveSession. Clone = with all the objects it exposes
Persist the changes from bgSaveSession in background
Replace the uiSession by bgSaveSession synchronously (i.e. in UI thread). If there are conflicts, allow user to handle them.
Btw, I just imagined one more issue in your case: when background thread is finishing saving the changes, it most modify internal Session state accordingly. I don't know exactly what happens in NH to do this, but it seems it must update some structures responsible for dirty checking at least. So actually this background thread even modifies the structures associated with the Session.
Consequence: possible concurrency issues on even lookup-only, read-only operations invoked by UI.
I understand that everything might work safely in exactly your case: the application you're describing is pretty simple. E.g. no fetches to the same Session can be induced by UI there. But real-life applications usually are a bit more complex: it's normal when you download additional content there on paging, opening detail view and so on. All these operations change internal Session state, so you can't run them concurrently with Save method. Moreover, even readers of part of Session state affected by Session.Save method can't run concurrently with it.
So since users may try to use your sample as prototype of real-life applications, I think it's desirable to fix this.
Btw, actually I don't know any other simple solution except cloning the Session to accomplish this safely. So if you can advise something else, I'd be glad to hear this.
That is the problem, it is going to be hard to do, is at all, and it doesn't gives you anything. The idea is, I presume, to allow concurrent work on the uiSession.
If that is the case, then you can't replace it.
Sessions don't have thread affinity, you just have the serialize access to them, so there is no problem with the scenari you describe.
Yes, they are, that is why each individual part in the UI gets its own session.
This is obviously a mistake here: this is possible only if you're disabling editing while save operation is running. Otherwise you should:
Check if there are new changes in uiSession; if there are no changes, follow the previously described plan.
Otherwise you must be able to merge the uiSession with bgSaveSession. Likely, Session merge is supported by NH.
Alex,
You can't merge sessions, because the notion itself is meaningless.
You can merge _entities_. At any rate, that would create a lot of confusion, I think, so a simpler alternative is not to do it in the first place, and just open a new session if you need to do separate work at the same time.
I noticed that in your case "individual part" is defined by IPresenter, that is actually a window presenter (btw, the MVP architecture you're using is far from true MVP from this point).
So "individual part" is Window. What about master-detail, expandable areas and so on?
Alex,
I think I discussed this in the article, master-detail will most likely share the same presenter/session, but composable parts (complex windows, etc) will have different presenters for each part.
Well, we already support this for DisconnectedState, and this is not meaningless.
Technically, any mySession.Save() operation in NH could be "emulated" with merge by this way:
Create a new commitSession
Fetch all the entities modified in mySession into commitSession directly from the database, but don't close the transaction
Try to merge commitSession into mySession. If there are no conflicts, simply persist mySession (w/o any version checks) & commit the transaction.
Of course, this is just a sample showing how merge must work.
I clearly wrote what does this give: no issues related to concurrency.
I never mentioned thread affinity - i.e. it's absolutely clear they can be sequentially processed by different threads.
What I wrote is that you allow readers & writers to compete, that will lead to concurrency issues, if there is no any special support for this.
So basically, you won't get serialized access to Session in just a bit more complex case (e.g. master-detail view on the same page with a single Save button).
Ok, to be fully clear: if there is master-detail, to follow the same pattern, you must:
Either load a complete detail data for all the masters on form appearance, but not on changing the master.
Or prevent loading detail data on master change while AllowEditing==false.
Examples are almost always better than general discussions ;)
How would you handle a richer main form, ie a gis application with a map that shows entities ?
The entities are "alive" which means they are being updated from sensors and other users, and they're also support updating.
Likely, update notification is the best option here. Related technologies:
SQL Server Notification Services
StreamInsight / Rx Framework (if amount of updates per second is huge).
Alex,
It may not be meaningless for the way you work, with NH, we have completely different approach for disconnected state (merging / attaching).
In NHibernate, merging sessions is meaningless.
Omer,
I would probably poll for updates from the DB, using session.Refresh.
Pretty hard to give an accurate answer with the given data.
Alex,
Update notification is really slow on big datasets. But we don't have any information yet to make a decision.
"Update notification" in my case refers to the solution in general, not to its particular implementation.
E.g. SQL Server Notification Services might be slow. But StreamInsight streams & Rx sequences intrinsically can't be. Obviously, the technologies I'm pointing to are quite different, but since I don't know any metrics, I point to several ones.
If there are millions of updates per second, forget about SQL Server & NH at all & stick to StreamInsight \ Rx. If there are thousands or less, you have more options. E.g. notifications created by application server must be pretty good option, especially if you deliver just diffs to the client.
You must know this better. This is meaningful in real world, so I used this term.
Alex, did you noticed the title of this post? Everything I reply here is in the context of NHibernate.
So if NH does not support some real-life feature X, people here used to silently workaround this? ;)
Oren, suggesting wide audience of people using intrinsically unsafe solution is what I'm talking about. This is in context of NHibernate. The way I describe alternative solutions is fully upon my choice: I use abstractions that looks good from my point of view. It does not matter if they exist in NH or not (btw, I even don't seriously think about this) - if this helps to illustrate the idea, it is perfect.
You mean nearly this query:
var updates =
from u in session.Linq <updateinfo<someentity>
where u.Time > lastUpdateTime
order by u.Time
select u;
Alex,
I am not getting into this again.
In this thread, I am going to talk about what NHibernate is doing. Feel free to post in your blog about whatever you feel like.
FFS Alex - I come here to read about NHibernate - stop trying to hijack ayende's hard work for your own marketing efforts.
It's transparent what you are attempting and very unethical. Trust me - your behaviour here has meant I will never be using anything associated with you.
I'm starting to feel myself angry - Oren tells "he tries to hijack my audience" and there is at least one Nick saying "yes, stop this!"
Let me return you back to the origin: I noticed a lack in your sample and pointed to it here. There was nothing about lack in NH, I don't care about them. Let me remember you what exactly I wrote:
I was almost 100% sure NH is capable of cloning the Session. I didn't touch the topic related to merge support (I repeat you, I even didn't know if it exist or not). I wrote about this when Oren said "this is wrong, this won't work" - so I explained how this could work.
Returning back to subject: I'm not speaking about some particular way of resolving the issue. It does not matter if there is merge or not - the only fact that matters here is that shown code has concurrency flaw, that's it.
Frankly speaking, I'd simply use synchronous save + relatively small ConnectionTimeout + blocked UI & "Updating..." \ "Loading..." to provide enough good experience. You aren't talking with appserver here, you're talking directly with DB. So what are the reasons of possible delays?
Deadlocks / locks // Rare, reprocessable
Connection failures // Quite rare
So actually I don't see any strong reasons for using this technique at all here. But since you used it & brought the code to public, you got the comments.
P.S. Being on your place, I'd simply say "really, that's risky - let's consider this next time!". On contrary, following the way yo prefer is not the best way to use your authority. You simply deceive such of your fans as Nick.
Yes i'm a fan of Ayende's. For two reasons:
On his own blog he has taught me so much just by sharing his learnings for free. I know he works incredibly hard and I am very grateful that he shares stuff here.
He is the main contributor of the free oss project NHibernate. A fantastic ORM that has saved me countless hours of development at work. He continues to help me and teach me how to use it here on his blog.
I will say no more about what you are "offering".
Great you're trying to protect Oren. What's bad is the way you choose (switching the context to the only one you are able to recognize - for me this looks much like: "he is alien! bow-wow!") and your inability to distinguish between advertisement and regular discussion of issue. So you're true fan ;)
P.S. I write here only when I have something important to say. Moreover, I'm also reader of Oren's blog. If I'd decide to advertise something here, obviously, I'd ask the others.
Alex,
Please attribute quotes correctly.
Your approach with synchronous save is going to lead to either hung UI or to lot of connection timeout errors. Neither of which are nice to the user.
My approach, quite explicitly, as mentioned in the article is there to handle the case of not hunging the UI.
And I was quite clear about how to handle multiple actions at the same time.
Nick,
A small correction, I am not the main contributor for NHibernate, just the loudest one.
I once wrote a reasonably large enterprise app (80+ tables, 30+ concurrent users) using NHibernate 1.2 with WPF thick clients.
I ran into the painful lesson that Ayende describes as "common bad practice", using a single session per application instance.
Once we gained a better understanding around how to handle sessions correctly, we refactored and found NH to be effective for our needs.
One subtle but important lesson I learned is that the design of the UI (correct use of master-detail windows, modal behavior) not only makes conflict management easier for the developer, but also gives the end user an understanding of the data model, and this transparency allows for a better all-around understanding of the application which is a good thing in terms of support.
This concept of UI arrangement can be of benefit to any thick client design regardless of NHibernate/EF/XX.
Oren, I fully agree that ideally any saving\loading must be asynchronous. But I think if this is shown in sample, it's better to use really safe approach instead of the one closer to a hack.
What do you normally do to perform async operation, if you'd like to avoid locking? Either use read-only structures or provide their own copy for each thread. Your solution was based on fact that a mutable structure won't be used concurrently (i.e. "let's think it's read only, although this is not fully true"). Thus I suggested to simply clone it to avoid any danger.
For me it's almost the same as recommending to use dictionary concurrently in case there is just a single writer.
Actually, this is dependent on the framework - all depends on intended Session usage in a particular ORM.
Btw (to state this once again :) ), the article itself is great. Simply perfect intro for NH @ WPF.
Whether you call it a session, a dataset, or just a bag of objects with state, it falls under the same conceptual umbrella. The objects contain state that can be invalidated and need to be kept in sync with the master copy. That's a universal problem regardless of whether you are using an ORM or a carrier pigeon.
Behavior of objects exposed in Session can significantly vary. Obviously, what you see in NH isn't the only possible way. You're right, finally it's a part of the whole graph of objects stored in DB, so the differences aren't dramatic. But this is enough to make a question of using multiple sessions versus single one subjective.
Let me finish here. There are such guys as Nick, and I'm already feeling his "grrrr...." ;)
Alex,
I consider the cloning approach to be a hack.
Hack = usage of API that is not intended for solving the problem.
Concurrent read-write access to Session is not a part of its intended usage.
If cloning is really supported, I don't see any hacks (inappropriate usage of APIs) inside my solution. If you do, just name them.
Alex,
Cloning the session results in more problems down the road
Alex, the reason folks get annoyed is that you are thoroughly argumentative and often condescending in your comments. No amount of winky faces makes up for that and despite whatever your intentions may be, your comments come across as nothing more than NH (or Oren) bashing. The fact that there are usually things like "we already support this" makes it sound like a misguided marketing attempt too (i.e. here is the problem with NH that you won't have with my product).
But even giving you the benefit of the doubt here, assuming you have good intentions and don't mean to sound condescending, the sheer volume of your comments detracts from any actual meaningful conversation to be had. All people see is comment after comment by you (often with no other comments in between) and that, in and of itself, is annoying.
I'll explain how this happens:
I ask a question like: "If you do, just name them."
Oren does not answers, but writes: "Cloning the session results in more problems down the road" without explaining this further (I seen the explanation earlier, and already shown there are at least no more problems than in his original version).
Respecting the party normally implies answering on exact questions. Answer, than add anything you want to say. But it's the second topic I take part here where this rule does not work, and not from my side. Winky faces normally appear when I ask the same question for N-th time, and getting no direct answer.
So guys, good look paying attention on various side effects of such discussions like smiles in messages.
Don't rephrase me: I answered this on "merging Sessions is meaningless". Meaningless is not the best matching term here - possibly, for NH too (there must be original state used to perform dirty checking; so just detect the conflicts there, merge it and merge the actual state - again, with conflict detection). I wrote "we" mainly because I know how this works in our case. Moreover, the whole subject about merging was far from the original topic.
Btw, I completely don't understand why are you so aggressively talking with competitors here: if you're strong/popular enough (that's obvious), there is nothing to fear at all. Moreover, you're getting various ideas as well as issue reports for free. And instead of just following this, you're turning a normal discussion into argue around marketing.
You can say I'm writing mainly about the issues here (and that's true). Why? Well, good solutions are interesting for me, but frequently there is nothing to discuss, because it just works. I write mainly about our own good solutions, or the ones I consider brilliant (like Rx) - something, that is really new for me. Oren writes lots of nice posts, but personally I simply don't know what to write about many of them, although I understand they must be useful for developers.
On the other hand, finding & discussing possible issues in proposed solution is what really interesting. Sometimes this may even brake the whole idea. But this is good, because if you don't pay attention to issues, you're not developer at all.
So I'm not a kind guy constantly pointing on problems. Looking for them is simply a part of my mind. Obviously, in my daily life I mainly discussing solutions, but not just issues ;) But when I read something, I read it as critic as well. And, if there is something suspicious, I normally point on this; if this leads to argue, I'm proving my position. I easily agree with the opponent, if I understand his reasoning.
Ok, I feel I said enough for this thread. It's time to sleep here (3am). Have a good day/night.
Alex,
I don't like to repeat myself. And I don't see much of a point in arguing endlessly.
Hmmm...winky faces appear when you ask questions for the Nth time with no answer? Is that why you opened the comments with this:
To me (and probably 99% of the population) that reads as if you don't really mean it was a nice article. Maybe the real problem (aside from your argumentative and condescending nature and inability to recognize it in yourself of course) is that you simply don't understand how to use emoticons.
Actually that happened about 16 comments or so back. ;)
In the code of this article, when i load an entity from database, then i compare as the following:
entity.GetType().IsSubclassOf(typeof(ToDoAction))
the result is true, and
entity.GetType().Equals(typeof(ToDoAction))
alway return false. :(
Is there any way to make the type of an entity loaded from database equal the type of ToDoAction class?
Sorry for my poor English :)
lemycanh,
No, because NHibernate may provide a proxy class for you. For all intents and purposes, you may treat this instance as a ToDoAction class.
Making runtime checks of GetType() == typeof() is a bad idea
Oh, thanks, that is a great aticle, the Model-View-Presenter architecture in that example is also very interesting, tyvm ;)
Alex I'm interested in seeing what DataObjects.Net offers with the Sync framework, it looks like an interesting perspective on the issue.
@pete w — you should probably ask that elsewhere, don't ya think?!? Ayende's blog is hardly the place to discuss DO. If Alex wants to discuss DO's features he can do so on his blog or write his own MSDN article. ;)
Richard -
I think my statement is absolutely appropriate.
I am disappointed by you and other readers in here that antagonize and mock Alex not for his ideas but because you dont like the tone of his writing.
If you try to open up and learn a different perspective you might pick up something useful along the way.
pete if you try to open up maybe you understant that in a post titled
"Building a Desktop To-Do Application with NHibernate"
talking about "what DataObjects.Net offers with the Sync framework" is a little off topic :)
and about alex same people overdo it but he continue to make only comments where it speaks about a little thing of zero+ importance that is framework can do and nhibernate no and continue to speak about that as it is a very important feature.
DataObjects.Net leverages the microsoft sync framework as a means of tackling the classic problem of keeping "occasionally connected" thick clients in sync with the master object graph (the database).
NHibernate tackles this problem by controlling the granularity of sessions.
These are two unique approaches to the same problem.
It is an important problem, my question is not off topic, and obviously you have never dealt with the problem first hand. I have great respect for Ayende and his writing, but the syncophants who post here arent worth my time to argue with any more.
@pete w — Whether you find that type behavior disappointing or not is irrelevant — asking for information about a completely different topic/product within a comment thread is what is commonly known as hijacking the comment thread. So here it my point a little clearer — asking for more information about DO is perfectly valid, but this is not the place to do it.
With respect to a opening up and learning a different perspective, I am very open to looking into any product that could help me become a better developer, thank you very much. I just respect Ayende enough not to discuss Alex's product here.
As for Alex's tone, well, it doesn't bother me much actually. Nonetheless, maybe he should take your advice and learn how to better communicate so he doesn't ruffle as many feathers. He does do his fair share of antagonizing too.
You can leave the name calling out if it as it doesn't make your case any stronger.
"Alex I'm interested in seeing what DataObjects.Net offers with the Sync framework, it looks like an interesting perspective on the issue."
Despite how you try to reword in the follow-up comment, this comment wasn't worded as a question and doesn't have anything to do with Ayende's article or NH for that matter. It is an invitation to Alex to explain how his product leverages the synch framework.
Again, this is a perfectly valid question/inquiry, but this blog is not the place for that discussion.
I tried to adapt one of your ideas from the article (saving the configuration to disk) - but it throws me an exception:
System.Runtime.Serialization.SerializationException wasn't caught by user code
Message="The type \"NHibernate.Util.CollectionHelper+EmptyMapClass`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[NHibernate.Mapping.MetaAttribute, NHibernate, Version=2.1.0.1003, Culture=neutral, PublicKeyToken=null]]\" in Assembly \"NHibernate, Version=2.1.0.1003, Culture=neutral, PublicKeyToken=null\" is not marked as serializable
Source="mscorlib"
InnerException:
Any idea what could be wrong with my code ?
It looks like you are using an older version of NHibernate, from the version numbers
I saw the article and it's very interesting. I am completely agree with you as the session in a smart client application has and should have a complete different approach.
When I work with n-tier app and I have to use Nhb session both in WCF or in the win side, the session approach is completely different.
Really happy and satisfied from your article. As usual, great job!!
Your article is very interesting and it reads good, but i have two questions.
How would you solve a scenario where you could not edit and save parts of a larger object model in one view. Instead you have to save or discard the large object model complete.
In example think of an CRM where you could open a customer, do some changes on more then one view, validate all at once and then save or discard you changes.
And as second question, how did you work with services this way? The services are often need access to the full object when they do operations, but the services dose not know of the session from the view.
Comment preview