Serializing XamPivotGrid views in Silverlight

Atanas Dyulgerov / Thursday, July 15, 2010

The XamPivotGrid control is an interactive tool. The data it visualizes changes according to the preferences of the user and they usually change during runtime. Finding information through data mining requires actions such as slicing, drilling down, dicing, etc. The XamPivotGrid allows the user to perform those actions, but the data remains intact – only the view changes. So at some point you might want to go back to a specific view that was defined by a number of filters, several dimension hierarchies as columns and some hierarchies as rows. Setting those criteria manually can be a tedious job and sometimes even impossible. So how do we save that view and how do we restore it later?

This article gives three solutions to this task. First we will deal with saving the details of a pivot grid slice in memory using no serialization. This will be a base step that will allow us to proceed with the persistence of the data to an XML file (XML serialization) or to a database (binary serialization).

Before we dive deeply into the implementation we need to know the milestones that we’ll go through. The first important step is to understand what we need to save in order to be able to reconstruct the view of the pivot grid, given that we are using the same DataSource.

What to save

The DataSource is the place where all data is contained. XamPivotGrid is a control that only visualizes the data from the DataSource and the XamPivotDataSelector is a control that only helps selecting the different settings of the slice you want to create. It is the DataSource itself that you want to work with in order to save the current view. The main elements that define a slice are the following:

  • Data cube from the data source
    • Selected either from code or the data selector
  • Dimension hierarchies from the selected cube
    • Assigned to the Row, Column and Filter (Slicer) axes
  • Measures from the selected cube
    • Assigned to the Measures area
  • Filter members within each selected hierarchy
    • Selection state in the filter menus
    • Expansion state in the header cell areas

If we want to be able to completely recreate the view of the Pivot Grid we need to save the identifying details for all those elements. That is valid for all types of DataSources, but it is not always enough. If we are using FlatDataSource (as we will be throughout this article) it will be all we need to save, but if we are using for example XMLADataSource we also have to save the ServerURI and the Database name. We must make sure that we are using the same DataSource before we start loading a saved view.

Now that we know what we have to save to recreate a complete slice we need a convenient place to store the identifying details.

How to save it

Each of the elements mentioned above has UniqueName. It is a string and when time comes for reloading the view we can find the corresponding element in the data and assign it to the appropriate place by using that UniqueName. This is the most practical approach to saving views as the stored info is small (it’s just text) and it can be easily serialized as you will see later in this article.

Storing the selected hierarchies and the selected cube can be done by just storing the UniqueName strings in a collection and the SelectedIndex integer in an int variable. The expansion and selected states however require being stored in pairs – a Boolean value of the state and the UniqueName string that the value corresponds to. We can create a custom class that has the two properties. We also must provide a parameterless constructor in order to make the class serializable. In the sample that I have attached this class is called CustomDictItem.

All these collections with properties and variables can be stored in another custom class. It will allow us to add other useful information to each particular view save, like name of the save, timestamp, etc. The class I refer to is called SavedPivotGridView in the sample attached. Other benefit of storing all data related to one save in one class is that we can add methods to save and to load the specific view. The code will be nicely organized and when we are ready we will have to serialize only the separate instances of that class – which ultimately is the goal of this article.

Here is a snippet of the region with properties that save all useful data for one save:

    public class SavedPivotGridView
    {
        #region SavedInformation
 
        private stringtimestamp;
        public stringTimestamp
        {
            get { returntimestamp; }
            set { timestamp = value;}
        }
 
        public intSelectedCubeIndex;
 
        //unique names of each row, column, filter and measure thatwill be saved
        public List<string> RowHierarchies;
        public List<string> ColumnHierarchies;
        public List<string> FilterHierarchies;
        public List<string> Measures;
 
        //unique name of each member to save and its IsExpandedproperty
        public List<CustomDictItem> FilterMembersToExpand;
        //unique name of each member to save and its IsSelectedproperty
        public List<CustomDictItem> FilterMembersToLoad;

        #endregion SavedInformation
    }

Note that all properties are public so we can serialize them later. Here is the CustomDictItem class code:

    public class CustomDictItem
    {
        public string UniqueName;
        public bool Value;
 
        //a parameterless constructor is needed in order to make this class serializable
        public CustomDictItem()
        {
 
        }
 
        public CustomDictItem(string UniqueName, bool Value)
        {
            this.UniqueName = UniqueName;
            this.Value = Value;
        }
    }

