The Code Slinger

November 20, 2007

LINQ to Objects: Nested Queries

Filed under: .NET,LINQ,VB.NET — Pete @ 6:21 pm
Tags: , ,

LINQ allows us to query our object tree(s) in a much easier and more readable manner than using traditional looping/iteration mechanisms of the past.

In this example, I have a Role object which contains a Dictionary(Of Type, List(Of IPermission)) which stores a series of object types as it’s key and a list of permission objects (which implement the IPermission interface). This keeps track of the relationship(s) between roles and permissions.

In the past, I would have written a function which takes a particular characteristic of one permission in particular and returns that permission type as the following:

 

253 Public Function InheritedStatusesByType(ByVal a_TypeEnum As WorkObjectTypeEnum) As IEnumerable(Of Status) 254 Dim hshReturn As New Dictionary(Of Integer, Status)

255 256 For Each rl As Role In CurrentRole.GetPermissionDL(Of Role)() 'ChildrenRolesDirect

257 For Each st As Status In rl.GetPermissionFlat(Of Status)() 'Statuses 258 If st.WorkObjectTypeEnum = a_TypeEnum Then

259 If Not hshReturn.ContainsKey(st.ID) Then 260 hshReturn.Add(st.ID, st)

261 End If 262 End If

263 Next 264 Next

265 Return New List(Of Status)(hshReturn.Values) 266 End Function

So this loops through the children roles (roles can be nested in this security system) that are direct children, then for each one it gets the list of all statuses which are either directly set or inherited from further sub-roles.

It inspects each status to be sure it’s of the type we want (via the enum), then it checks the return dictionary to make sure we don’t already have the status, add it if necessary, and once the loops exit successfully, the return values of the dictionary are cast into a return list of unique statuses the current Role object has access to.

This isn’t too bad, but it certainly isn’t all that intuitive to the regular programmer. One has to take a few minutes to “wrap ones head” around what is going on. More time spent on that is less time adding features or new functionality.

So, I’ve rewritten this type of code using LINQ. It goes something like…..

 

244 Public Function InheritedStatusesByType(ByVal a_TypeEnum As WorkObjectTypeEnum) As IEnumerable(Of Status) 245 'Try with LINQ

246 Dim ret = From rl In _ 247 m_CurrentRole.GetPermissionDL(Of Role)(), _

248 st In rl.GetPermissionFlat(Of Status)() _ 249 Where st.WorkObjectTypeEnum = a_TypeEnum _

250 Select st 251

252 Return ret 253 End Function

So far so good. This does basically the same thing using LINQ style query syntax. However the only issue with this is that it can potentially return duplicate statuses. This is because like in normal SQL, it is effectively doing a join on the detail records, and if more than one sub-role has access to the same status, it will be returned multiple times.

So, we need to figure out a way to do the equivalent of “DISTINCT” in SQL. Looking in the documentation one finds just that.

 

244 Public Function InheritedStatusesByType(ByVal a_TypeEnum As WorkObjectTypeEnum) As IEnumerable(Of Status) 245 'Try with LINQ

246 Dim ret = (From rl In _ 247 m_CurrentRole.GetPermissionDL(Of Role)(), _

248 st In rl.GetPermissionFlat(Of Status)() _ 249 Where st.WorkObjectTypeEnum = a_TypeEnum _

250 Select st).Distinct() 251

252 Return ret 253 End Function

The only problem is that when I run this with my example code, I still get duplicates. Hmmmm. Digging further into the Distinct syntax, I find that it’s default implementation uses the method GetHashCode() to determine equality. Obviously this won’t work, as our objects are distinct instances even if their data is the same. We need a way of overriding the Distinct logic without losing our readability.

This is where we find out about Extension Methods.

Here’s the code for the extension method we need to write to accomplish our own Distinct implementation:

 

9 <Extension()> _ 10 Public Function Distinct(Of T As PersistLoad)(ByVal source As IEnumerable(Of T)) As IEnumerable(Of T)

11 Return System.Linq.Enumerable.Distinct(source, New IDComparer(Of T)()) 12 End Function

Our source object is simply the same type that we want to write the extension off of (since that is the “type” we’re doing the Distinct operation on). However the second parameter is a custom class which implements the IEqualityComparer(Of T) interface, effectively defining how two objects (in this case inheriting from the base type PersistLoad) are defined as being “equal”.

 

2 Public Class IDComparer(Of T As PersistLoad) 3 Implements IEqualityComparer(Of T)

4 5 Public Function Equals1(ByVal x As T, ByVal y As T) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of T).Equals

6 Return x.ID = y.ID 7 End Function

8 9 Public Function GetHashCode1(ByVal obj As T) As Integer Implements System.Collections.Generic.IEqualityComparer(Of T).GetHashCode

10 Return obj.ID.GetHashCode() 11 End Function

12 End Class

In this case, all PersistLoad objects have a property called “ID” which defines it’s key or unique value. You could use any attribute to define equality in reality though.

So, implementing our extension method of the Distinct(Of T as PersistLoad) method and our custom comparer class which it uses, we can retry our LINQ query in our Role object. NOTE that we could literally write any number of Distinct extension methods, which because of the nature of polymorphism, will get called based on the calling type at runtime. In our case, the LINQ query is returning an IEnumerable(Of T as PersistLoad), therefore when calling Distinct on it, our extension method written for that context will be called rather than the default.

 

244 Public Function InheritedStatusesByType(ByVal a_TypeEnum As WorkObjectTypeEnum) As IEnumerable(Of Status) 245 'Try with LINQ

246 Dim ret = (From rl In _ 247 m_CurrentRole.GetPermissionDL(Of Role)(), _

248 st In rl.GetPermissionFlat(Of Status)() _ 249 Where st.WorkObjectTypeEnum = a_TypeEnum _

250 Select st).Distinct() 251

252 Return ret 253 End Function

So our final code (after the extension and comparer implementation) looks exactly the same as before. Now when we run this, no matter how many times a status shows up in the object tree of roles, it will only be returned a single time to the variable ret, which houses a custom iterator ultimately representing an IEnumerable(Of Status) that actually ends up being a List(Of Status) because of the underlying data structures.

Technorati Tags: , ,

Advertisements

1 Comment »

  1. Much like newspapers, they’ve got didn’t figure out how to
    translate for the new digital age. An SEO marketing company usually takes your web traffic
    and increase it immensely from your trickle to
    your raging torrent of visitors who are targeted looking to
    do business with YOU. By controlling yourself you’re switching off your neighbor.

    Comment by Pirate Bay Proxy — April 9, 2013 @ 10:20 pm | Reply


RSS feed for comments on this post. TrackBack URI

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

Create a free website or blog at WordPress.com.

%d bloggers like this: