title: Why is DirectorySearcher so slow when compared to PrincipalSearcher?
body: <p>Our application has a process which fetches all users from Active Directory and updates the relevant SQL tables with their information. The process at nights and it was written a few years ago - so it's legacy code which works and "if it ain't broken, don't fix it". However we're introducing a new feature to our application which requires modifications to this code, and since it hasn't been touched for years I thought I might as well clean it up a little.</p>
  
  <p>Said process runs ONLY during the night except for rare server faults, in which case we have to run it manually during the day. The process uses the good old <code>System.DirectoryServices</code> library to do its job, and although it works, it runs quite slowly.</p>
  
  <p>I thought about using the newer <code>System.DirectoryServices.AccountManagement</code> library instead, so I started rewriting the whole process (a few hundreds lines of code) and I was amazed to see that <code>PrincipalSearcher</code> <strong>dramatically</strong> outperforms <code>DirectorySearcher</code>.</p>
  
  <p>I've been trying to look for the reason and came upon <a href="https://stackoverflow.com/a/26321067/4183902">the following SO answer</a> which gives a comparison between the two, stating that <code>DirectorySearcher</code> should be faster than <code>PrincipalSearcher</code>.</p>
  
  <p>I fired up a test project to make sure I was not hallucinating:</p>
  
  <pre><code>class Program
  {
      static void Main(string[] args)
      {
          // New stuff
          var context = new PrincipalContext(ContextType.Domain, "mydomain.com");
          var properties = new[] { "cn", "name", "distinguishedname", "surname", "title", "displayname" };
          var i = 0;
          var now = DateTime.Now;
  
          new Thread(delegate()
          {
              while (true)
              {
                  Console.Write("\r{0} ms, {1} results", (DateTime.Now - now).TotalMilliseconds, i);
                  Thread.Sleep(1000);
              }
          }).Start();
  
          using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
          {
              var underlying = searcher.GetUnderlyingSearcher() as DirectorySearcher;
              underlying.PageSize = 1000;
              underlying.PropertiesToLoad.Clear();
              underlying.PropertiesToLoad.AddRange(properties);
              underlying.CacheResults = false;
  
              using (var results = searcher.FindAll())
              {
                  foreach (var result in results)
                  {
                      i++;
                  }
              }
          }
  
          Console.WriteLine("It took {0}", (DateTime.Now - now).TotalMilliseconds);
          now = DateTime.Now;
          i = 0;
  
          // Old stuff
          var root = new DirectoryEntry("LDAP://DC=mydomain,DC=com");
          var filter = "(&amp;(objectCategory=user)(objectClass=user))";
  
          using (var searcher = new DirectorySearcher(root, filter, properties))
          {
              searcher.PageSize = 1000;
              searcher.CacheResults = false;
  
              using (var results = searcher.FindAll())
              {
                  foreach (var result in results)
                  {
                      i++;
                  }
              }
          }
  
          Console.WriteLine("It took {0}", (DateTime.Now - now).TotalMilliseconds);
      }
  }
  </code></pre>
  
  <p>Querying some thousand users, the results were around 0.9ms per user with <code>PrincipalSearcher</code> (around 30 seconds for ~34k users) and around 5.2ms per user with <code>DirectorySearcher</code> (around 2 minutes and 30 seconds for ~34k users) - <code>PrincipalSearcher</code> being almost six times faster.</p>
  
  <p>I tried debugging and comparing the <code>PrincipalSearcher</code>'s underlying <code>DirectorySearcher</code> with the one I created and they seemed pretty much similar.</p>
  
  <p>I tried examining further ahead and it seems that if I use the search root from the <code>PrincipalSearcher</code>'s underlying searcher, then the <code>DirectorySearcher</code> I create actually outperforms the <code>PrincipalSearcher</code>:</p>
  
  <pre><code>        // ...
  
          DirectoryEntry psRoot;
  
          using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
          {
              var underlying = searcher.GetUnderlyingSearcher() as DirectorySearcher;
              psRoot = underlying.SearchRoot;
  
              // ...
          }
  
          // ...
  
          using (var searcher = new DirectorySearcher(psRoot, filter, properties))
          {
              // ...
          }
  </code></pre>
  
  <p>While debugging I found out that the search roots are largely the same - i.e., they represent the same domain.</p>
  
  <p>What could cause the search speed to slow down like this?</p>