I've encountered a bit of a performance issue in a little Active Directory
searching tool i've created.
The issue is extremely similar to this one, essentially i'm retrieving a list of users from AD via the below code:
DirectoryEntry dEntry = new DirectoryEntry("LDAP://ldappath");
DirectorySearcher searchdirectory = new DirectorySearcher(directory);
searchdirectory.Filter = "(&(objectClass=user)(objectcategory=person)(|(name=" + searchString + "*)(givenname=" + searchString + "*)(sn=" + searchString + "*)(mail=" + searchString + "*)(telephonenumber=" + searchString + "*)(samaccountname=" + searchString + "*)))";
searchdirectory.Sort.PropertyName = "name"; // Optional sorting by attribute
searchdirectory.SizeLimit = 100; // Sets the search limit
searchdirectory.PageSize = 1000; // Sets the results per returned page
SearchResultCollection collectionResults = searchdirectory.FindAll();
directory.Dispose();
searchdirectory.Dispose();
List<SearchResult> results = new List<SearchResult>();
for (int i = 0; collectionResults.Count > i; i++)
{
results.Add(collectionResults[i]);
}
return results;
The above returns the SearchResultCollection
near instantly (~4ms) regardless of the amount of results (the server is a 2012-R2 VM with large resources) or the returned attributes, the issue occurs when anything attempts to access SearchResultCollection
, including a SearchResultCollection.Count
.
For 10 results of all attributes and the searchString
being blank, it takes roughly 65ms, 100 results takes 240ms and 1000 takes 1950ms. Searching for an actual string seems to add ~350ms to any amount of results, regardless of # of results or search string. It also appears to slower to search "a" compared to "*a".
This would usually be perfectly fine, but on slower PC's this can take 30+ seconds for even 50 results.
The speed is affected by what's in the SearchResultCollection
, for example if I add searchdirectory.PropertiesToLoad.Add("cn")
it will take a fraction of the time (1/10th or lower). So i'm assuming the issue stems from the amount of data within the SearchResultCollection
and the time it takes to cache/move it to memory or similar (still a bit new with programming).
It also makes a large difference if I change the fields that it is searching by, but this should not make a difference as the time issue is when accessing the SearchResultCollection
and not when calling FindAll()
.
What i'd like to know and clarify is why does it take time when accessing the SearchResultCollection
and not when actually performing the search, to my knowledge it should be retrieving all results from LDAP when I call FindAll()
.
What would also be great is if someone can assist in any way (with an example or pointing to a learning resource) with making this code run more efficiently without compromising heavily on returned attributes or the search parameters.
I know Powershell can retrieve these sorts of details near instantly, so I'm thinking that there must be a better approach to the issue.
It's pretty common in .NET for various types of lists to not be completely available until you actually access the list. So that part is not surprising.
Also, if you don't set PropertiesToLoad
, then all properties with values are returned, for every account in the results. So it is a good idea to set PropertiesToLoad
to only the attributes you need.
And yes, the query can affect performance. It depends on which attributes you're searching. In this case, telephoneNumber
is not indexed. That means that it must look at every account to check the phone number. I suspect if you just remove that condition, then your query will be much faster.
This may or may not help you, but you can look at Ambiguous Name Resolution. You use this in the LDAP query (anr=" + searchString + ")"
and AD will search against various attributes (listed in the documenation).
And as EricLavault mentioned, you should dispose the results. From the documentation for FindAll()
:
Due to implementation restrictions, the SearchResultCollection class cannot release all of its unmanaged resources when it is garbage collected. To prevent a memory leak, you must call the Dispose method when the SearchResultCollection object is no longer needed.