The save view method

After we have prepared the properties to hold the saved data we can proceed with populating them. A convenient approach is to create a public method SaveView that gets the XamPivotGrid as an argument and in its body we extract all needed info and put it in the respective properties.  You can find all code in the SaveMethods region of the SavedPivotGridView.cs file in the sample attached.

Before we do anything in the SaveView method we need to check if the pivotGrid.DataSource is null and the pivotGrid.DataSource.Cube is null. If either of those items is null just quit the method. If those are null that means there is no loaded data and thus nothing to save.

After we have done our little sanity check we proceed with storing the selected cube. We can take advantage of the fact that all cubes that the DataSource provides are stored in the Cubes property. So in order to restore the selected cube we only need its index in the Cubes property.

            this.SelectedCubeIndex =
                        pivotGrid.DataSource.Cubes.IndexOf(pivotGrid.DataSource.Cube); 

This should suffice for the cube. We continue with saving all loaded dimension hierarchies. There are three axes that hold hierarchy data and they are called Rows, Columns and Filters.

Each axis is actually a collection of IAreaItemViewModel items (selected hierarchies). What we can do is go through all of those and put their respective UniqueNames in the hierarchy collections. Like this:

            this.RowHierarchies = new List<string>();
            foreach (IAreaItemViewModel item in pivotGrid.DataSource.Rows)
            {
                var FilterViewModel = item as IFilterViewModel;
                if (FilterViewModel != null)
                {
                    this.RowHierarchies.Add(FilterViewModel.Hierarchy.UniqueName);
                }
            }

Do the same for pivotGrid.DataSource.Columns and pivotGrid.DataSource.Filters

Measures work in essentially the same way, but instead of IFilterViewModel we will use IMeasureViewModel.

            this.Measures = new List<string>();
            foreach (IAreaItemViewModel item in pivotGrid.DataSource.Measures)
            {
                var measureViewModel = item as IMeasureViewModel;
                if (measureViewModel != null)
                {
                    this.Measures.Add(measureViewModel.Measure.UniqueName);
                }
            }

Having saved the Hierarchies and Measures we can proceed to saving the IsExpanded states and the IsSelected states of the filter members.

At this point you might be wondering what are filter members. When you select the filter button of a hierarchy in the XamPivotGrid UI a menu would open with a tree with items that represent the levels of the hierarchy. Each of those items can be selected or expanded. As you might have guessed those are the filter members.  Expansion of those levels can also be done automatically when you click the expansion indicators of the HeaderCells. It is also easier to get their values from the HeaderCells too.

The FilterMembers are organized in a tree hierarchy and each IFilterMember has a property FilterMembers. The best way to go through all filter members and save their states is to write a recursive method to go through all of them. The following code gets all members that have to be expanded for the filter hierarchies:

            foreach (IFilterViewModel filterViewModel in pivotGrid.DataSource.Filters)
            {
                if (filterViewModel.FilterMembers != null)
                    foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                        SaveExpansionStateOfFilterMembers(filterMember);
            }

        //recusive method to save the expansion states of the filter members
        private void SaveExpansionStateOfFilterMembers(IFilterMember filterMember)
        {
            if (filterMember.FilterMembers != null)
            {
                foreach (IFilterMember subFilterMember in filterMember.FilterMembers)
                {
                    SaveExpansionStateOfFilterMembers(subFilterMember);
                }
            }
 
            CustomDictItem item = new CustomDictItem(filterMember.Member.UniqueName, filterMember.IsExpanded);
 
            if (!CustomDictContains(this.FilterMembersToExpand, item.UniqueName))
            {
                this.FilterMembersToExpand.Add(item);
            }
            else
            {
                GetCustomDictItem(this.FilterMembersToExpand, item.UniqueName).Value = true;
            }
        }

