Thursday, April 05, 2007 7:04 PM
bart
The IQueryable tales - LINQ to LDAP - Part 0: Introduction
Here we are again for some cool LINQ stuff. In the past I've been blogging on C# 3.0 language innovation quite a lot, including the core features that enable LINQ in its cute and nice form:
These posts focused on how C# 3.0 provides new constructs on top of existing ones in order to make various things simpler. In case you didn't read these posts yet, do it right now :-).
As you probably know by now, LINQ consists of a few pieces. One is the integration of querying syntax in programming languages like C# 3.0 and VB 9.0. This is what the integrated portion stands for. At the other side, different APIs exist that provide us with a gateway to underlying data sources. Today five of these are delivered by Microsoft in the current CTPs:
- LINQ-to-Objects - talks to in-memory objects (see my LINQSQO project; SQO stands for Standard Query Operators)
- LINQ-to-SQL - talks to SQL Server databases
- LINQ-to-XML - talks to hierarchical data represented in XML
- LINQ-to-DataSets - talks to DataSet objects and underlying DataTables with their relationships
- LINQ-to-Entities - talks to "entities", part of ADO.NET 3.0
There are a few differences between all of these however. Although the query syntax remains the same (it's integrated with the language after all and it has a "universal" character), the mechanisms that drive the functionality are different. For LINQ-to-Objects, the entire query expression is translated into IL at compile time by some mechanical procedure as outlined below:
List<Person> lstPersons = new List<Person>() { new Person { Name = "Bart", Age = 24 }, new Person { Name = "John", Age = 59 } };
var res = from person in lstPersons where person.Name == "Bart" select new { Name = person.Name.ToUpper() };
becomes
List<Person> lstPersons = new List<Person>() { new Person { Name = "Bart", Age = 24 }, new Person { Name = "John", Age = 59 } };
var res = lstPersons.Where(person => person.Name == "Bart").Select(person => new { Name = person.Name.ToUpper() });
where methods like Where and Select are extension methods to IEnumerable<T> that take a lambda expression as their parameter. The whole mechanism is driven by iterators behind the scenes.
However, LINQ query "providers" like LINQ-to-SQL and LINQ-to-XML work in a different way. Here we want to be able to translate the LINQ query to another domain specific query language (respectively SQL and XPath/XQuery) at runtime. In order to make this possible, the C# compiler doesn't generate IL code representing the query right away, but instead it spits out the code to create an in-memory representation of the query in the shape of an expression tree (see this link too). The compiler is told to do so when it encounters an IQueryable interface implementation used in a query expression.
In subsequent posts in this "The IQueryable tales - LINQ to LDAP" series, we'll talk about the creation of a query provider for LINQ that's capable of talking to Active Directory (and other LDAP data sources potentially) over LDAP. To set your mind, take a look at the concept of filtering expressions in LDAP on RFC 2254 and on TechNet (yes, an IT Pro resource on a developer's blog). As a little example to wet your appetite, take a look at a few queries below:
var users = new DirectoryObject<User>(new DirectoryEntry("LDAP://localhost"), SearchScope.Subtree);
var groups = new DirectoryObject<Group>(new DirectoryEntry("LDAP://localhost"), SearchScope.Subtree);
var res1 = from usr in users
select usr;
Console.WriteLine("QUERY 1\n=======");
foreach (var w in res1)
Console.WriteLine("{0}: {1} {2}", w.Name, w.Description, w.PasswordLastSet);
Console.WriteLine();
var res2 = from usr in users
where usr.Name == "A*"
select usr;
Console.WriteLine("QUERY 2\n=======");
foreach (var w in res2)
Console.WriteLine("{0}'s full name is {1}", w.Name, w.Dn);
Console.WriteLine();
int n = 10;
var res3 = from usr in users
where usr.Name == GetQueryStartWith("A") && usr.LogonCount > n && usr.Description.Contains("Built-in")
select new { usr.Name, usr.Description, usr.Groups, MaxLogonCount = n };
Console.WriteLine("QUERY 3\n=======");
foreach (var w in res3)
{
Console.WriteLine("{0} has logged on {2} times or more and belongs to {1} groups:", w.Name, w.Groups.Length, w.MaxLogonCount);
foreach (string group in w.Groups)
Console.WriteLine("- {0}", group);
}
Console.WriteLine();
var res4 = from usr in users
where (usr.Name.StartsWith("A") && usr.LogonCount > 2 * n) || usr.Name == "Guest"
select new { usr.Name, usr.Description, usr.Dn, usr.PasswordLastSet, Stats = new { usr.PasswordLastSet, usr.LogonCount, TwiceLogonCount = usr.LogonCount * 2 } };
Console.WriteLine("QUERY 4\n=======");
foreach (var w in res4)
Console.WriteLine("{0} has been logged on {1} times; password last set on {2}", w.Name, w.Stats.TwiceLogonCount - w.Stats.LogonCount, w.PasswordLastSet);
Console.WriteLine();
var res5 = from usr in users
orderby usr.Name ascending //not supported in LDAP; alternative in-memory sort
select usr;
Console.WriteLine("QUERY 5\n=======");
foreach (var w in res5)
Console.WriteLine("{0}: {1}", w.Name, w.Description);
Console.WriteLine();
var res6 = from grp in groups
where grp.Name.EndsWith("ators")
select new { grp.Name, MemberCount = grp.Members.Length };
Console.WriteLine("QUERY 6\n=======");
foreach (var w in res6)
Console.WriteLine("{0} has {1} members", w.Name, w.MemberCount);
Console.WriteLine();
The result of the queries above looks like this on my test machine (including the corresponding LDAP queries that were created at runtime):
You'll be able to execute those against a live Active Directory domain controller or an ADAM instance pretty soon! This being said, you should be warned not to expect a feature complete query provider for LDAP but rather a hands-on demo implementation. In the meantime, get ready for subsequent posts in this series by installing the March 07 CTP of Orcas (if you haven't done yet).
Read on ... The IQueryable tales - LINQ to LDAP - Part 1: Key concepts
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: C# 3.0, LINQ