UXGridView Part II: Data Access the MVVM-way with QueryDescriptor

As Jimmy wrote in his blog post, our upcoming Grid control for Silverlight and WPF, UXGridView, allows you to perform data operation with MVVM pattern elegantly using QueryDescriptor. In this post, I will explain some of fundamental concepts of the QueryDescriptor in more practical ways.

Understanding QueryDescriptor

First let’s take a look the QueryDescriptor class below.

QueryDescriptor Class

As you can see, the QueryDescriptor has three properties that hold the information about the query. It also has a QueryChanged event that will be raised when any of the QueryDescriptor’s properties are changed. The following illustration shows some examples on how query information is stored in the QueryDescriptor.

QueryDescriptor queryDescriptor = new QueryDescriptor();

// filtering
// get records that have 
// (UnitPrice >= 0 AND UnitPrice < 50) OR (UnitPrice == 0)
CompositeFilterDescriptorCollection groupFilter1 = 
    new CompositeFilterDescriptorCollection();

groupFilter1.LogicalOperator = FilterCompositionLogicalOperator.And;
groupFilter1.Add(
    new FilterDescriptor() 
    { 
        PropertyName = "UnitPrice", 
        Operator = FilterOperator.IsGreaterThanOrEqualTo, Value = 0 
    }
);
groupFilter1.Add(
    new FilterDescriptor() 
    { 
        PropertyName = "UnitPrice", 
        Operator = FilterOperator.IsLessThan, Value = 50 
    }
);

CompositeFilterDescriptorCollection groupFilter2 = 
    new CompositeFilterDescriptorCollection();

groupFilter2.LogicalOperator = FilterCompositionLogicalOperator.And;
groupFilter2.Add(
    new FilterDescriptor() 
    { 
        PropertyName = "UnitsInStock", 
        Operator = FilterOperator.IsEqualTo, Value = 0 
    }
);

queryDescriptor.FilterDescriptors.LogicalOperator = 
    FilterCompositionLogicalOperator.Or;

queryDescriptor.FilterDescriptors.Add(groupFilter1);
queryDescriptor.FilterDescriptors.Add(groupFilter2);

// paging
// get the record 6 - 10
queryDescriptor.PageDescriptor.PageSize = 5;
queryDescriptor.PageDescriptor.PageIndex = 1;

// sorting
// sort by category ascending then by product id descending
queryDescriptor.SortDescriptors.Add(
    new SortDescriptor() 
    { 
        PropertyName = "CategoryID", 
        Direction = System.ComponentModel.ListSortDirection.Ascending 
    }
);
queryDescriptor.SortDescriptors.Add(
    new SortDescriptor() 
    { 
        PropertyName = "ProductID", 
        Direction = System.ComponentModel.ListSortDirection.Descending 
    }
);    

With our data controls, the QueryDescriptor will be updated automatically whenever users perform data operations through our data controls such as paging, filtering and sorting. So all you need to do here is simply wiring up the QueryDescriptor to the data controls and listen to its QueryChanged event.

In the QueryChanged function delegate, you will need to parse the information in QueryDescriptor to a data operation command for your specific data source. Fortunately, our data provider libraries come with some methods allowing you to easily parse the QueryDescriptor into WCF RIA or DevForce data service.

Next, I will show you how to bind the QueryDescriptor to UXGridView, listen to its QueryChanged and perform the data operation.

Binding QueryDescriptor to UXGridView using MVVM Pattern

First, let’s create the ViewModel for our example.

using System.Collections.Specialized;
using Intersoft.Client.Data.ComponentModel;

namespace UXGridView.Samples.ViewModels
{
    public class ListProductsViewModel : ViewModelBase
    {
        public ListProductsViewModel()
        {
            this._queryDescriptor = new QueryDescriptor();
        }

        private INotifyCollectionChanged _products;
        private QueryDescriptor _queryDescriptor;