When we save the filter member states for the Rows and Columns however we can take a short cut and use the VisibleColumnsHeaderCells and VisibleRowsHeaderCells collections as everything is already expanded:

            foreach (PivotHeaderCell cell in pivotGrid.GridLayout.RowHeaderCells)
            {
                CustomDictItem item = new CustomDictItem(cell.Member.UniqueName, cell.IsExpanded);
 
                if (!CustomDictContains(this.FilterMembersToExpand, item.UniqueName))
                {
                    this.FilterMembersToExpand.Add(item);
                }
                else
                {
                    GetCustomDictItem(this.FilterMembersToExpand, item.UniqueName).Value = true;
                }
            }
            foreach (PivotHeaderCell cell in pivotGrid.GridLayout.ColumnHeaderCells)
            {
                CustomDictItem item = new CustomDictItem(cell.Member.UniqueName, cell.IsExpanded);
 
                if (!CustomDictContains(this.FilterMembersToExpand, item.UniqueName))
                {
                    this.FilterMembersToExpand.Add(item);
                }
                else
                {
                    GetCustomDictItem(this.FilterMembersToExpand, item.UniqueName).Value = true;
                }
            }

Note that we are checking if we have added an item with the same UniqueName. The reason for that is because when an item is expanded a total item is added to summarize the group. Because of the implementation that item has the same UniqueName and we need to set the expanded state to true.

With those three cycles we have saved all info needed to expand the filter members properly when we load them. Now it is time to save the IsSelected states. The process is similar, but this time we can’t use a shortcut and we have to go through all three cycles in a recursive way.

A pitfall you may fall into here is the fact that not all elements of the Columns and Rows are of type IFilterViewModel. When you have more than one Measure those collections might contain IMeasureViewModel and thus you can’t use the foreach cycles and have to use for. The Filters collection is save though. So here is the code for saving the selection states:

            FilterMembersToLoad = new List<CustomDictItem>();
            for (int i = 0; i < pivotGrid.DataSource.Columns.Count; i++)
            {
                IFilterViewModel filterViewModel = pivotGrid.DataSource.Columns[i] as IFilterViewModel;
 
                if (filterViewModel != null)
                {
                    if (filterViewModel.FilterMembers != null)
                        foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                            SaveSelectionStateOfFilterMembers(filterMember);
                }
            }
            for (int i = 0; i < pivotGrid.DataSource.Rows.Count; i++)
            {
                IFilterViewModel filterViewModel = pivotGrid.DataSource.Rows[i] as IFilterViewModel;
 
                if (filterViewModel != null)
                {
                    if (filterViewModel.FilterMembers != null)
                        foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                            SaveSelectionStateOfFilterMembers(filterMember);
                }
            }
            foreach (IFilterViewModel filterViewModel in pivotGrid.DataSource.Filters)
            {
                if (filterViewModel.FilterMembers != null)
                    foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                        SaveSelectionStateOfFilterMembers(filterMember);
            }
 
        //recusive method to save the selection states of the filter members
        private void SaveSelectionStateOfFilterMembers(IFilterMember filterMember)
        {
            if (filterMember.FilterMembers != null)
            {
                foreach (IFilterMember subFilterMember in filterMember.FilterMembers)
                {
                    SaveSelectionStateOfFilterMembers(subFilterMember);
                }
            }
 
            if (filterMember.IsSelected.HasValue)
            {
                CustomDictItem item = new CustomDictItem(filterMember.Member.UniqueName, filterMember.IsSelected.Value);
 
                if (!CustomDictContains(this.FilterMembersToLoad, item.UniqueName))
                {
                    this.FilterMembersToLoad.Add(item);
                }
                else
                {
                    GetCustomDictItem(this.FilterMembersToLoad, item.UniqueName).Value = true;
                }
            }
        }

At this point we have saved the selected hierarchies in each axis; we have saved the expansion states and the filter selection states. That is all basic info that we need in order to reconstruct a view in a pivot grid.

Now we can add some additional info like taking a timestamp of the save:

        this.Timestamp = DateTime.Now.ToString();

Asking the user to give a name for this View is another option. It’s a matter of taste really what you would do further in the SaveView method – anyway its job is done.

The fill method

Now it is time to load the saved view. A convenient way to do that is by providing a method that accepts the pivot grid instance as parameter and then in its body the method reads the saved info and restores it in the provided instance of the XamPivotGrid.

