2012-09-29

LINQ to Objects: Querying Nested Collections

I love LINQ.  LINQ is just fun to use.  I love the fact that I can query the same way across databases, XML  documents and objects (alright XML is slightly different, but...)

Today I had the task of querying down a complex set of objects that each own a collection data member.  I needed to get to some data 4-layers deep and thought the LINQ query would get pretty nasty.  That is until I learned that I can use the "from" keyword multiple times in the same LINQ query.

My example will be very contrived to save having to look at a lot of extra code for different collections, so I'll go ahead and just nest the same collection several layers down, just so we get the concept.

I have a Person class:
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }


    public int IQ { get; set; }

    private List<Person> _children;
    public List<Person> Children
    {
        get{ return _children ?? (_children = new List<Person>()); } 
        set{ _children = value; }
    }
}
If I set up a new List<Person> like so:
  List<Person> grandpas = new List<Person>
  {
    new Person{
      FirstName = "John", LastName = "Doe", IQ = 91,
      Children = new List<Person>{ // children
        new Person{ FirstName = "Meh", LastName = "Doe", IQ = 103 },
        new Person{
          FirstName = "Tae Kwon", LastName = "Doe", IQ = 125,
          Children = new List<Person>{ // grandchildren
            new Person{ FirstName = "Karate", LastName = "Kid", IQ = 68, },
            new Person{ FirstName = "Karate", LastName = "Kid2", IQ = 117 },
          }
        },
      }
    },
 
    new Person{
      FirstName = "John", LastName = "Doh", IQ = 135,
      Children = new List<Person>{ // children
        new Person{ FirstName = "Meh", LastName = "Doh", IQ = 42 },
        new Person{
          FirstName = "Tae Kwon", LastName = "Doh", IQ = 140,
          Children = new List<Person>{ // grandchildren
            new Person{ FirstName = "Doh", LastName = "Boy", IQ = 111 },
            new Person{ FirstName = "Doh", LastName = "Nut", IQ = 98 },
          }
        },
      }
    },
  };
Check out how easy it is to get to data that is below the top layer:
IEnumerable<Person> grandChildrenWithHighIQ = from grandpa in grandpas from parent in grandpa.Children from grandkid in parent.Children where grandkid.IQ > 100 select grandkid;
EDIT: I was asked to provide a version with the fluent API as well:

IEnumerable grandChildrenWithHighIQ = grandpas .SelectMany(grandpa => grandpa.Children.SelectMany(parent => parent.Children)) .Where(grandchild => grandchild.IQ > 100);

Now we know longer need be afraid of querying complex objects and getting the data we want.  Write your queries in the bLINQ of an eye, and get your data back even faster!

And I'll put a shameless plug out there for LINQPad since I took a screenshot from its results above.  It is a fantastic tool for crafting LINQ queries and is a wonderful sandbox for small .NET proof-of-concept projects because you can write small programs without creating a Visual Studio project or solution.

Enjoy!

2012-08-29

Telerik RadNumericUpDown for Silverlight does not update binding when value is blanked out

Note: This may be fixed in a later version of Telerik controls; this issue was discovered in the Telerik.Windows.Controls library; version 2011.1.315.1040

Working on a Silverlight application, we found ourselves with some dialogs that utilize the Telerik RadNumericUpDown control.  We discovered a defect when binding the Value property of the control to a ViewModel behind the scenes.

The original binding works as expected, and if the spinner controls are used to change the value, the ViewModel updates as we expect.  If a user types a new value into the control it also updated the binding on the ViewModel correctly.

The problem arises when we blank out the value by clicking into the textbox portion of the control, highlighting it and hitting the Backspace or Delete key.  Removing focus from the control or not, the ViewModel property bound to that control does not get updated.

To work around this, we created this Event Handler:
void RadNumericUpDown_ValueChanged(object sender, 
                                   RadRangeBaseValueChangedEventArgs e)
{
    if (sender is RadNumericUpDown)
    {
        RadNumericUpDown upDownControl = sender as RadNumericUpDown;
        if (!upDownControl.Value.HasValue)
            upDownControl.Value = upDownControl.Minimum;
    }
}
Now we apply this event handler to our XAML:
<telerik:RadNumericUpDown ValueChanged="RadNumericUpDown_ValueChanged" ... />
This makes the behavior act the same as if the user had used the down spinner on the control to modify the value down to the minimum.

This may not be the ideal solution for you, but understanding this will give you direction on how you can create your own workaround.  We started down the path of handling the KeyUp event, but ValueChanged seems much more appropriate.

Telerik makes fantastic controls; and I'm not mad by any means.  Being a developer myself, I realize these things happen; but I do hope they have handled this better in a later release.  And as far as I know this is the only control in the 2011.1.315.1040 version that has this issue.

2012-07-04

PayPal Instant Payment Notification (IPN) Simulator Requires Port 80

Someone please correct me if I'm wrong, but I fought for an hour or more trying to get PayPal's Instant Payment Notification (IPN) Simulator to post test transactions to my test site running on IIS Express which listens on a port different than 80; and it just doesn't work.

I asked friends on external networks to bring my site up in their browsers and they could hit it fine on the non-standard port, so I knew my firewall and IIS Express were configured correctly.

I cannot find anywhere in PayPal's documentation where it states that port 80 is required but it seems their own firewall is pretty strict with outbound traffic.  Once I forced my router to listen on port 80, and forward the request to the non-standard port my site listens on, the simulator worked like a champ.

So, if you're having problems with the IPN simulator not sending to your listener and you are listening on a non-standard port, make sure to do one of the following:

  1. Force your test site to listen on port 80 and configure your firewall accordingly.
  2. Force your router to listen on port 80, but forward to the port your test site is set up on.
That's what we like to see!
As a side note, if I remember correctly... the standard ASP.NET Development Server (Cassini) that comes with Visual Studio, does not accept requests from an external network.  I believe it can only accept them from the same machine you are doing the development on.  This is why I opted to use IIS Express.

Hope this saves someone else some time and head-banging on your desk.  :)
Happy coding!