【问题标题】:Why is DirectorySearcher so slow when compared to PrincipalSearcher?与 PrincipalSearcher 相比,为什么 DirectorySearcher 这么慢?
【发布时间】:2017-07-27 17:55:50
【问题描述】:

我们的应用程序有一个从 Active Directory 获取所有用户并使用他们的信息更新相关 SQL 表的过程。晚上的过程,它是几年前写的——所以它是有效的遗留代码,“如果它没有坏,就不要修复它”。然而,我们正在向我们的应用程序引入一项新功能,该功能需要修改此代码,并且由于多年未触及它,我想我不妨稍微清理一下。

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

我考虑改用较新的 System.DirectoryServices.AccountManagement 库,所以我开始重写整个过程(几百行代码),我惊讶地发现 PrincipalSearcher 大大 优于 @ 987654325@.

我一直在寻找原因,发现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 毫秒(约 34k 用户约 30 秒)和DirectorySearcher 每位用户约 5.2 毫秒(约 34k 用户约 2 分 30 秒) ) - PrincipalSearcher 几乎快了六倍。

我尝试调试并将PrincipalSearcher 的底层DirectorySearcher 与我创建的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))
        {
            // ...
        }

在调试时我发现搜索根在很大程度上是相同的 - 即它们代表同一个域。

什么会导致搜索速度这样变慢?

【问题讨论】:

    标签: c# active-directory


    【解决方案1】:

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

    您看到“旧东西”代码行为缓慢的主要原因是,当您在“新东西”中使用 PrincipalSearcher 时,您得到了底层 DirectorySearcher 并提供了它的 PropertiesToLoad 集合。您没有在“旧东西”代码中这样做。

    var properties = new[] { "cn", "name", "distinguishedname", "surname", "title", "displayname" };
    //...
    var underlying = searcher.GetUnderlyingSearcher() as DirectorySearcher;
    //...
    underlying.PropertiesToLoad.AddRange(properties);
    

    因此,您的“旧东西”代码会提取每个 AD 属性以获得匹配的结果(即传输的数据要多得多),而您使用 PrincipalSearcher 的实现仅读取 6 个属性。

    在使用PrincipalSearcher 时这样做通常也不是必需的,因为它自己处理缓存和选择属性。确实,在使用PrincipalSearcher 时,您唯一需要获取底层DirectorySearcher 是设置PageSize,因为PrincipalSearcher 没有提供设置它的标准方法。

    我怀疑您在指定域时看到改进的原因是它不需要做任何工作来找出域名。在这方面,您不公平地让“新事物”领先,因为您可以说是在开始计时之前创建了PrincipalContext

    // 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; // you should have done this BEFORE setting `context`. 
    

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

    【讨论】:

      【解决方案2】:

      在写这个问题时,我正在修改测试代码并设法找到问题所在。通过在构造根DirectoryEntry时提供域地址:

      // var root = new DirectoryEntry("LDAP://DC=mydomain,DC=com");
      var root = new DirectoryEntry("LDAP://mydomain.com/DC=mydomain,DC=com");
      

      DirectorySearcher 的搜索效果优于 PrincipalSearcher。我不确定为什么——也许这与搜索者在哪里寻找结果有关——但它确实提高了搜索速度。

      【讨论】:

        猜你喜欢
        • 2012-05-05
        • 2011-02-11
        • 2012-05-25
        • 1970-01-01
        • 2020-11-25
        • 1970-01-01
        • 2021-09-04
        • 2013-02-22
        • 2015-03-02
        相关资源
        最近更新 更多