ComponentOne Data Source for Entity Framework
Design-Time Features / Simplifying MVVM
In This Topic
    Simplifying MVVM
    In This Topic

    The Model-View-View-Model (MVVM) pattern is gaining in popularity as developers realize the benefits it gives their applications, making them easier to maintain and test and, particularly in the case of WPF and Silverlight applications, allowing a much clearer division of labor between the designer of the UI and the creator of the code that makes it work. However, without effective tools to aid program development based on MVVM patterns, programmers can actually find themselves working harder because of the need to implement an extra layer (the View Model) and ensure that data is properly synchronized between that and the Model. This extra burden isn’t onerous when you have a relatively small application with just a few simple collections (and best practice with MVVM is to use ObservableCollection as the datasource), but the bigger it becomes and the more collections it spawns, the worse it becomes. DataSource for Entity Framework can ease the burden.

    C1DataSource lets you use live views as your view model. Just create live views over your model collections and use them as your view model. Live views are synchronized automatically with their sources (model), so you don’t need any synchronization code - it’s all automatic. And live views are much easier to create than to write your own view model classes using ObservableCollection and filling those collections manually in code. You have all the power of LINQ at your disposal to reshape model data into live views. So, not only does synchronization code disappear, but the code creating view models is dramatically simplified.

    To demonstrate how easy it is to follow the MVVM pattern using live views, let’s create a form combining all features from two previous examples: the Category-Products master-detail from Working with DataSources in Code and the reshaping/filtering/ordering of data from Live Views. It will be a Category-Products view, showing non-discontinued products whose unit price is at least 30, ordered by unit price, and displaying a customized set of product fields in a master-detail form where the user can select a category to show the products of that category. We'll follow the MVVM pattern. The form (view), called CategoryProductsView, only hosts GUI controls (a combo box and a grid) and does not have any code except what is used to set the data source to the view model, like this:

    To write code in Visual Basic

    Visual Basic
    Copy Code

    PublicClassCategoryProductsView

         PrivateviewModel AsCategoryProductsViewModel= NewCategoryProductsViewModel()

         PublicSubNew()
             InitializeComponent()

             comboBox1.DisplayMember = "CategoryName"
             comboBox1.ValueMember = "CategoryID"
             comboBox1.DataSource = viewModel.Categories
             dataGridView1.DataSource = viewModel.Products

         EndSub
     EndClass

     

    To write code in C#

    C#
    Copy Code

    publicpartialclassCategoryProductsView: Form
     {
         CategoryProductsViewModelviewModel = newCategoryProductsViewModel();

         publicCategoryProductsView()
         {
             InitializeComponent();

             comboBox1.DisplayMember = "CategoryName";
             comboBox1.ValueMember = "CategoryID";
             comboBox1.DataSource = viewModel.Categories;
             dataGridView1.DataSource = viewModel.Products;
         }
     }

     

    All logic is in the view model class, separate from the GUI. More exactly, there are three classes: CategoryViewModel, ProductViewModel, and CategoryProductsViewModel. The first two are simple classes defining properties with no additional code:

     

    To write code in Visual Basic

    Visual Basic
    Copy Code

    PublicClassCategoryViewModel
         PublicOverridablePropertyCategoryID AsInteger
         PublicOverridablePropertyCategoryName AsString
     EndClass


     Public ClassProductViewModel
         PublicOverridablePropertyProductID AsInteger
         PublicOverridablePropertyProductName AsString
         PublicOverridablePropertyCategoryID AsInteger?
         PublicOverridablePropertyCategoryName AsString
         PublicOverridablePropertySupplierID AsInteger?
         PublicOverridablePropertySupplierName AsString
         PublicOverridablePropertyUnitPrice AsDecimal?
         PublicOverridablePropertyQuantityPerUnit AsString
         PublicOverridablePropertyUnitsInStock AsShort?
         PublicOverridablePropertyUnitsOnOrder AsShort?
     EndClass

       

     

    To write code in C#

    C#
    Copy Code
    public classCategoryViewModel

    {

        public virtual int CategoryID { get; set; }

        public virtual string CategoryName { get; set; }

    }

    public classProductViewModel

    {

        public virtual int ProductID { get; set; }

        public virtual string ProductName { get; set; }

        public virtual int? CategoryID { get; set; }

        public virtual string CategoryName { get; set; }

        public virtual int? SupplierID { get; set; }

        public virtual string SupplierName { get; set; }

        public virtual decimal? UnitPrice { get; set; }

        public virtual string QuantityPerUnit { get; set; }

        public virtual short? UnitsInStock { get; set; }

        public virtual short? UnitsOnOrder { get; set; }

    }

       

    And here is the code for the CategoryProductsViewModel class:

     

    To write code in Visual Basic

    Visual Basic
    Copy Code
    PublicClassCategoryProductsViewModel

         Private_scope AsEntityClientScope

         Private_categories AsBindingSource
         PublicPropertyCategories AsBindingSource
             Get
                 Return_categories
             EndGet
             PrivateSet(value AsBindingSource)
                 _categories = value
             EndSet
         EndProperty

         Private_products AsBindingSource
         PublicPropertyProducts AsBindingSource
             Get
                 Return_products
             EndGet
             PrivateSet(value AsBindingSource)
                 _products = value
             EndSet
         EndProperty

         PublicSubNew()
             _scope = Program.ClientCache.CreateScope()

             DimCategoriesView AsObject=
                 Fromc In_scope.GetItems(OfCategory)()
                 SelectNewCategoryViewModelWith
                 {
                     .CategoryID = c.CategoryID,
                     .CategoryName = c.CategoryName
                 }
             Categories = NewBindingSource(CategoriesView, Nothing)

             DimProductsView AsObject=
                 Fromp In_scope.GetItems(OfProduct)().AsFilteredBound(Function(p) p.CategoryID.Value).BindFilterKey(Categories, "Current.CategoryID").Include("Supplier")
                 SelectNewProductViewModelWith
                 {
                     .ProductID = p.ProductID,
                     .ProductName = p.ProductName,
                     .CategoryID = p.CategoryID,
                     .CategoryName = p.Category.CategoryName,
                     .SupplierID = p.SupplierID,
                     .SupplierName = p.Supplier.CompanyName,
                     .UnitPrice = p.UnitPrice,
                     .QuantityPerUnit = p.QuantityPerUnit,
                     .UnitsInStock = p.UnitsInStock,
                     .UnitsOnOrder = p.UnitsOnOrder
               }
              Products = NewBindingSource(ProductsView, Nothing)

         EndSub
     EndClass

       

    To write code in C#

    C#
    Copy Code
    public classCategoryProductsViewModel

    {

        private C1.Data.Entities.EntityClientScope _scope;

        publicBindingSource Categories { get; private set; }

        public BindingSource Products { get; private set; }

        public CategoryProductsViewModel()

        {

            _scope = Program.ClientCache.CreateScope();

            Categories = new BindingSource(

                from c in _scope.GetItems<Category>()

                select new CategoryViewModel()

                {

                    CategoryID = c.CategoryID,

                    CategoryName = c.CategoryName

                }, null);

            Products = new BindingSource(

                from p in _scope.GetItems<Product>().AsFilteredBound(p =>
                p.CategoryID).BindFilterKey(Categories,
                     "Current.CategoryID").Include("Supplier")

                select new ProductViewModel()

                {

                    ProductID = p.ProductID,

                    ProductName = p.ProductName,

                    CategoryID = p.CategoryID,

                    CategoryName = p.Category.CategoryName,

                    SupplierID = p.SupplierID,

                    SupplierName = p.Supplier.CompanyName,

                    UnitPrice = p.UnitPrice,

                    QuantityPerUnit = p.QuantityPerUnit,

                    UnitsInStock = p.UnitsInStock,

                    UnitsOnOrder = p.UnitsOnOrder

                }, null);

            }

    }

    Basically, it contains just two LiveLinq statements, nothing more. The statements (creating live views, see Live Views) are wrapped in BindingSource constructors to add currency, current item, support to the Categories and Products collections exposed by the view model class. Note that using BindingSource is only necessary in WinForms, because the WinForms platform is not as well suited for MVVM as WPF or Silverlight. Note also that because we use BindingSource, you need to add the following statement to the code file (in WinForms only):

    using System.Windows.Forms;
    

    Similar to what we saw in Working with DataSources in Code, using AsFilteredBound() gives us server-side filtering. We also connected the filter key, which is the Product.CategoryID property, to the CategoryID selected by the user using a combo box event. Here we can’t do this because we must keep our code independent of the GUI. So we use a BindFilterKey method to bind the filter key to the Category.CategoryID property of the item currently selected in the Categories collection. This is one reason why we need to support currency in the Categories collection and why we wrapped it in a BindingSource to get currency support in WinForms.

    The Include ("Supplier") operator is not strictly necessary; it is used here for performance optimization. Without it, Entity Framework will fetch Supplier objects lazily, one-by-one, every time the user accesses an element of the Products collection whose Supplier was not yet fetched. This can cause delays, and it’s generally much less efficient to fetch data in single rows rather than in batches, so we opted out of lazy loading here using Include("Supplier"), which tells the Entity Framework to fetch supplier information in the same query with products.