4

I'm developing a WPF application using Entity Framework for my database communication. The application has a hierarchy of tabs where each tab has a db context. Each tab allows the user to view some particular object, make changes, save, update, close or open tabs for related objects.

My crux is that some tabs contain information that depends on other objects as well, an object O of type A may be related to other A objects (o_1,...,o_n) in a parent-children relationship, where I have a graphic indicator showing wether property P in O has the same value as P in all the children of O, for example. Red indicator means they are unequal, green means they are equal.

The "overlap" in information between contexts will only be presentation-wise, the different tabs will not be stepping on each others toes by changing each others data.

Now if I have Tabs for O and o_3 opened, change P in o_3 and save, the indicator in the tab for O might need to change as well, but since that tab has its own (now outdated) db context, the indicator still shows the old value, which might be confusing to the user.

This seems like a very general and common problem considering how many application has this architecture.

How would you solve the problem? I see a few different but flawed/problematic solutions.

A. Make the user aware of the fact that tabs can become "obsolete" when saving others. This feels like a forfeit to technological troubles.

B. Force updates of related tabs when saving. This would either overwrite user changes or I would have to build some system (repository pattern?) that maintains information of user changes and mediates what to update and what to keep, what to tell the user and so on.

C. Indicate outdated:ness of related tabs. This seems like the pragmatic solution, but the user is left to wonder what would be affected if updating the tab manually, and any changes made would have to be dismissed

D. Communicate the change to the other tab without involving the db context. This would sort of work, but could lead to complicated problems by introducing state that is not related to the db context, overall it would increase complexity

  • Are the tabs open at the same time or can the user only see one at any given point? Normally tabs would be the latter, but I want to be clear. – glenatron Sep 3 '14 at 14:39
  • Only one tab at the time can be shown. – Mårten Sep 4 '14 at 6:09
2

In the desktop application I am currently working at we solved similar problems by introducing an event mechanism, centered around a global "Event Manager". We have got a handful of "main business objects" (~10 different types), and whenever one dialog changes a business object, it raises a "change" event, including the type and the ID of the related object (the ID is also the primary DB key, we have uniform integer keys for all our objects). There are also "add" or "deleted" events for newly created or deleted objects.

Now dialogs use the publisher-subscriber pattern, if they are "interested" in getting notifications about changes for a specific object type, they register themselves at the event manager, which then will pass notifications for the specific type to every registered listener.

But how does this solve the problems you mentioned? Well, the answer is: every dialog can now decide for himself what to do when he gets a change event for a related object. That makes changes and collision handling much more simpler and manageable as if one dialog would have to make decisions for another "parent" or "child" dialog. For example, when getting a change notification, a dialog can test if the ID in the change events is equal to the ID of one of the objects it is currently presenting, and decide to refresh the object in stake from the DB. Or, if there were pending changes made to that object by the user in a second dialog, it could present the user with some kind of "collision resolve" dialog, do some automatic collision handling, or just notify the user about the collision and let him deal with that. That is actually what you mentioned under "B". How the "collision resolve" strategy has to look like depends very much on the amount and importance of the data, how much data in the "unit of work" can get lost when you implement only a simple strategy like "last change wins", or if you need a very sophisticated "compare-and-merge" tool.

Note that this event management works only within the local application, we did not implement such a thing across different clients, so when one user working at client 1 changes an object a second user working at client 2 has on its display, it is possible that client 2 still shows the old data for some time. The purpose is just not to confuse the user by showing the same object in different states simultanously on the same screen.

  • Thank you for your answer! Does your application use a database? If so, are the events raised when saving to it or when the local objects are changed? It seems like one might end up with situations where the user doesn't save the first object, but the changes are moved to the second object which in turn might be saved => danger danger. – Mårten Sep 4 '14 at 6:08
  • @user1013159: DB=database in my answer, so yes, I am talking about database objects. A change event will be raised after any DB update transaction, and only if the transaction succeeds. And if you read my answer carefully again, you will see there is no room for "moving changes" to a second object locally (the event mechanism creates only change notifications including the object ID), if a listener wants to know what attributes have changed, he has to read the object from the database again. – Doc Brown Sep 4 '14 at 6:16
1

I think the real problem here is that you are allowing an implementation detail ( the relationship between a DBContext and interface tabs ) to restrict your thinking about the user experience of your application. There is nothing to tie your database connections methodology to the interface. In fact for practicality and testability you should definitely be decoupling them as far as you possibly can. The simple act of separating your data layer from your interface and implementing your logic as an intermediate layer would cause this problem to evaporate.

There is also something I don't quite understand about your second option - why would updating other tabs when the user saves overwrite their changes? Surely if you do it whenever a user saves, then it shouldn't overwrite their data at all? If the dependencies are strong enough that changing Item 2 on Tab 3 overwrites Item 1 on Tab 1, you need to go back to first principles and rethink your interface design - surprising users is bad, surprising users by overwriting data they previously input without letting making it obvious this will happen is a major no-no.

With all that said, I would be thinking about two things here- firstly user experience, secondly application structure.

If I am using a piece of software and I update it, I expect the software to reflect my update across the board. ( Tangential but important for architectural considerations, I also expect the software to be able to undo that change. )

From this we can immediately discount options A and C from your list. In case C if I open a tab and it indicates it is obsolete the only thing I can do is refresh it anyway.

I am strongly inclined to always do more work so that my users can do less work.

Thinking about B and D comes down to your application structure. If it is client-server, is your business logic on the server? If the business logic is on the client then you can go for D because you already have all the information you need to update neighbouring data available right there. If you have the business logic on the server then you're going to need to go back to the server to reload data in the other tabs.

Edited to add: You might want to provide some kind of locking facility, just to prevent two users editing the same thing at the same time. With a low user count you don't need to worry too much about having a lot of database or network traffic, so you can do this fairly safely.

  • If this was a product our company sold, I would absolutely agree with you, spending developing time to reduce user time is almost always the right choice in that case. This is an application that will only be user internally by ~5 people though, so I will have to consider developing time. Developing some kind of repository that keeps track of changes caused by the user and through what tab (and by other users, possibly), that allows partial saves (of tabs) and rollback would essentially mean dumping entity framework and build something better for my purposes.That seems too costly unfortunately – Mårten Sep 4 '14 at 13:24
  • The reason updates overwrite changes is that I currently have no way of doing updates that aren't complete.Updating the context will push the changes directly to the viewmodel and thus to the view.Notice that we're talking about one saved tab causing updates in other (potentially unsaved) tabs here.All business logic is in the client.Maybe I'm overestimating the amount of time I'd need to spend on building my own layer that handles partial saves (the user doesn't always want to save all tabs),rollback,consistency etc. This is my first project using EF or any ORM, so it's all a bit unfamiliar. – Mårten Sep 4 '14 at 13:42
  • Low user count and an EF back end suggest to me that you might as well update early and update often. I would also have some kind of polling facility to check when the last update was performed so that if User 1 is using the product and User 2 makes an update it can notify them and update their interface directly. I would be more worried about concurrency problems between users than between tabs on a single user's machine. – glenatron Sep 4 '14 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.