You might expect that the restore process is straight forward, but you would be wrong. There are a few tricky places where you need to be extra careful. Those will be mentioned in the following paragraphs, so read on and you should be fine.

The tricks that are worth mentioning are necessary because the loading of all data in the XamPivotGrid is asynchronous. That is why we can turn the deferred layout update on in the beginning of the load process and turn it off at the end. If we do that the UI will be updated with all changes at once. Another thing that we can use is a flag that marks the load state as incomplete. After we have added the rows, columns, filters and measures we have to wait for the data to load before we can expand and select filter members. The way to figure out when the data is loaded is to subscribe to the LayoutLoaded event. However this event might be thrown more than once and thus we need that flag. See complete code in the sample for clearer picture.

The first step to load the view is to select the right cube of data. We have stored the index of that cube from the Cubes property of the DataSource and we can get it from there now and assign it to the Cube property:

            pivotGrid.DataSource.Cube =
                        pivotGrid.DataSource.Cubes[this.SelectedCubeIndex];

Then we have to clear all loaded hierarchies and measures before we add the saved ones by finding the hierarchy in each dimension that has the UniqueName of the saved hierarchy. Similarly we will find the measures too.

            pivotGrid.DataSource.Rows.Clear();
            pivotGrid.DataSource.Columns.Clear();
            pivotGrid.DataSource.Measures.Clear();
            pivotGrid.DataSource.Filters.Clear();

Each Cube has a collection of Dimensions and each Dimension has a collection of Hierarchies:

            foreach (IDimension dim in pivotGrid.DataSource.Cube.Dimensions)
            {
                foreach (IHierarchy hierarchy in dim.Hierarchies)
                {
                    if (this.RowHierarchies.Contains(hierarchy.UniqueName))
                    {
                        IFilterViewModel fvm = pivotGrid.DataSource.CreateFilterViewModel(hierarchy);
                        pivotGrid.DataSource.Rows.Add(fvm);
                    }
                    else if (this.ColumnHierarchies.Contains(hierarchy.UniqueName))
                    {
                        IFilterViewModel fvm = pivotGrid.DataSource.CreateFilterViewModel(hierarchy);
                        pivotGrid.DataSource.Columns.Add(fvm);
                    }
                    else if (this.FilterHierarchies.Contains(hierarchy.UniqueName))
                    {
                        IFilterViewModel fvm = pivotGrid.DataSource.CreateFilterViewModel(hierarchy);
                        pivotGrid.DataSource.Filters.Add(fvm);
                    }
                }
            }

Each Cube has a collection of Measures:

            foreach (IMeasure measure in pivotGrid.DataSource.Cube.Measures)
            {
                if (this.Measures.Contains(measure.UniqueName))
                {
                    IMeasureViewModel mvm = pivotGrid.DataSource.CreateMeasureViewModel(measure);
                    pivotGrid.DataSource.Measures.Add(mvm);
                }
            }

At this point we have to trigger an update of the UI and wait for the data to load. This is done my setting the Deferred Layout Update to false and then in the LayoutLoaded event to true again. See sample source for more info.

When the data is available we have to go through all filter members and restore their states. First we must do the expansion and only then we can do the selection. This is so because if the parent of a member that has to be selected is not expanded it is not part of the visual tree and thus you can’t interact with it.

Also because the expansion is an asynchronous action we have to use a ManualResetEvent in the ExpandMembers recursive method. See code below:

                //expand members for columns, rows and filters
                for (int i = 0; i < (sender as XamPivotGrid).DataSource.Rows.Count; i++)
                {
                    IFilterViewModel filterViewModel = (sender as XamPivotGrid).DataSource.Rows[i] as IFilterViewModel;
 
                    if (filterViewModel != null)
                    {
                        if (filterViewModel.FilterMembers != null)
                            foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                                ExpandMembers(filterViewModel, filterMember);
                    }
                }
                for (int i = 0; i < (sender as XamPivotGrid).DataSource.Columns.Count; i++)
                {
                    IFilterViewModel filterViewModel = (sender as XamPivotGrid).DataSource.Columns[i] as IFilterViewModel;
 
                    if (filterViewModel != null)
                    {
                        if (filterViewModel.FilterMembers != null)
                            foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                                ExpandMembers(filterViewModel, filterMember);
                    }
                }
                foreach (IFilterViewModel filterViewModel in (sender as XamPivotGrid).DataSource.Filters)
                {
                    if (filterViewModel.FilterMembers != null)
                        foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                            ExpandMembers(filterViewModel, filterMember);
                }