        public INotifyCollectionChanged Products
        {
            get { return this._products; }
            set
            {
                if (this._products != value)
                {
                    this._products = value;
                    this.OnPropertyChanged("Products");
                }
            }
        }

        public QueryDescriptor QueryDescriptor
        {
            get
            {
                return this._queryDescriptor;
            }
            set
            {
                if (this._queryDescriptor != value)
                {
                    if (this._queryDescriptor != null)
                        this._queryDescriptor.QueryChanged 
                            -= new System.EventHandler(OnQueryChanged);

                    this._queryDescriptor = value;
                    this._queryDescriptor.QueryChanged 
                        += new System.EventHandler(OnQueryChanged);

                    this.OnPropertyChanged("QueryDescriptor");
                }
            }
        }

        public virtual void LoadProducts()
        {

        }

        private void OnQueryChanged(object sender, System.EventArgs e)
        {
            this.LoadProducts();
        }
    }
}        

Notice that the LoadProducts() is still empty now, we’ll get to that later. Next we will bind this to our UXGridView in our View.

<Intersoft:UXPage
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	xmlns:Intersoft="http://intersoft.clientui.com/schemas"
        xmlns:ViewModels="clr-namespace:UXGridView.Samples.ViewModels"
	x:Class="UXGridView.Samples.Views.UXGridView.ListProducts"
	Title="ListProducts Page"
	d:DesignWidth="640" d:DesignHeight="480">

    <Grid x:Name="LayoutRoot">
        <Grid.DataContext>
            <ViewModels:ListProductsViewModel/>
        </Grid.DataContext>

        <Grid MaxWidth="700" Margin="12">
            <Intersoft:UXGridView 
                Margin="8" AutoGenerateColumns="False" 
                ItemsSource="{Binding Products}"
                QueryOperation="Server"
                SortDescriptors="{Binding QueryDescriptor.SortDescriptors}"
                PageDescriptor="{Binding QueryDescriptor.PageDescriptor}" 
                PageSize="20" CanUserPage="True">
                <Intersoft:UXGridView.Columns>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Category ID" 
                        Binding="{Binding CategoryID}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Product ID" 
                        Binding="{Binding ProductID}" 
                        Aggregate="Count" FooterFormatString="Count = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Product Name" 
                        Binding="{Binding ProductName}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Unit Price" 
                        Binding="{Binding UnitPrice}" 
                        Aggregate="Avg" FooterFormatString="Avg = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Units In Stock" 
                        Binding="{Binding UnitsInStock}" 
                        Aggregate="Max" FooterFormatString="Max = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Units On Order" 
                        Binding="{Binding UnitsOnOrder}" 
                        Aggregate="Min" FooterFormatString="Min = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Quantity Per Unit" 
                        Binding="{Binding QuantityPerUnit}"/>
                </Intersoft:UXGridView.Columns>
            </Intersoft:UXGridView>
        </Grid>
    </Grid>
</Intersoft:UXPage>

Note that you will need to set the QueryOperation to Server to enable server-side data operation. That’s required because UXGridView also has the capability to manipulate the data at client side, which is the default setting. For now, let’s focus on the server-side query mode.

The QueryChanged event of the QueryDescriptor will be raised when it is bind to any of our data controls such as UXGridView, UXDataFilter or UXDataPager. The event will also be raised whenever there are changes in the QueryDescriptor, so it is the only place where you want to handle all data operations.

Now, let’s start processing this QueryDescriptor and retrieve a piece of data from our repository.

Parsing QueryDescriptor and Retrieving Data from WCF RIA

To parse the QueryDescriptor to WCF RIA, you need to include the Intersoft.Client.Data.Provider.Ria assembly in your project. This data provider assembly provides several methods that will allow you to easily parse the QueryDescriptor to the WCF RIA data service.

Now, let’s parse our QueryDescriptor and write some code to load the data in the LoadProducts() method that we’ve prepared in the previous section.

private NorthwindDomainContext _manager;
private NorthwindDomainContext Manager
{
    get
    {
        if (this._manager == null)
            this._manager = new NorthwindDomainContext();

        return this._manager;
    }
}

