【问题标题】:Active directory query with wildcards has poor performance使用通配符的 Active Directory 查询性能较差
【发布时间】:2026-02-17 10:35:01
【问题描述】:

我正在用 C# 编写一个方法,它应该查询 Active Directory 并查找所有用户和组的显示名称格式为 {displayName}(通配符搜索带有前导通配符和尾随通配符),方法将用于自动完成字段。

问题是我写的方法的性能真的很差,尝试查询 AD 需要 30 秒到一整分钟之间的任何时间,具体取决于查询字符串。

我的组织的广告非常大,但如果需要这么长时间,自动完成字段将毫无意义。

这是我现在使用的代码:

// Intialize the results list.
result.queryResult = new List<Classses.ADSearchObject>();

// Set up domain context.
PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain, Constants.adQueryUser, Constants.adQueryPassword);

// Set up a directory searcher.
DirectorySearcher dSearcher = new DirectorySearcher();
// Define a SearchCollection to store the results.
SearchResultsCollection searchCol;
// Define returned result paging for performance.
dSearcher.PageSize = 1000;
// Define the properties to retrieve
dSearcher.PropertiesToLoad.Add("sAMAccountName");
dSearcher.PropertiesToLoad.Add("displayName");
// Define the filter for users.
dSearcher.Filter = $"(|(&(displayName = {result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))";

// Search based in filter and save the results.
searchCol = dSearcher.FindAll();

// Add the results to the returned object 
foreach (SearchResult searchResult in searchCol)
{
   DirectoryEntry de = searchResult.GetDirectoryEntry();
   // Code to get data from the results...
}

// Define the filter for groups.
dSearcher.Filter = $"(|(&(displayName={result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))";

// Search based in filter and save the results.
searchCol = dSearcher.FindAll();

// Add the results to the returned object 
foreach (SearchResult searchResult in searchCol)
{
   DirectoryEntry de = searchResult.GetDirectoryEntry();
   // Code to get data from the results...
}

目前搜索分为用户和组,以便于区分它们,但如果它显着提高性能,我会将它们统一为一个搜索。

编辑:正如用户 rene 建议的那样,我使用 Stopwatch 来检查 FindAll 所需的时间,并且我还检查了我的 foreach 循环需要多长时间。

我发现 FindAll 调用大约需要 100 毫秒(非常快),即使使用 AD 索引的前导通配符(不是)进行搜索。

显然,耗时最长的调用是我的 foreach 循环,大约需要 40 秒(40,000 毫秒)。

我正在使用 foreach 循环中的代码块更新问题,因为我还没有弄清楚如何提高其性能:

// --- I started a stopwatch here
foreach (SearchResult searchResult in searchCol)
{
   // --- I stopped the stopwatch here and noticed it takes about 30,000ms
   result.code = 0;

   DirectoryEntry de = searchResult.GetDirectoryEntry();

   ADSearchObject adObj = new ADSearchObject();

   adObj.code = 0;

   if (de.Properties.Contains("displayName")
   {
        adObj.displayName = de.Properties["displayName"].Value.ToString();
   }

    adObj.type = "user";

    result.queryResults.Add(adObj);
}

请注意我在更新的代码中开始和停止“秒表”的位置,我不知道为什么开始循环需要这么长时间。

【问题讨论】:

  • 这是处理微软广告时的工作方式。也许您可以考虑将 AD 树与某种 DB 同步,这样您就可以在几毫秒内执行查询,而不是每次都查询 AD。
  • (&amp;(displayName=*{result.querystring}*)(objectCategory=person)) 不会和你的过滤器一样吗?你能秒表FindAll 电话吗?那些花时间最多的人吗?
  • @rene 请查看我对帖子所做的编辑,我添加了来自Stopwatch 我在代码的不同部分打开的时间
  • 只需调用FindAll() 即可准确检索您需要的内容。不要无缘无故地使用GetDirectoryEntry()来一一检索完整条目。难怪盯着屏幕看会变老。
  • @marabu 请参考我的编辑,进入foreach 循环时会出现性能下降。在初始进入循环后,性能与您预期的一样快,并且对GetDirectoryEntry() 的调用不会花费特别长的时间

标签: c# active-directory ldap ldap-query


【解决方案1】:

当然,子字符串匹配比唯一值的相等匹配成本更高。此外,大部分已用时间落在您的迭代器块中也就不足为奇了,根据您的分析,这总共消耗了 40 秒。

如果您确信仅通过设置迭代器就会导致性能大幅下降,我不会——那是因为您选择了时间点。

StartClock("foreach");
foreach (SearchResult searchResult in searchCol)
{
    // use an empty block to speed things up or
    StopClock("foreach");
    // whatever
    RestartClock("foreach");
}
StopClock("foreach");
LogClock("foreach");

如果您注意我已经评论过的最佳实践,我预计会获得巨大的性能提升(对于较大的条目数):向服务器发送一个请求,接收您在搜索结果中需要的所有内容,而不是发送另一个请求每个项目的请求。虽然对 GetDirectoryEntry() 的一次调用只会消耗不到 1 毫秒,但大量的条目将使您的代码对您的应用程序自动完成功能毫无用处。

感谢@rene 为该过滤器表达式提供了一个正常形式。我不知道 Active Directory 中的过滤器优化,所以我会采取肯定的路径

(&(objectCategory=person)(displayName=*{result.querystring}*))

【讨论】: