Writing...Data Services

Coding and writing about OData and now Windows Azure Mobile Services...

New OData Tombstoning Behavior in Mango

New OData Tombstoning Behavior in Mango

  • Comments 6

As I mentioned in my previous post OData Updates in Windows Phone “Mango”, new methods have been added to the DataServiceState class that improve performance and functionality when storing client state. You can now serialize nested binding collections as well as any media resource streams that have not yet been sent to the data service. But what does this new behavior look like?

Storing state in the State dictionary of the PhoneApplicationService essentially involves serializing a DataServiceState object, including a typed DataServiceContext object, one or more DataServiceCollection<T> objects, and all the entity data referenced by these objects. To make this behavior more correct, the old SaveState method is replaced with a new static Serialize method. This new method returns, quite simply, a string that is the XML serialized representation of the stored objects. This works much better for storing in the state dictionary because the DataServiceState is able to explicitly serialize everything before it gets stored. Also, nested collections should now work (this was broken in the Windows Phone 7 version).

The following code, based on the quickstart Consuming a Windows Azure Data Service by using the OData Client, has been updated to use the new Mango tombstoning behavior, including the new static Serialize method to store application state on deactivation:

// Code to execute when the application is deactivated (sent to background).
// This code will not execute when the application is closing.
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    if (App.ViewModel.IsDataLoaded)
    {
        // Store application state in the state dictionary.             
            PhoneApplicationService.Current.State["ApplicationState"]
                = ViewModel.SaveState();               
    }
}

// Return a collection of key-value pairs to store in the application state.
public Dictionary<string, string> SaveState()
{
    if (App.ViewModel.IsDataLoaded)
    {
        Dictionary<string, string> state
            = new Dictionary<string, string>();

        // Create a new dictionary to store binding collections.
        var collections = new Dictionary<string, object>();

        // Add the current Titles binding collection.
        collections["Titles"] = App.ViewModel.Titles;

        // Store the current context and binding collections
        // in the view model state.
        state["DataServiceState"] = 
                DataServiceState.Serialize(_context, collections);

        state["CurrentPage"] = CurrentPage.ToString();
        state["TotalCount"] = TotalCount.ToString();

        return state;
    }
    else
    {
        return null;
    }
}

A new static Deserialize method on DataServiceState takes the stored serialization and returnes a rehydrated DataServiceState instance, so the re-activation code now looks like this:

// Code to execute when the application is activated (brought to foreground).
// This code will not execute when the application is first launched.
private void Application_Activated(object sender, ActivatedEventArgs e)
{
    // If data is not still loaded, try to get it from the state store.
    if (!ViewModel.IsDataLoaded)
    {
        if (PhoneApplicationService.Current.State.ContainsKey("ApplicationState"))
        {
            // Get back the stored dictionary.
            Dictionary<string, string> appState =
                PhoneApplicationService.Current.State["ApplicationState"]
                as Dictionary<string, string>;

            // Use the returned dictionary to restore
            // the state of the data service.
            App.ViewModel.RestoreState(appState);
        }
    }
}

// Restores the view model state from the supplied state dictionary.
public void RestoreState(IDictionary<string, string> appState)
{
    // Create a dictionary to hold any stored binding collections.
    Dictionary<string, object> collections;

    if (appState.ContainsKey("DataServiceState"))
    {
        // Deserialize the DataServiceState object.
        DataServiceState state
            = DataServiceState.Deserialize(appState["DataServiceState"]);

        // Restore the context and binding collections.
        var context = state.Context as NetflixCatalog;
        collections = state.RootCollections;

        // Get the binding collection of Title objects.
        DataServiceCollection<Title> titles
            = collections["Titles"] as DataServiceCollection<Title>;

        // Initialize the application with stored data.
        App.ViewModel.LoadData(context, titles);

        // Restore other view model data.
        _currentPage = Int32.Parse(appState["CurrentPage"]);
        _totalCount = Int32.Parse(appState["TotalCount"]);
    }
}