private void ExpandMembers(IFilterViewModel filterViewModel, IFilterMember filterMember)
        {
            //use this event for syncronizing the otherwise async load of members
            ManualResetEvent manualResetEvent = new ManualResetEvent(false);
            filterViewModel.LoadFilterMembersCompleted += (sender1, e1) => manualResetEvent.Set();
 
            if (CustomDictContains(FilterMembersToExpand, filterMember.Member.UniqueName))
                filterMember.IsExpanded = GetCustomDictItem(FilterMembersToExpand, filterMember.Member.UniqueName);
 
            if (!filterMember.IsExpanded)
                return;
 
            manualResetEvent.WaitOne();
 
            foreach (IFilterMember fm in filterMember.FilterMembers)
            {
                ExpandMembers(filterViewModel, fm);
            }
 
        }

After all that is done the last thing to do remains – to select the filter members that are saved. The way we do that is similar to the expansion, but there is no need for the manual reset event.

                //select members for columns, rows and filters
                foreach (IFilterViewModel filterViewModel in (sender as XamPivotGrid).DataSource.Filters)
                {
                    if (filterViewModel.FilterMembers != null)
                        foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                            SelectMembers(filterMember);
                }
                for (int i = 0; i < (sender as XamPivotGrid).DataSource.Rows.Count; i++)
                {
                    IFilterViewModel filterViewModel = (sender as XamPivotGrid).DataSource.Rows[i] as IFilterViewModel;
 
                    if (filterViewModel != null)
                    {
                        if (filterViewModel.FilterMembers != null)
                            foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                                SelectMembers(filterMember);
                    }
                }
                for (int i = 0; i < (sender as XamPivotGrid).DataSource.Columns.Count; i++)
                {
                    IFilterViewModel filterViewModel = (sender as XamPivotGrid).DataSource.Columns[i] as IFilterViewModel;
 
                    if (filterViewModel != null)
                    {
                        if (filterViewModel.FilterMembers != null)
                            foreach (IFilterMember filterMember in filterViewModel.FilterMembers)
                                SelectMembers(filterMember);
                    }
                }
 
        private void SelectMembers(IFilterMember filterMember)
        {
            if (filterMember.FilterMembers != null)
            {
                foreach (IFilterMember subFilterMember in filterMember.FilterMembers)
                {
                    SelectMembers(subFilterMember);
                }
            }
 
            if (CustomDictContains(FilterMembersToLoad, filterMember.Member.UniqueName))
                filterMember.IsSelected = GetCustomDictItem(FilterMembersToLoad, filterMember.Member.UniqueName);
        }

With that we have an almost complete Save and Load views class. The only additions are two helper methods to get a CustomDictItem by UniqueName and a checker if an item with the UniqueName exists.

        private CustomDictItem GetCustomDictItem(List<CustomDictItem> ListOfItems, string UniqueName)
        {
            foreach (CustomDictItem item in ListOfItems)
            {
                if (item.UniqueName == UniqueName)
                    return item;
            }
            throw new Exception("Dict does not contain this item");
        }
 
        private bool CustomDictContains(List<CustomDictItem> ListOfItems, string UniqueName)
        {
            foreach (CustomDictItem item in ListOfItems)
            {
                if (item.UniqueName == UniqueName)
                    return true;
            }
            return false;
        }

In summary the SavedPivotGridView class has a number of collections that store the UniqueNames of hierarchies to restore and collections with combinations of UniqueNames and expanded or selected states. It also stores the selected Cube index and some miscellaneous information that is non-essential to the restoring process. The class also has a method to save the info given a XamPivotGrid instance and a fill method to populate an instance based on the saved information.

What is left to do now is to decide on how to store the instances of that class. You can either save it in memory, or use serialization to save it in an XML file or a database.

XML serialization

So far all that we have done has gone in the SavedPivotGridView class. Now we move our focus to the main page of the application. Let’s say that we have two buttons – SaveViewToXMLFile and LoadViewFromXMLFile.  In the save button event handler we would first add a new instance of the SavedPivotGridView. Then we will execute the SaveView method of the instance and we would have a complete view of the current grid that we can proceed to serialize.

