ActiveDirectory DirectorySearcher:为什么 FindOne() 比 FindAll() 慢以及为什么省略属性?

问题描述 投票:0回答:3

我有一个循环从 ActiveDirectory 检索一些信息。结果发现这是一个很大的性能瓶颈。

此片段(在执行 31 次的循环内)花费了 00:01:14.6562500(1 分 14 秒):

SearchResult data = searcher.FindOne();
System.Diagnostics.Trace.WriteLine(PropsDump(data));

用此片段替换它,将其降低到 00:00:03.1093750(3 秒):

searcher.SizeLimit = 1;
SearchResultCollection coll = searcher.FindAll();
foreach (SearchResult data in coll)
{
    System.Diagnostics.Trace.WriteLine(PropsDump(data));
}

结果完全相同,相同的属性以相同的顺序返回。我在另一个线程中找到了一些有关内存泄漏的信息,但他们没有提到性能(我使用的是.Net 3.5)。


以下实际上是一个不同的问题,但它提供了一些关于我为什么要循环的背景:

我想在一个查询中获取所有属性,但我无法让 DirectorySearcher 一次性返回所有想要的属性(它省略了 PropertiesToLoad 中指定的大约 30% 的属性(也尝试在构造函数中设置它,这使得没有区别),我发现其他人也有同样的问题,这是他的解决方案(循环遍历它们)。当我像这样循环遍历它们时,使用 FindOne() 或 FindAll() 我确实获得了所有属性,但实际上这一切感觉像是一种解决方法。

我错过了什么吗?


编辑:

问题似乎出在我使用 DirectorySearcher 获取第一个 DirectoryEntry 的方式上。

这是导致 DirectorySearcher 仅返回部分属性的代码:

private static DirectoryEntry GetEntry() {
    DirectoryContext dc = new DirectoryContext(DirectoryContextType.DirectoryServer, "SERVERNAME", "USERNAME", "PASSWORD");
    Forest forest = Forest.GetForest(dc);
    DirectorySearcher searcher = forest.GlobalCatalogs[0].GetDirectorySearcher();

    searcher.Filter = "OU=MyUnit";
    searcher.CacheResults = true;
    SearchResultCollection coll = searcher.FindAll();
    foreach (SearchResult m in coll)
    {
        return m.GetDirectoryEntry();
    }
    throw new Exception("DirectoryEntry not found");
}

用这一行替换了一大堆内容后,DirectorySearcher 返回了所有属性并且不再需要循环:

private static DirectoryEntry GetEntry2()
{
    return new DirectoryEntry(@"LDAP://SERVERNAME/OU=MyUnit,DC=SERVERNAME,DC=local", "USERNAME", "PASSWORD");
}

现在只需不到十八秒的时间即可获得 31 个条目的所有所需属性。 因此,同一 DirectoryEntry 的两个不同实例似乎可以根据其构造方式给出不同的结果......感觉有点令人毛骨悚然!


编辑

使用 JetBrains DotPeek 查看实现。 FindOne 函数的启动是这样的:

public SearchResult FindOne()
{
  SearchResult searchResult1 = (SearchResult) null;
  SearchResultCollection all = this.FindAll(false);
  ...

我的第一反应是啊!难怪……但后来我注意到了这个争论。 FindAll 有一个接受布尔值的私有版本,这是 FindAll 的开始:

[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public SearchResultCollection FindAll()
{
  return this.FindAll(true);
}

private SearchResultCollection FindAll(bool findMoreThanOne)
{
  ... // other code
  this.SetSearchPreferences(adsSearch, findMoreThanOne);

因此,这提供了更多的见解,但并没有真正解释太多。

c# .net active-directory ldap directorysearcher
3个回答
2
投票

新事物的新答案。您的第一个方法是使用全局目录,所以就像使用

private static DirectoryEntry GetEntry3()
{
    return new DirectoryEntry(@"GC://SERVERNAME/OU=MyUnit,DC=SERVERNAME,DC=local", "USERNAME", "PASSWORD");
}

此外,Microsoft LDAP 库通常有一种方法来告诉它您是否提供了服务器名称,因为如果您不说它是服务器名称,它会进行一些优化,这些优化可能会非常慢。对于 DirectoryEntry,它是 具有最多参数

AuthenticationTypes.ServerBind
的构造函数。


1
投票

循环不是一个好主意。我将分析那个人的代码:

objGroupEntry = sr.GetDirectoryEntry();
dso = new DirectorySearcher(objGroupEntry);

dso.ClientTimeout = TimeSpan.FromSeconds(30);

dso.PropertiesToLoad.Add("physicalDeliveryOfficeName");
dso.PropertiesToLoad.Add("otherFacsimileTelephoneNumber");
dso.PropertiesToLoad.Add("otherTelephone");
dso.PropertiesToLoad.Add("postalCode");
dso.PropertiesToLoad.Add("postOfficeBox");
dso.PropertiesToLoad.Add("streetAddress");
dso.PropertiesToLoad.Add("distinguishedName");

dso.SearchScope = SearchScope.OneLevel;

dso.Filter = "(&(objectClass=top)(objectClass=person)(objectClass=organizationalPerson)(objectClass=user))";
dso.PropertyNamesOnly = false;

SearchResult pResult = dso.FindOne();

if (pResult != null)
{
    offEntry = pResult.GetDirectoryEntry();

    foreach (PropertyValueCollection o in offEntry.Properties)
    {
        this.Controls.Add(new LiteralControl(o.PropertyName + " = " + o.Value.ToString() + "<br/>"));
    }
}

我不知道他为什么要进行两次搜索,但我们假设有一个很好的理由。他应该从

SearchResult
获取这些属性,而不是从
pResult.GetDirectoryEntry
的返回值,因为它是一个全新的对象。

string postalCode = pResult.Properties["postalCode"][0] as string;
List<string> otherTelephones = new List<string>();
foreach(string otherTelephone in pResult.Properties["otherTelephone"])
{
    otherTelephones.Add(otherTelephone);
}

如果您坚持要获得

DirectoryEntry
,请使用
RefreshCache
立即询问所有属性:

offEntry = pResult.GetDirectoryEntry();
offEntry.RefreshCache(propertyNameArray);

如果这些都没有帮助,请查看您的过滤器,看看是否可以使用

BaseLevel
范围。


0
投票

您可以通过使用 FindAll() 并将 DirectorySearch.PageSize 设置为 1 轻松模拟 FindOne(),并且搜索返回的速度与使用 FindOne() 一样快。

此外,您可以使用 DirectoryEntry 构造函数(如上所述)并使用 GC:// 而不是 LDAP:// 来加快速度……不多,但如果您正在加载无数个对象或你陷入了一个紧密的循环中。

此外,如果您使用搜索器,如果您不传入propertiesToLoad 数组,默认情况下它将返回所有属性。但是,如果您有一个复杂的 AD,您可能会获得比您想要的更多的属性,这会减慢网络上的数据传输速度。

最后,关于使用 GC,并非所有属性都会传播到 GC。 IIRC,所有内置属性都可以,但自定义属性肯定不会,除非您明确配置它们这样做。

最后,我偶尔使用的另一个技巧是使用 GC 进行快速搜索,仅检索 GUID,将 GUID 存储在 List 集合中以尽快完成搜索循环,然后返回并使用 LDAP 和 GUID 语法加载对象: “LDAP://”,您也可以使用以下语法从特定 DC 中提取对象:“LDAP://dc-xyz.mycompany.com/”(是的,它也适用于 GC)。

© www.soinside.com 2019 - 2024. All rights reserved.