与PrincipalSearcher相比,为什么DirectorySearcher这么慢?

时间:2017-07-27 17:55:51

标签: c# active-directory

我们的应用程序有一个从Active Directory获取所有用户并使用其信息更新相关SQL表的过程。这个过程在晚上和几年前写的 - 所以它的遗留代码有效,“如果没有破坏,就不要修复它”。然而,我们正在为我们的应用程序引入一个新功能,需要对此代码进行修改,并且由于多年来没有被触及,我想我也可以稍微清理一下。

除了罕见的服务器故障之外,所述进程仅在夜间运行,在这种情况下,我们必须在白天手动运行它。该过程使用良好的旧System.DirectoryServices库来完成它的工作,虽然它有效但运行速度很慢。

我考虑过使用较新的System.DirectoryServices.AccountManagement库,所以我开始重写整个过程(几百行代码),我惊讶地看到PrincipalSearcher 显着优于DirectorySearcher

我一直在努力寻找原因,然后来到the following SO answer,对两者进行了比较,指出DirectorySearcher应该比PrincipalSearcher更快。

我启动了一个测试项目,以确保我没有产生幻觉:

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 = "(&(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);
    }
}

查询数千名用户,每位用户PrincipalSearcher约为0.9毫秒(约34,000名用户约为30秒),每位用户约为5.2毫秒DirectorySearcher(约2分30秒)对于~34k用户而言 - PrincipalSearcher几乎快了六倍。

我尝试调试并将PrincipalSearcher的基础DirectorySearcher与我创建的PrincipalSearcher进行比较,它们看起来非常相似。

我尝试进一步检查,如果我使用DirectorySearcher底层搜索者的搜索根,那么我创建的PrincipalSearcher实际上优于 // ... 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)) { // ... }

DirectoryEntry

调试时我发现搜索根基本相同 - 即它们代表相同的域。

什么可能导致搜索速度变慢?

2 个答案:

答案 0 :(得分:3)

在撰写此问题时,我正在修改测试代码并设法找到问题。通过在构造根// var root = new DirectoryEntry("LDAP://DC=mydomain,DC=com"); var root = new DirectoryEntry("LDAP://mydomain.com/DC=mydomain,DC=com"); 时提供域地址:

DirectorySearcher

PrincipalSearcher的搜索结果优于{{1}}。我不确定为什么 - 也许这与搜索者寻找结果的地方有关 - 但它确实提高了搜索速度。

答案 1 :(得分:1)

看看my question and answer on the differences between the two methodsPrincipalSearcher只是DirectorySearcher的包装。它的设计使它可以更轻松地使用Active Directory,同时提供了一些自动速度增强功能。 DirectorySearcher可能比PrincipalSearcher快得多,但是需要更多的工作。

您从“旧内容”代码中看到行为缓慢的主要原因是,当您在“新内容”中使用PrincipalSearcher时,您获得了基础DirectorySearcher并填充了其{{ 1}}集合。您没有在“旧资料”代码中这样做。

PropertiesToLoad

结果,您的“旧资料”代码为匹配的结果提取了每个AD属性(即,正在传输的数据大大增加),而使用var properties = new[] { "cn", "name", "distinguishedname", "surname", "title", "displayname" }; //... var underlying = searcher.GetUnderlyingSearcher() as DirectorySearcher; //... underlying.PropertiesToLoad.AddRange(properties); 的实现仅读取6个属性。

使用PrincipalSearcher时通常也不需要这样做,因为它可以自行处理属性的缓存和选择。实际上,在使用PrincipalSearcher时,您唯一需要获取基础PrincipalSearcher的时间就是设置DirectorySearcher,因为PageSize没有提供标准的设置方法。

我怀疑您在指定域时看到改进的原因是它不必做任何工作即可确定域名。在这方面,您不公平地给“新事物”一个开始,因为可以这么说,您是在开始计时之前制作了PrincipalSearcher

PrincipalContext

我在您的代码中注意到的其他一些事情实际上会改变时间的相反方向,那就是在“新内容”中,您不进行任何过滤,并且对委托线程进行初始化以显示进度在发生之后您记录了开始时间。