Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Datacontext Lifetime in WinForm Binding Scenario

This one has been stumping me for a while. But I'm no expert. This is a bit long...

I have a WinForms app with an Outlook style UI. That it to say there is a bar on the left hand pane that allows you to select a 'screen' which is a WinForms control, say the customer screen, and on the right hand pane there will appear a list of customers (i.e. the customer control). I call this the explorer interface. Double clicking a record will bring up a non-modal customer record in an additional window in the same way you would open an email in Outlook, we call this the inspector. If you double click multiple records you get multiple inspectors.

The whole thing is done using databinding. There is a BindingSource control on the customer list control and there another on the customer inspector. The customer control news up a static DataContext in it's load event and assigns the result of a simple Linq-To-SQL query to the BindingControl datasource property. When the customer list is double clicked the event looks up the record, casts it to a Linq-To-SQL customer object, and supplies this to the constructor of the customer inspector form. The customer inspector gets the customer object and assigns the datasource property of it's BindingSource control to it.

Since the BindingSource control supports IBindingList the contents of the customer object are not modified until EndEdit is called, in this app when the OK button is clicked. Since Linq to SQL implements the INotifyPropertyChanged interface the customer list is then updated. Cool.

However the problem comes when I want to refresh the content of the customer list to pick up changes made by other users, which I want to happen at regular intervals, every 60 seconds say. If I have a timer and re-run the query on the same datacontext no changes are picked up, because Linq to SQL does not want to squash any changes that have been made to the data under control of the datacontext. Strangely it runs the query against the DB but the identity tracking means that only new customer objects returned from the DB are added to the list. If I new up another datacontext then any open customer inspectors are no longer using the same datacontext, so any future changes to the open customer inspector are not reflected in the customer list.

Basically the data is stale because I am not using the Unit of Work pattern. So (if you are still with me), the questions are.

1. How the hell do I make the unit of work pattern work in this situation? It's easy enough is ASP.NET to use a request scoped datacontext, it's short lived, but in WinForms?

2. Are there any other ORMs that would work better in this scenario? NHibernate, EF, LLBLGEN etc.

3. How else should I go about it?

And also.

4. If I can make Linq to SQL work like this has anyone implemented IBindingList in the Linq to SQL partial classes which would avoid me having to use IBindingSource controls. (I'm not sure I should care about this).

5. If I can make Linq to SQL work like this is there any way of using SQL Notifications in SQL 2008 so that I can be notified when the underlying query results changes and requery then, rather than polling.

Thanks!

P.S. I am aware that I can use

db.Refresh(System.Data.Linq.RefreshMode.KeepChanges, customers)

but this causes a query to be run against the DB for each customer's record in the list.

like image 566
Christopher Edwards Avatar asked Dec 19 '08 13:12

Christopher Edwards


1 Answers

I'm going to restate your problem to make sure I've understood it.

You have a widget which presents a list of entities (the LIST). When you click an item in the LIST, another widget appears which allows the user to edit the entity. When the user has finished editing the entity, their changes are committed to the DB, and should also be reflected in the LIST of entities. Periodically, the system should also fetch changes that other users have made to items in the LIST and update the LIST.

If this is correct, I'm going to leave aside any concurrency problems of two users editing the same entity as this does not seem to be your concern, and will focus on how to organise the UI and the Unit of work.

You need to separate out the entities in your LIST from your entities being edited by your inspectors. The business process represented by your inspectors are your units of work, one unit for each entity. Your list does not represent a unit of work. It is a stale representation, or point in time, of the combined work of all the previously committed units of work. Your LIST doen't even have to deal with your entities directly, it can hold an ID or any other way for your inspectors to get at the underlying entity from the DB when a user clicks it. Now you will be free to update the list whenever you like as your inspectors don't share instances with it at all.

To simulate to your user that when they are editing an entity via an inspector and to make them appear to be bound to the same thing you have two choices.

1) list is only bound to committed data in the DB. This is easy, when an inspector flushes local changes back to the database and commits successfully, provide a way for the inspector to tell the list to update itself.

2) list is bound to committed data + local un committed data. This is a bit harder, you need to expose methods on your list which allow an inspector to trump data coming back from the Db, and overwrite it with its own local dirty data.

like image 147
Noel Kennedy Avatar answered Oct 29 '22 01:10

Noel Kennedy