Combine two databases/domain services in one View

Sep 16, 2010 at 8:36 AM
Edited Sep 16, 2010 at 8:38 AM

I need to combine information from two different databases, into one view. 

Use case:

  • A list of sub-elements in the CurrentItem of the PagedCollectionView (a large datagrid), defines what rows should be collected from another database/context. 
  • The result from the second database query will be presented in the SelectedDetailsView of the CurrentItem (Datagrid SelectedItem)

So - with two databases, two domain services/context, and two ViewModels for the same view, what will be the best design?

Thoughts?

Coordinator
Sep 16, 2010 at 9:16 PM

Here is how I have solved this issue in my applications using the MVVMS framework:  

Option #1:

Add both services into one client-side service model (you will end up with 2 ContextManagers and 2 ServiceCotexts), then expose both PagedCollectionViews into a single viewmodel.  This makes things simple sincey you can create a filter, and consume the CurrentItemChanged event on the master pagedcollectionview.  Here is an example of how I changed this in my class to support mapping 2 services into a single client data-service-model:

        #region Basic Service Implementation
        #region Private classes - do not change variable names

        // This provides management functions for the Project's Domain context
        ContextManager contextManager;
        ContextManager searchManager;

        // TODO: Replace this type with your project's provided DomainContext
        WorkContext domainContext;
        SearchContext searchContext;

        // TODO: Replace this type with your project's provided DesignData Service
        static WorkDesignData designData;

        #endregion

        public WorkService()
        {
            if (DesignerProperties.IsInDesignTool)
            {
                if (designData == null)
                {
                    designData = new WorkDesignData();
                    //designData.SetupSamples();
                }
            }
            else
            {
                domainContext = new WorkContext();
                searchContext = new SearchContext();

                contextManager = new ContextManager(domainContext, OnError);
                searchManager = new ContextManager(searchContext, OnError);
            }
        }

Option #2:

Another approach that also works easily in the framework is to leverage 2 already built viewmodels (which it sounds like you may already have).  Then Create a Notification with the Notifications Class to send a message between them (note: this class is static, no instance needed).  The key here is to use the WeakEventHandler to create the reference, to allow for resource cleanup.

For example:

In the Details ViewModel (to recieve a mesage such as some item's id):

        /// <summary>
        /// Add this to your Constructor in your View Model
        /// </summary>
        void setupNotifications()
        {
            SL.MVVMS.RIA.Notifications.ReceiveNotification +=
                new WeakEventHandler<NotificationEventArg>(notification_Received).Handler;
        }

        /// <summary>
        /// Process all your Notifications Here
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void notification_Received(object sender, NotificationEventArg args)
        {
            // Note: This must be public to allow the WeakEventHandler
            // to provide the delegate

            // DONE: Process Messages Recieved Here
            if (args.Message == "Lookups Refreshed")
            {
                OpenTickets.Refresh();
            }
        }

Then in your master item's view model, you can use some code in your PagedCollectionView's CurrentItemChanged event handler to send a notification to the detail View Model like this:

// Notify any other ViewModels that this refresh was completed 
Notifications.SendNotification(this, "Notifications Refreshed", null, null);
Sep 16, 2010 at 11:04 PM
Edited Sep 16, 2010 at 11:17 PM

Thank you, for both options. One follow up on Option 2:

How would choose to pass the payload from the sender, payload being the list of ID's to include in the query on the foreign database.

Sample code would be great ..

Coordinator
Sep 16, 2010 at 11:37 PM
Edited Sep 17, 2010 at 12:16 AM

Here you go - let me know if this is not clear and I can try to clarify.

In the Parent Item (in this case my "OfficeList") which is a PagedCollectionView

                // Bind to the PagedCollectionView's CurrentChanged property
                OfficeList.CurrentChanged += (s, e) =>
                    {
                        Office masterItem = OfficeList.CurrentItem as Office;

                        // Get the List of Items from the Masteritem's properties
                        int[] tickets = masterItem.Tickets
                            .Select(t => t.TicketID).ToArray();

                        // Send the Notification (don't do both)
                        Notifications.SendNotification(this, "Tickets To Show", tickets, null);

                        // Another way to do this with a callback (don't do both)
                        Notifications.SendNotification(this, "Tickets To Show", tickets, () =>
                            {
                                System.Windows.MessageBox.Show("Ticket details loaded elsewhere.");
                            });
                    };
Then in my Detail (reciever of the notification):
        public OpenTicketViewModel()
        {
            dataService = new HelpdeskService();

            // DONE: Add setupCommand and setupCollection functions here
            // to construct those items as needed
            setupOpenTicketsCollection();
            loadOpenTicketsCollection();
            setupNotifications();
        }

        /// <summary>
        /// Add this to your Constructor in your View Model
        /// </summary>
        void setupNotifications()
        {
            SL.MVVMS.RIA.Notifications.ReceiveNotification +=
                new WeakEventHandler<NotificationEventArg>(notification_Received).Handler;
        }

        /// <summary>
        /// Process all your Notifications Here
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void notification_Received(object sender, NotificationEventArg args)
        {
            // Note: This must be public to allow the WeakEventHandler
            // to provide the delegate

            // DONE: Process Messages Recieved Here
            if (args.Message == "Tickets To Show")
            {
                OpenTickets.Refresh();
                if (args.Callback != null)
                    args.Callback();
            }
        }
Sep 17, 2010 at 12:30 PM
Edited Sep 17, 2010 at 8:27 PM

Brilliant.

I've decided to keep the viewmodels separated for the most part, but I will include a second datacontext into ViewModel 1, for seamless access to the two tables that is related to the rest of the data in Viewmodel 1.

The rest I will leave in the second viewmodel since it really belongs to other Views.

By the way - by looking at your recent check-ins, it looks like you are working on the ViewCollection/IList issue? :-)