public virtual void LoadProducts()
{
    if (Intersoft.Client.Framework.ISControl.IsInDesignModeStatic)
        return;


    var query = this.Manager.GetProductsQuery()
                         .OrderBy(p => p.ProductID)
                         .Parse(this.QueryDescriptor);
    query.IncludeTotalCount = true;


    this.Manager.Load(
        query,
        op =>
        {
            if (op.IsComplete)
            {
                this.Products = new PagedCollectionView(op.Entities);
                if (op.TotalEntityCount != -1)
                    this.QueryDescriptor.PageDescriptor.TotalItemCount 
                        = op.TotalEntityCount;
            }
            else
            {
                // error handling
            }
        },

        true);
}

private void OnQueryChanged(object sender, System.EventArgs e)
{
    this.LoadProducts();
}
        

As you can see, the implementation is very straightforward, you just need to call .Parse(this.QueryDescriptor) to produce a query that WCF RIA can process. Note that IncludeTotalCount is set to True, which is important in paging scenarios as we will need to set the QueryDescriptor.PageDescriptor.TotalItemCount property to the total entity count of the particular query. This enables the data pager UI to determine the total number of pages available.

Parsing QueryDescriptor and Retrieving Data from Dev Force

In addition to the WCF RIA, you can also parse the QueryDescriptor to a DevForce service using similar approaches, with minor adjustments. First, you need to include the Intersoft.Client.Data.Provider.DevForce assembly in your project. This data provider provides similar methods as available in the RIA counterpart. Next, you need to adjust some of the code such as declaring the relavant DevForce type, see below.

private NorthwindEntities _manager;
private NorthwindEntities Manager
{
    get
    {
        if (this._manager == null)
            this._manager = new NorthwindEntities();

        return this._manager;
    }
}

public virtual void LoadProducts()
{
    if (Intersoft.Client.Framework.ISControl.IsInDesignModeStatic)
        return;

    this.Manager.Products
        .OrderBy(p => p.ProductID).Parse(this.QueryDescriptor)
        .ExecuteAsync(
        op =>
        {
            if (op.CompletedSuccessfully)
            {
                this.Products = new PagedCollectionView(op.Results);
            }
            else
            {
                // error handling
            }
        }
        );                 
}

public virtual void GetTotalItemCount()
{
    var queryCount = this.Manager.Products
        .Parse(this.QueryDescriptor, false).AsScalarAsync().Count();

    queryCount.Completed += (o, e) =>
    {
        if (e.Result != -1)
            this.QueryDescriptor.PageDescriptor.TotalItemCount = 
                e.Result;
    };    
}

private void OnQueryChanged(object sender, System.EventArgs e)
{
    this.GetTotalItemCount();
    this.LoadProducts();
}

Notice that you need to retrieve the total item count in two separate calls. This is required since DevForce handles the total item count retrieval differently.

I hope you agree that the QueryDescriptor makes MVVM and data service significantly easier and straightforward to implement. Also, remember that the QueryDescriptor can be shared across multiple controls such as UXDataFilter and UXDataPager, in addition to the UXGridView.

In my next post, I will cover about some nice Grid features that we shipped in the first CTP release. For now, download the CTP1 bits here, and enjoy building your data-centric application the MVVM way.

Best Regards,

Andry

Advertisements

5 thoughts on “UXGridView Part II: Data Access the MVVM-way with QueryDescriptor

  1. Pingback: UXGridView Part 3: Fundamental Grid Features | Intersoft Solutions Corporate Blog

  2. Pingback: UXGridView CTP2: MVVM Data Editing | Intersoft Solutions Corporate Blog

  3. Pingback: UXGridView RC Adds MVVM Data Exporting, Multi Aggregates, Selectors, and more | Intersoft Solutions Corporate Blog

  4. Pingback: ClientUI vNext Roadmap Unveiled | Intersoft Solutions Corporate Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s