Stuck with a DataBinding, coupling, design problem.

Topics: CAB & Smart Client Software Factory
Jan 28, 2007 at 6:16 AM
I will try to explain as clearly as I can, but it's gonna be tough ;)

I have a view where I want to display a list of Work Orders. I've gone about everything in the correct CAB fashion so far. I'm using a 3rd party OR/MAPPER library called EntitySpaces. I have generated an Entity for my Sales Order data. I would also like to display an image of the product and some customer information in the grid. These properties are not part of the 'WorkOrder' business object. The WorkOrder object has FKs to the customer data and product data.

Late last night my first attempt involved catching the DataGridView's RowsAdded event and fetching the Product data from my Products Service and the Customer data from my Customer Service. I did this in the VIEW class which of course I later realized is a big CAB no-no.

My next solution was to add a couple Presenter methods to get the data I needed and call these methods from the RowsAdded handler in the View.
// in the rows Added handler
Product product = _presenter.GetOrderProduct(productID);
dgv.Rowse.RowIndex.Cells0.Value = product.Image;

Customer customer = _presenter.GetCustomer(customerID);
dgv.Rowse.RowIndex.Cells1.Value = customer.Name;

// etc

I seem to recal that in a pure MVP implementation the View shouldn't "pull" data, but rather notify the Presenter that something happened and let the Presenter "push" the model (or other data) to the View. If this is true, my above solution isn't gonna fly.

The only way I can think to stick with the correct "View-Notify Presenter-Push Data to the View" workflow would be if the View called something like this:
_presenter.RowAdded(DataGridRow row);

Then the Presenter collected the Product and Customer data from various services, filled in the DataGridRow and pushed it back to the View. This seems ugly and doesn't feel right either.

I suppose one other option could be:
1) View notifies Presenter it's adding a row and passes whatever ID the presenter needs to get the correct data
2) View stores the index of the row it's adding in a member variable
3) Presenter gathers data and pushed the Product and Customer object to the View
4) View uses the stored row index to set the cell values from the data sent from the Presenter

How does that sound?

Ahhh, HELP! ;)
Jan 28, 2007 at 6:19 AM
This forumn system is lame-O - Can't edit, can't format code snippets, etc. Too bad.

Anyway, I wanted to edit my post but I can't, so I needed to reply to say that storing the Product and Customer data in the WorkOrder item isn't an option. I could take a long time to tell you why, but trust me, I need to fetch the Product and Customer data for each row at "Bind Time"

Jan 29, 2007 at 5:23 AM

I know you said you can't store the product & customer information in the WorkOrder object, but at a fundamental level this is a business object problem. I'm guessing the reason you can't store the data that way is because you are using the O/R mapper objects directly?

This is what custom business objects were invented for. At our place, we have the same deal: we have an O/R mapper that maps tables nicely to objects with lots of built-in features. And if you ask nicely, the O/R mapper will return entire graphs of object hierarchies. But to keep ourselves from being tied to that vendor's objects (in case we decide to jump to Linq, or something else presents itself later as a better solution in our DAL) we translate all of the entities fetched into our own custome business objects. Our business objects implement BindingList<T> (when they are collections) so that we can bind them directly to grids for cases just as you mention.

Now.. if you absolutely can't make your own custom WorkOrder objects to encapsulate the proper Product and Customer objects, then there's other ways to accomplish this, but you may want to adjust your strategy a bit, because what you propose sounds very chatty with the server (basically having the Presenter make a request to the DAL every time a RowAdded event fires - I'd steer clear of that sort of implementation if you can).

If you know what objects you need (the primary keys to the WorkOrders are in your possession) then you should really only have to make a maximum of three calls to the DAL: (1) to grab the WorkOrders, (2) to fetch the Products for all WorkOrders and (3) to fetch all Customers related to the WorkOrders. Then, using the FK information off the WorkOrders, you should be able to assemble all of those items however you need them on the client side, in the Presenter, before passing them to the view for Binding.

Jan 29, 2007 at 7:15 AM
Hi Chris, thanks for the reply.

I should have been more clear: I can change the Business Objects, but to start doing that would invalidate much of my existing design. I've utilized Services as mush as I can. My Product Service already has all our products and the Customer Service already has all the customers, etc, etc.

If I were to add Product and Customer references to my Build Order, I would need to pre-process them to set the references befor binding to the control - I have done this in the past and what happens is when the Product or Customer collection are changed, the other Business Entities don't have valid references anymore.

I can see why you were concerned thinking I would hit the DB for each order to get the Product and Customer, but luckily that is not the case.

But now you have me thinking that maybe I should store the references to the Product and Customer in the Build Order....? The objects I'm using (Entity Spaces) are really nice, they have hierarchical support that is amazing so I already have a Lazy-Loaded reference to the Product and Customer, but both of those have BLOBS and if I'm going to show a list of 40+ Build Order for a particular product, there is no point in loading the BLOB 40+ times.

Hmmm... more to think about.

Thanks again for the post!