For the XML serialization we need to add the System.Xml.Serialization namespace. In that namespace there is a class called XmlSerializer and need an instance of it. In the default constructor we have to supply the class type of the data we are going to serialize, namely the SavedPivotGridView class. After that we need a MemoryStream that we’ll use to save the file data. And then we call the Serialize method of the XmlSerializer. It’s as simple as that. The following snippet shows how to implement the whole event handler for the save button click and save the result to an XML file:

        private void saveToXML_Click(object sender, RoutedEventArgs e)
        {
            XMLSerializedView = new SavedPivotGridView();
            XMLSerializedView.SaveView(this.xamPivotGrid);
 
            XmlSerializer serializer = new XmlSerializer(typeof(SavedPivotGridView));
            MemoryStream memoryStream = new MemoryStream();
            serializer.Serialize(memoryStream, XMLSerializedView);
 
            SaveFileDialog saveFileDialog = new SaveFileDialog()
            {
                DefaultExt = "xml",
                Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*",
                FilterIndex = 1
            };
 
            if (saveFileDialog.ShowDialog() == true)
            {
                using (Stream stream = saveFileDialog.OpenFile())
                {
                    byte[] bytes = memoryStream.ToArray();
                    stream.Write(bytes, 0, bytes.Length);
                    stream.Close();
                    memoryStream.Close();
                }
            }
        }

The reverse process is similar. You need to create a read stream for the file you have selected and the call the Deserialize method of the XmlSerializer. The method returns an object that you have to cast to SavedPivotGridView and then call its Fill method that we created before.

        private void loadFromXML_Click(object sender, RoutedEventArgs e)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(SavedPivotGridView));
 
            OpenFileDialog openFileDialog = new OpenFileDialog()
            {
                Multiselect = false,
                Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*",
                FilterIndex = 1
            };
 
            bool? result = openFileDialog.ShowDialog();
 
            if (result != null && result == true)
            {
                XMLSerializedView = (SavedPivotGridView)serializer.Deserialize(openFileDialog.File.OpenRead());
                XMLSerializedView.Fill(this.xamPivotGrid);
            }
        }

Binary serialization

The binary serialization works in the same general principle as the XML serialization. The namespace is different though. It is called System.Runtime.Serialization. The serializator class on the other hand is called DataContractSerializer. The Serialize/Deserialize method pair is also called differently – WriteObject and ReadObject.

The result of the bin serialization is a byte array that is convenient to put in a database. For the sample I have created a database with one table that has a timestamp column as the primary key and value column for the byte[].

As Silverlight can’t directly access the database we need to use WCF services to do the job for us. Let’s assume that the service is already created and has two operation contracts. One is called UploadView and accepts two parameters – timestamp and byte array. The other one is called DownloadView and accepts just a DateTime parameter by which we’ll be searching the database for the save. We are going to implement the service after a few pharagraphs.

So assuming that we have added the service reference and have access to those two methods we can see the whole code for the binary serialization. The following is the code for saving the view in a database:

        private void saveViewToDB_Click(object sender, RoutedEventArgs e)
        {
            SavedPivotGridView view = new SavedPivotGridView();
            view.SaveView(this.xamPivotGrid);
 
            DataContractSerializer serializer = new DataContractSerializer(typeof(SavedPivotGridView));
            MemoryStream memoryStream = new MemoryStream();
            serializer.WriteObject(memoryStream, view);
 
            client.UploadViewAsync(DateTime.Parse(view.Timestamp), memoryStream.ToArray());
        }
 
        void client_UploadViewCompleted(object sender, UploadViewCompletedEventArgs e)
        {
            if (e.Result)
            {
                MessageBox.Show("Upload completed successfuly");
            }
            else
                MessageBox.Show("Upload failed");
        }

Note that the service method is asynchronous and we need to subscribe to the completed event where we actually know if the upload was successful.

