与 PrincipalSearcher 相比,为什么 DirectorySearcher 如此缓慢?

Adi*_*ber 6 c# active-directory

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

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

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

我一直在试图寻找原因并找到了以下 SO 答案,该答案对两者进行了比较,指出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);
    }
}
Run Code Online (Sandbox Code Playgroud)

查询大约一千个用户,结果是每个用户PrincipalSearcher大约 0.9 毫秒DirectorySearcher(大约34k 用户大约 30 秒)和每个用户大约 5.2 毫秒(大约 34k 用户大约 2 分 30 秒)——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))
        {
            // ...
        }
Run Code Online (Sandbox Code Playgroud)

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

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

Adi*_*ber 5

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

// var root = new DirectoryEntry("LDAP://DC=mydomain,DC=com");
var root = new DirectoryEntry("LDAP://mydomain.com/DC=mydomain,DC=com");
Run Code Online (Sandbox Code Playgroud)

的搜索DirectorySearcher优于 的搜索PrincipalSearcher。我不太清楚为什么 - 也许这与搜索者寻找结果的位置有关 - 但它确实提高了搜索速度。


Dre*_*pin 5

看看我关于这两种方法之间差异的问答PrincipalSearcher只是围绕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);
Run Code Online (Sandbox Code Playgroud)

结果,您的“旧东西”代码正在提取匹配结果的每个 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`. 
Run Code Online (Sandbox Code Playgroud)

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