Executing the Query and Enumerating Results

Once we have chosen the search root, scope, filter, attribute list, and number of results to return, we are actually ready to execute the query and examine the results. The FindOne and FindAll methods do this.

Finding a Single Object with FindOne

FindOne is a convenient shortcut when we want only one result back and do not want to write the extra code to enumerate the SearchResultCollection. If no object matches the query, FindOne simply returns null.

FindOne internally calls FindAll under the hood to perform the actual query and returns the first result found. As such, it is never faster than FindAll and we can never guarantee that only one object matched the query. FindOne is best used in conjunction with a filter designed to match only a single object in the query scope. Listing 4.11 demonstrates a typical use of FindOne.

Listing 4.11. A Typical Invocation of FindOne

DirectoryEntry root = null;
DirectorySearcher ds = null;
SearchResult result = null;

using (root = new DirectoryEntry(
 "LDAP://CN=Users,DC=domain,DC=com",
 null,
 null,
 AuthenticationTypes.Secure
 ))
{
 using (ds = new DirectorySearcher(root))

 {
 ds.Filter = "(sAMAccountName=jkaplan)";
 ds.SizeLimit = 1;
 result = ds.FindOne();
 }
}
if (result != null)
 Console.WriteLine(result.Path);

Listing 4.11 demonstrates how we might use FindOne to locate a user whose log-on name is jkaplan by doing a subtree search starting in our domain's Users container using the current thread's Windows credentials to access the directory. Here, the filter uses the Active Directory sAMAccountName attribute, which happens to be unique within a given domain, so we have some certainty that using the = filter type will match at most only one object in the scope of a single domain. We have taken the defaults for most of the properties, such as SearchScope and PropertiesToLoad, but we can obviously change those as needed.

We have also applied the C# using statement to wrap both of the objects that implement the IDisposable interface. We discussed this in Chapter 3 several times, including in the Close or Dispose? sidebar. It is important to apply this technique to all IDisposable objects to ensure timely resource cleanup, especially in long-running server processes like web applications, even if the code is slightly more verbose.

Beware the FindOne Memory Leak Bug

Users are discouraged from using the FindOne method in versions of .NET prior to 2.0 because of a bug culminating in a memory leak. Essentially, the bug will manifest itself whenever FindOne is called and no result is returned. The underlying SearchResultCollection that was obtained was not properly disposed in this situation, leading to a leak. This code demonstrates what we mean:

//given a DirectorySearcher ds
SearchResult result = ds.FindOne();
if (result==null)
{
 //WARNING: memory leak here under the hood!
}
else
{
 //No memory leak here; we found an object
}
 

This bug is obviously quite easy to run across, as we often wish to search for objects that may not exist. In a long-running server process such as a web application, these leaks can build up over time and cause instability.

Luckily, we can create a simple function to work around this problem:

SearchResult FindOne(DirectorySearcher ds)
{
 SearchResult result = null;
 ds.SizeLimit = 1;
 using (SearchResultCollection results =
 ds.FindAll())
 {
 foreach (SearchResult res in results)
 {
 result = res;
 break;
 }
 return result;
 }
}
 

This bug is fixed as of .NET 2.0.

 

Getting Multiple Results with FindAll

FindAll allows us to return multiple results that match our query. Its usage is similar to FindOne, except that FindAll returns a SearchResultCollection that we must enumerate to retrieve the individual results. Let's modify Listing 4.11 to find the first 100 objects in the same container that have an email address, and then dump the addresses to the console (see Listing 4.12).

Listing 4.12. Finding Objects with Email Addresses Using FindAll

DirectoryEntry root = null;
DirectorySearcher ds = null;
SearchResultCollection results = null;

using (root = new DirectoryEntry(
 "LDAP://CN=Users,DC=domain,DC=com",
 null,
 null,
 AuthenticationTypes.Secure
 ))
{
 using (ds = new DirectorySearcher(root))
 {
 ds.Filter = "(mail=*)";
 ds.SizeLimit = 100;
 ds.PropertiesToLoad.Add("mail");
 using (results = ds.FindAll())
 {
 foreach(SearchResult result in results)
 {
 Console.WriteLine(result.Properties["mail"][0]);
 }
 }
 }
}

In Listing 4.12, we have added the mail attribute to the list of attributes to return. This causes the search to return this single attribute instead of returning all of the object's nonconstructed attribute values. A tiny change like this can have enormous performance impacts on a search, especially if the objects matched by the query contain large amounts of data.

We have once again wrapped the use of the SearchResultCollection in a using statement, as it too implements the IDisposable interface. Have we harped on this enough?

Enumerating the Results

We tend to use the foreach statement to enumerate the results. Unlike some other collection classes in the .NET Framework, SearchResultCollection internally implements its own private IEnumerator object that pulls the results from the directory. Any access to the SearchResultCollection properties, such as inspecting the Count property, will cause the entire result set to be retrieved from the directory and then enumerated. This can have a large impact, especially when returning many results, for a couple of reasons. First, using a foreach loop would allow us to break off the search if necessary, without enumerating all the results completely. This is more efficient, as we break off the search early and do not force the server to return all of the results first. Second, we can process each result as it comes from the server using a foreach loop without waiting for the entire search to complete. Using a for loop in conjunction with the Count property prevents both of these scenarios. As such, we generally recommend using the foreach loop when enumerating our results.

Since we knew that each result returned in Listing 4.12 would have a mail attribute (our filter dictated that), we can safely dig into the collection results without fear of getting a NullReferenceException or Index-OutOfRangeException. If we were not sure whether the SearchResult actually contained a particular attribute value, we would want to check first, using the Contains method.

Returning Many Results with Paged Searches

Part I: Fundamentals

Introduction to LDAP and Active Directory

Introduction to .NET Directory Services Programming

Binding and CRUD Operations with DirectoryEntry

Searching with the DirectorySearcher

Advanced LDAP Searches

Reading and Writing LDAP Attributes

Active Directory and ADAM Schema

Security in Directory Services Programming

Introduction to the ActiveDirectory Namespace

Part II: Practical Applications

User Management

Group Management

Authentication

Part III: Appendixes

Appendix A. Three Approaches to COM Interop with ADSI

Appendix B. LDAP Tools for Programmers

Appendix C. Troubleshooting and Help

Index



The. NET Developer's Guide to Directory Services Programming
The .NET Developers Guide to Directory Services Programming
ISBN: 0321350170
EAN: 2147483647
Year: 2004
Pages: 165

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net