Then we have the reverse process:

        private void loadViewFromDB_Click(object sender, RoutedEventArgs e)
        {
            if (this.listOfViews.SelectedItem != null)
                client.DownloadViewAsync(DateTime.Parse(this.listOfViews.SelectedValue.ToString(), System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat));
            else
                MessageBox.Show("Please select the timestamp of the view you want to load");
        }
 
        void client_DownloadViewCompleted(object sender, DownloadViewCompletedEventArgs e)
        {
            if (e.Result != null)
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof(SavedPivotGridView));
 
                int len = 0;
                while (e.Result[len] > 0)
                    len++;
                MemoryStream memoryStream = new MemoryStream(e.Result, 0, len);
 
                SavedPivotGridView view = serializer.ReadObject(memoryStream) as SavedPivotGridView;
                view.Fill(this.xamPivotGrid);
            }
            else
                MessageBox.Show("Upload failed");
        }

The interesting thing here is that the Result being a byte array because it is taken from the database will always have a fixed length. No matter what the size of the save is the array will be filled with zeros after the useful data. That’s why we need to find out the real length of the array (the while cycle) and then create the memory stream with those boundaries. Otherwise the Fill method will crash.

All that is left now is to actually create the service and those two OperationContracts.

Database interaction WCF service

There are several ways to access the database, upload a byte array and then download it. For the sake of simplicity here I have created an ADO.NET entity data model and then a Silverlight enabled service that does the job through it.

The first step that we have to take before we create the entity model is to create the database and the table that we are going to use. You have the freedom to choose whatever you want setup and the only thing that you have to have in mind is that the byte array column has to be big enough. In my case I have a database called PivotGridTests and a table called SerializationSamples. That table has a Timestamp column of type DateTime and a SavedView column of type byte(8000). I have used the wizard to add the entities model and I selected to include only that table.

After the model is added we can add a new Silverlight enabled service. I’ve called it DatabaseInteraction.svc. The key components in that service are the proxy and the operation contracts.

    public class DatabaseInteraction
    {
        PivotGridTestsEntities proxy;
   
       
        Operation contracts here
       
   
    }

A useful tip is to check in all contracts whether the proxy is initialized and the database exists. If that sanity check passes you can interact with the database.

In order to upload a view you can call the AddToSerializationSamples method of the proxy and then call the SaveChanges method. In order to download a view you need to go through all SerializationSamples and check if the timestamp matches. That can also be done with LINQ.

The following is the code for our two contracts:

        [OperationContract]
        public bool UploadView(DateTime stamp, byte[] viewToSave)
        {
            if (proxy == null)
                proxy = new PivotGridTestsEntities();
 
            if (proxy.DatabaseExists())
            {
                proxy.AddToSerializationSamples(new SerializationSample() { Timestamp = stamp, SavedView = viewToSave });
                proxy.SaveChanges();
                return true;
            }
            return false;
        }
 
        [OperationContract]
        public byte[] DownloadView(DateTime stamp)
        {
            if (proxy == null)
                proxy = new PivotGridTestsEntities();
 
            if (proxy.DatabaseExists())
            {
                foreach (SerializationSample currentSample in proxy.SerializationSamples)
                {
                    if (currentSample.Timestamp == stamp)
                    {
                        return currentSample.SavedView;
                    }
                }
            }
            return null;
        }

Summary

So far we have seen what important components of the DataSource we need to save if we want to completely reconstruct a XamPivotGrid view. We have seen how to save them in a convenient way and we have seen how to serialize that saved view to an xml file and to a database.

I hope you have enjoyed this article and you have found it useful. Take a look at the attached solution. To make it work you need to add the references to the Infragistics assemblies:

  • InfragisticsSL4.Controls.Grids.XamPivotGrid.v10.2.dll
  • InfragisticsSL4.Controls.Menus.XamTree.v10.2.dll
  • InfragisticsSL4.DragDrop.v10.2.dll
  • InfragisticsSL4.Olap.FlatData.v10.2.dll
  • InfragisticsSL4.Olap.v10.2.dll
  • InfragisticsSL4.v10.2.dll

If you do not have the full version you can download the trial version freely from our website. You will also have to recreate the entities model to reflect the connection to your database and your table. Make sure all the property names are the ones in your setup.

If you have any questions, concerns or any form of feedback I’ll be happy to hear it, either here as comments or on my email: adyulgerov@infragistics.com

Thank you for your time!

PivotGridSerialization.zip