Although directory services store a wealth of useful information, the volumes of data they contain can easily result in slow search performance. A slow directory service gives an overall impression that the application is slow. Although most directory service solutions, such as Active Directory, are scalable and improve as additional hardware is added, improvements to the search parameters can more easily address the problem. The following guidelines can significantly improve the performance of a search and, as a result, the application.
Improving the performance of a search has a lot to do with choosing the optimal starting point for the search. This starting point can be either the root of the hierarchy, an object entry, or a container. One-level searches on a container are faster than subtree searches over the entire hierarchy. When you know the name of the container where the object is, use the name of the container and perform a one-level search. After you find the object, bind to the object using its name to access its attributes:
public void ConnectToSpecificUser() { //connect as close as possible to target object String strConnection = "LDAP://srv-enterprise/CN=User/UID=alyssa"; DirectoryEntry direntry = new DirectoryEntry( strConnection ); return; }
The scope of a search also impacts performance. You can select three scopes for a search: base, one level, and subtree. The base scope is the fastest scope to use for retrieving a single object. The one-level scope is the fastest way to retrieve multiple objects. When the objects are located in a single known container, you should use the one-level scope to return the attributes for one or more objects. The subtree scope retrieves data from multiple containers. When you need to return the attributes for one or more objects contained in multiple containers in the same hierarchy, you should use the subtree scope
When performing a search, specify only the attributes needed in the returned results. By default, all attributes associated with an object are returned. The fewer attributes that need to be loaded, the faster the results can be built and returned. This is where the PropertiesToLoad property becomes handy. In this example, only the givenname and sn attributes are necessary to build a user list. To increase performance, only those properties are requested :
public void RetrieveSpecificData() { String strConnection = "LDAP://srv-enterprise/CN=User"; //establish connection to base object DirectoryEntry dirEntry = new DirectoryEntry( strConnection ); DirectorySearcher dirSearcher = new DirectorySearcher( dirEntry ); //search for the specific user dirSearcher.Filter = "(&(objectClass=User)(cn=\"Lonnie Scully\"))"; dirSearcher.SearchScope = SearchScope.Subtree; //specify the data to retrieve dirSearcher.PropertyNamesOnly = true; dirSearcher.PropertiesToLoad.Add( "givenname" ); dirSearcher.PropertiesToLoad.Add( "sn" ); //perform the search SearchResult searchResult = dirSearcher.FindOne(); return; }
Although the directory service has powerful support for partial string matches, it is time consuming. If partial matches are necessary, place the wildcard character at the end of the search string, rather than the beginning. A query, such as (cn=alyssa*) performs much faster than (cn=*lyss*) or (cn=*lyssa) .
When reading attributes from a directory service object, such as a User object, load all attributes at once and store them locally. This way, you can retrieve attributes more quickly than retrieving a number of attributes from the directory service a little at a time:
class UserData { string _Lastname = ""; string _Firstname = ""; public void GetUserFullName() { String strConnection = "LDAP://srv-enterprise/CN=User"; //establish connection to base object DirectoryEntry dirEntry = new DirectoryEntry( strConnection ); DirectorySearcher dirSearcher = new DirectorySearcher( dirEntry ); //search for the specific user dirSearcher.Filter = "(&(objectClass=User)(cn=\"Lonnie Scully\"))"; dirSearcher.SearchScope = SearchScope.Subtree; SearchResult searchResult = dirSearcher.FindOne(); //retrieve and store the user data DirectoryEntry dirResult = searchResult.GetDirectoryEntry(); _Firstname = dirResult.Properties["givenname"].Value.ToString(); _Lastname = dirResult.Properties["sn"].Value.ToString(); return; } }
Because directory searches can take time, applications should perform only an initial search for an object. Once found, the application should store the unique identifier, or GUID, for that object. If another connection in the future is necessary, it is faster to connect directly to the object based on its GUID rather than perform another search for the same object:
public string RetrieveNodeGuid() { String strConnection = "LDAP://srv-enterprise/CN=User"; //establish connection to base object DirectoryEntry dirEntry = new DirectoryEntry( strConnection ); DirectorySearcher dirSearcher = new DirectorySearcher( dirEntry ); //search for the specific user dirSearcher.Filter = "(&(objectClass=User)(cn=\"Lonnie Scully\"))"; dirSearcher.SearchScope = SearchScope.Subtree; SearchResult result = dirSearcher.FindOne(); //return the object's GUID for later binding return result.GetDirectoryEntry().NativeGuid; } public void ConnectByGuid( string strGuid ) { //bind to the object by its native guid DirectoryEntry dirEntry = new DirectoryEntry( strGuid ); //interact with bound object's properties string strFirstname = dirEntry.Properties["givenname"].Value.ToString(); string strLastname = dirEntry.Properties["sn"].Value.ToString(); return; }
Because the operational status of a directory service is unknown, it can be difficult to determine if a directory service is performing a long search or is simply not available. If a directory service is not available, then using timeouts enables searches to simply time out rather than never returning at all:
public SearchResult SearchWithTimeout() { String strConnection = "LDAP://srv-enterprise/CN=User"; //establish connection to base object DirectoryEntry dirEntry = new DirectoryEntry( strConnection ); DirectorySearcher dirSearcher = new DirectorySearcher( dirEntry ); //set the timeout to one minute dirSearcher.ClientTimeout = new TimeSpan( 0, 1, 0 ); //set the filter to retrieve the specific user dirSearcher.Filter = "(&(objectClass=User)(cn=\"Lonnie Scully\"))"; dirSearcher.SearchScope = SearchScope.Subtree; //execute the search return dirSearcher.FindOne(); }