WPF DataModel for CSLA?

WPF DataModel for CSLA?

Old forum URL: forums.lhotka.net/forums/t/2055.aspx


Dirk.Rombauts posted on Sunday, December 31, 2006

I read the CSLA.NET 2.0 book (C# version) some six months ago, and now I started reading it again in preparation of finally getting my hands dirty with Csla.

I have also been immersing myself into WPF, specifically by reading Petzold's book and trying out the archticture proposed by Dan Crevier (http://blogs.msdn.com/dancre/archive/2006/10/11/datamodel-view-viewmodel-pattern-series.aspx).  One comment Dan makes when talking about the DataModel is "When data is expensive to fetch, it abstracts away the expensive operations, never blocking the UI thread (that is evil!)".

At first sight, CSLA objects seem perfect as DataModels: they implement INotifyPropertyChanged, and a lot more besides. But: if I understand CSLA correctly, everything runs synchronously on the main (UI) thread by default.  If I understand CSLA correctly, there's nothing in CSLA that enables me to perform expensive operations (like network hops) asynchronously.

For Windows Forms 2.0, it's of course possible to use the Background Worker component.  For ASP.NET 2.0, it's possible to use asynchronous pages.  Both techniques prevent blocking the UI thread, be it Windows Forms UI or the ASP.NET working process thread pool.  But now we have WPF, and as far as I know, there's no Background Worker component/class/whatever for WPF.

Since Dan Crevier specifically states "All of [the DataModel's] public APIs must be called on the UI thread only", I'm forced to make a decision here.

Alternative 1): use CSLA objects as DataModel on the UI thread, and live with the fact that I block the UI thread.  This is a big trade-off, because there's a lot going on behind the scenes with Dependency Properties and the like. So my assumption is that blocking the UI thread in WPF is a far worse offense than in WinForms.

Alternative 2): use CSLA objects on a background thread, and hope WPF figures out the databinding across threads.  This would blatantly disregard Dan's statement about public API on the UI thread.

Alternative 3): Implement a proxy class for each CSLA object I want to use, abstracting away expensive operations and such.  This would have the advantage of being able to convert all properties to dependency properties.  But it has the same huge disadvantages of the "Class in Charge" pattern.  Even with a generator, this would be a non-trivial thing to implement.

Alternative 4): Create a DataModel<T> where T : BusinessBase<T> that exposes a property "Entity" of type BusinessBase<T> with our business object.  DataModel<T> would then call the "expensive" operations on a background thread.  WPF Databinding should then prefix all path strings with "Entity".

Alternative 4 seems like the lesser evil to me.

Have you encountered such a situation?  Do you see any other alternatives?  What did or would you do?  Thanks!


 

Paul Czywczynski replied on Sunday, December 31, 2006

Here is some basic code we prototyped with. It covers async databinding and saving. We're binding to plain 'ol CSLA business objects.

        void Details_DataBind()
        {
            if  (PageJournal.Current.PrimaryKey != null)
            {
            using (_odp.DeferRefresh())
            {
                _odp.ObjectType = typeof(EmployeeDetails);
                _odp.MethodName = "GetEmployeeDetails";
                _odp.IsAsynchronous = true;
                _odp.DataChanged += new EventHandler(_odp_DataChanged);
            }

            this.MainGrid.DataContext = _odp;
            }
        }

        void _odp_DataChanged(object sender, EventArgs e)
        {
            if (_odp.Error != null)
                throw _odp.Error;
        }


        public void Save()
        {
            BackgroundWorker bwSave = new BackgroundWorker();
            bwSave.DoWork += new DoWorkEventHandler(bwSave_DoWork);
            bwSave.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwSave_RunWorkerCompleted);
            bwSave.RunWorkerAsync();
        }

        void bwSave_DoWork(object sender, DoWorkEventArgs e)
        {
            using (StatusBusy busy = new StatusBusy("Saving..."))
            {
                // get business object from data provider
                EmployeeDetails data = (EmployeeDetails)_odp.Data;

                // validate that there are no broken rules
                if (data.BrokenRulesCollection.Count > 0)
                    MessageBox.Show(data.BrokenRulesCollection.ToString());

                if (data.IsSavable)
                {
                    EmployeeDetails temp = data.Clone();
                    temp.ApplyEdit();
                    try
                    {
                        data = temp.Save();
                        _odp.Refresh();
                    }
                    catch (Csla.DataPortalException ex)
                    {
                        MessageBox.Show(ex.BusinessException.ToString(), "Error saving", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                    }

                }
            }
        }

        void bwSave_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
                throw e.Error;
        }

Dirk.Rombauts replied on Monday, January 01, 2007

Thanks for your reply.  I have a couple of questions, though.

What exactly is _odp?  A CSLA object? (but then, where does "IsAsynchronous" come from?)  A custom object?

_odp serves as datacontext for the MainGrid.  That means all its public API should be called on the UI thread.  Yet you call _odp.Refresh() in bwSave_DoWork, which is called on a different thread.  Or does _odp.Refresh automatically invoke itself on the UI thread if needed?

Paul Czywczynski replied on Monday, January 01, 2007

Sorry, I forgot to post the declaration of _odp.
        private ObjectDataProvider _odp = new ObjectDataProvider();

The ObjectDataProvider automatically checks which thead it is on and will invoke across threads if it needs to. We prefer to use it because it is so much easier to use across threads because we don't need to think about it.

http://www.beacosta.com/2006/03/why-should-i-use-objectdataprovider.html


Dirk.Rombauts replied on Tuesday, January 02, 2007

Great.

To summarize: when using the ObjectDataProvider, I sort of get the combined benefits of Windows Forms's BackGroundWorker and Object Binding.  ObjectDataProvider can be made to work asynchronously, so I can use CSLA as it is.  Moreover, I don't need to prefix my binding paths with "Entity" or something because the ObjectDataProvider knows it should defer binding paths to the object it is wrapping.

This sounds like the best of both worlds to me. Now all I have to do is create a subclass of ObjectDataProvider that knows which CSLA methods to call and I'm all set.

Copyright (c) Marimer LLC