Showing posts with label Linq. Show all posts
Showing posts with label Linq. Show all posts

Monday, January 11, 2010

Silverlight, RIA Services, and the Reactive Framework (Rx)

This post is not specific to RIA Services but I thought I'd add it to the title since the example is based on RIA Services.

Anyone who is familiar with RIA Services will recognize this piece of code that is used to load a entity -
EntityQuery<Person> personQuery = DomainContext.GetPersonQuery();
DomainContext.Load(personQuery, loadOperation =>
{
    HandleLoadOperationError(loadOperation);
}, null);
Typically this piece of code wll be called from some other code in a simulated synchronous fashion, like this -
public void LoadPerson(Action callback)
{
    EntityQuery<Person> personQuery = DomainContext.GetPersonQuery();
    DomainContext.Load(personQuery, loadOperation =>
    {
        if (!HandleLoadOperationError(loadOperation))
            return;

        if (callback)
        {
            InitializeData();
            callback(null);
        }
    }, null);
}

public void DoMore()
{
    ShowBusyIndicator(); // Disable the active window and show busy indicator
    LoadPerson(() =>
    {
        // This will be called after the asychronous load in LoadPerson has completed
        // ...
        CloseBusyIndicator(); // Re-enable user interaction
        // ...
    }    
}
Let's say we have to display a search page with a bunch of fields, mostly pre-populated combo boxes and list boxes. The data for the combobox and listbox comes from corresponding Entity objects. One way to load the Entities could be the following -
public void LoadLookupEntities(Action callback)
{
    DomainContext.Load(DomainContext.GetPersonQuery(), loadOperation1 =>
    {
        if (!HandleLoadOperationError(loadOperation1))
            return;

        DomainContext.Load(DomainContext.GetGenderQuery(), loadOperation2 =>
        {
            if (!HandleLoadOperationError(loadOperation2))
                return;

            DomainContext.Load(DomainContext.GetEthnicityQuery(), loadOperation3 =>
            {
                if (!HandleLoadOperationError(loadOperation3))
                    return;

                // and so on ...

                if (callback)
                {
                    InitializeData();
                    callback(null);
                }
            }, null);
        }, null);
    }, null);
}
Now let's go ahead and refactor LoadLookupEntities with the Reactive Framework (Rx). First we need to create an extension method LoadAsync on DomainContext which is essentially the same as Load but will return an IOBservable<LoadOperation<>> instead of LoadOperation<>. This is one way to do it (this technique can be used for async patterns that don't follow the begin/end pattern) -
public static class DomainContextExtensions
{
    public static IObservable<LoadOperation<TEntity>> LoadAsync<TEntity>(
        this DomainContext domainContext,
        EntityQuery<TEntity> query)
        where TEntity : Entity
    {
        var asyncSubject = new AsyncSubject<LoadOperation<TEntity>>();

        domainContext.Load(query, loadOperation =>
        {
            if (loadOperation.HasError)
                asyncSubject.OnError(loadOperation.Error);
            else
            {
                asyncSubject.OnNext(loadOperation);
                asyncSubject.OnCompleted();
            }
        }, null);

        return asyncSubject;
    }
}
Once we have the extension method let's go ahead and use it.
public void LoadLookupEntities(Action callback)
{
    var loadAll =
        from loadOperation1 in DomainContext.LoadAsync(DomainContext.GetPersonQuery())
        where !HandleLoadOperationError(loadOperation1)
        from loadOperation2 in DomainContext.LoadAsync(DomainContext.GetGenderQuery())
        where !HandleLoadOperationError(loadOperation2)
        from loadOperation3 in DomainContext.LoadAsync(DomainContext.GetEthnicityQuery())
        where !HandleLoadOperationError(loadOperation3)
        // ...
        select true;

    loadAll.Subscribe(loadedAll =>
    {
        if (loadedAll)
        {
            InitializeData();
            callback(null);
        }
    });
}
Or if we want parallel execution -
var loadAllInParallel =
    Observable.ForkJoin<OperationBase>(
        DomainContext.LoadAsync(DomainContext.GetPersonQuery()).Select(s => s as OperationBase),
        DomainContext.LoadAsync(DomainContext.GetGenderQuery()).Select(s => s as OperationBase),
        DomainContext.LoadAsync(DomainContext.GetEthnicityQuery()).Select(s => s as OperationBase),
    ).Finally(() => System.Diagnostics.Debug.WriteLine("Done!"));

loadAllInParallel.Subscribe(
    loadOperationsAll =>
    {
        InitializeData();
        callback(null);
    },
    error =>
    {
        // Handle Errors
    });
That's it!

Why would you want to do this? If you don't know the answer, go ahead and check out the resources listed below. If you still don't know the answer, you don't need it!

My reaction to the Rx framework - Unfreaking believably cool! More importantly, it's so useful that I suspect I will be using it as commonly as I do Linq.

Note that the examples above are just that - examples. Obviously the error handling has a lot to be desired!

Some Really Good Resources on Rx

Monday, October 19, 2009

Remove all empty XML elements using LINQ

While working on the XamlWriter I had to do some post processing on the generated Xaml. One was to delete all nodes that did not have any attributes or contain any elements. The first pass of removing the empty nodes could generate more empty nodes and hence this problem is recursive in nature.