Note that with multi-tasking and the new fast application switching functionality in Mango, it is possible that your application state will be maintained in a “dormant” state in memory when your app loses focus. This means that even though the Activated event is still raised, the application data is still there and is immediately redisplayed. This is much faster than tombstoning because you don’t have to deserialize and re-bind everything (and the bound images don’t have to be downloaded again—hurray!). For more information, see Execution Model Overview for Windows Phone in the Mango beta documentation.

Cheers,

Glenn Gailey

Leave a Comment
  • Please add 3 and 1 and type the answer here:
  • Post
  • Hi Glenn,

    I have made the changes you outline above (correctly I think). I also recreated my entity data model and the WCF Data Service that exposes the data. However, in my Deactivated method, I get a serialization error when I execute the DataServiceState's Serialize method. The problem seems to be the inability to serialize the context. My code looks like:

           If App.CourseViewModel.IsDataLoaded Or App.ScoreViewModel.IsDataLoaded Then

               ' create a 'state' dictionary

               Dim state As New Dictionary(Of String, String)

               ' create a dictionary to store the collection of courses and scores

               Dim theCollections As New Dictionary(Of String, Object)

               ' store the courses into the dictionary

               If App.CourseViewModel.Courses IsNot Nothing Then

                   theCollections.Add("Courses", App.CourseViewModel.Courses)

               End If

               ' store the scores into the dictionary

               If App.ScoreViewModel.Scores IsNot Nothing Then

                   theCollections.Add("Scores", App.ScoreViewModel.Scores)

               End If

               ' store the current context and binding collections

               Dim _context = GolfContext.Context

               state.Add("DataServicesState", DataServiceState.Serialize(_context, theCollections))

    Executing the final line above throws the error:

    Type 'WP7_Part03_Proto01.Golf_DB1Model.Golf_DB1Entities' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.

    Any suggestions on resolving the issue?

    Thanks ... bill burrows

  • Hey Bill,

    The new OData tombstoning does require that any object being serialized be decorated with DataServiceKeyAttribute, which should be done by the Mango version of the tools when the client data classes are generated from the service reference. If this is not happening, you should check on the following:

    1) That the target version of the project is set to 7.1

    2) That you have, in fact, updated the service reference.

    3) That the Reference.datasvcmap file in the service reference, the ClientAssemblyReference parameter is referencing the 7.1 version of the System.Data.Services.Client.dll assembly.

    4) If all else fails, you can always use the DataSvcUtil.exe utility included with the Mango SDK to manually generate the client data classes and add it to your project in place of the service reference. For more on using this utility, see msdn.microsoft.com/.../ee383989.aspx.

    Cheers!

    Glenn Gailey

  • Hi Glenn,

    Creating client data classes (step 4 above) did the trick.

    Thnaks for your quick responses and help.

    bill

  • This may be a stupid question, but I simply can't find the answer. Can tombstoning be done somehow also on desktop apps? I need a method to create a copy of the entire context and I have no ideea on how to do it.

  • The DataServiceState class, which serializes DataServiceContext and DataServiceCollection<T>, is only available in the OData client for Windows Phone 7. It was intended to be used to enable tombstoning in Windows Phone, because a Windows Phone app can get shut down at any point by the OS.

    I've never investigated trying to clone an existing DataServiceContext, but based on how difficult it seemed to be to serialize and deserialize it correctly maintaining the objects, collections, and state it must be difficult.

    The OData client for WP is a subset of the full .NET client, but I've also never tried to run it in a non-WP application.

  • I just found the solution to the problem that I commented on a few minutes ago.

    When I opened up the Reference.cs file and added [DataContract] just before the Entities (DataServiceContext) class, the problem went away.  I verified that neither "Add Service Reference" and SVCUTIL.EXE generate code with the DataContract attribute.

    All is good now... Thanks

Page 1 of 1 (6 items)