This was my first attempt
var query = doc.Descendants().Where(c => !c.HasAttributes && c.IsEmpty);
while (query.Count())
   query.Remove();

This could be certainly improved using the .Any operator

var query = doc.Descendants().Where(c => !c.HasAttributes && c.IsEmpty);
while (query.Any())
   query.Remove();

The problem with the above solution(s) is that the query is executed twice, once for the Any/Count method and once for the Remove method. The next approach provided 2x performance gains (in the best case scenario) over the above approach

void RemoveEmptyNodes(XElement root)
{
   var query = root.Descendants().Where(e => !e.HasAttributes && e.IsEmpty).ToList();
   query.Remove();
   if (query.Any())
      RemoveEmptyNodes(root);
}

The reason for the performance improvement is that although both the Any and the Remove operations are executed on the query, the .ToList operator executes and caches the result and hence the query is executed only once.

The concern I have with the above recursive solution is the memory requirement for the local variable query which is of type IEnumerable.

Maybe something like this will help but I don't understand GC (garbage collection) in .NET well enough to recommend this approach

void RemoveEmptyNodes(XElement root)
{
   var query = root.Descendants().Where(e => !e.HasAttributes && e.IsEmpty).ToList();
   query.Remove();
   if (query.Any())
   {
      query = null;
      RemoveEmptyNodes(root);
   }
}

Anyone with better or alternate approaches please post your thoughts as comments.

Tuesday, April 1, 2008

Dissecting Hard Rock Memorabilia and Silverlight Deep Zoom - Part 6

The filter example is finally here!

Update Oct 14, 2008: Removed the example from this post to reduce load time of this page. Please use the example from this post, DeepZoom sample ported to Silverlight 2 Beta 2, to try the functionality.


Click on the Filter button to filter the images based on the tag entered in the text box to the left of the filter button. The following tags will yield some result: lilies, tulip, rose, hibiscus, iris, blue, white, yellow, red, peach, pink, purple. The flower tag will return all the images. The current limitation is that only one tag can be used at a time.

Behind the scenes...

This was actually pretty easy. I used the method suggested by George (see the first comment in this post ) instead of storing a pointer in the Tag property of the MultiScaleSubImage control.

This is what I did to accomplish basic filter functionality:
1. Modified the Deep Zoom generated SparseImageSceneGraph.xml file to include some tag information
?xml version="1.0"?>
<SceneGraph version="1">
<SceneNode>
<FileName>tigerlilies_cr.jpg</FileName>
<ZOrder>1</ZOrder>
<Tags>
<Tag>lilies</Tag>
<Tag>flower</Tag>
</Tags>
</SceneNode>
<SceneNode>
<FileName>tulip_bluebase-1_cr.jpg</FileName>
<ZOrder>5</ZOrder>
<Tags>
<Tag>tulip</Tag>
<Tag>flower</Tag>
<Tag>blue</Tag>
</Tags>
</SceneNode>
<SceneNode>
<FileName>2redandwhitelilies_cr.jpg</FileName>
<ZOrder>8</ZOrder>
<Tags>
<Tag>lilies</Tag>
<Tag>flower</Tag>
<Tag>red</Tag>
<Tag>white</Tag>
</Tags>
</SceneNode>
...
</SceneGraph>

2. Loaded the SparseImageSceneGraph.xml asynchronously into the control. I could have simplified this by loading the metadata file synchronously or by embedding it in the xap package itself but I wanted to learn the asynchronous download feature provided by Silverlight

using System.Net;
using System.Linq;
using System.Xml.Linq;

XElement _xmlImageMetadata; // to store the metadata associated with the images

msi.Loaded += delegate(object sender, RoutedEventArgs e)
{
// Now set the source property.
// This ensures that the html page with the silverlight control is loaded completely before loading the multiscaleimage.
// This is to enhance the overall user experience.
msi.Source = new Uri("http://thepintospatronus.com/deepzoom/test/items.bin");

// Download the metadata associated with the above uri
WebClient _downloader = new WebClient();
_downloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(imageMetadata_DownloadStringCompleted);
_downloader.DownloadStringAsync(new Uri("http://thepintospatronus.com/deepzoom/test/SparseImageSceneGraph.xml"));
};

void imageMetadata_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null) {

// Convert the string xml representation to a valid XML document
_xmlImageMetadata = XElement.Parse(e.Result);

// The filter button is disabled by default. Enable it when the metadata is available
buttonFilter.IsEnabled = true;
}
}

3. Used Linq to filter the dataset

buttonFilter.Click += delegate(object sender, RoutedEventArgs e)
{
IEnumerable<int> ZOrders =
from sceneNode in _xmlImageMetadata.Elements("SceneNode")
where
(from tag in sceneNode.Elements("Tags").Elements("Tag")
where
((string)tag.Value).ToUpper() == txtFilter.Text.ToUpper()
select tag)
.Any()
select ((int)sceneNode.Element("ZOrder")-1);

_imagesToShow.Clear();
_imagesToHide.Clear();
for (int i=0; i<msi.SubImages.Count; i++) {
if (ZOrders.Contains(i))
_imagesToShow.Add(msi.SubImages[i]);
else
_imagesToHide.Add(msi.SubImages[i]);
}
ArrangeImages();
};

TODO - Better animation.

Comments are welcome!