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

时间:2013-09-03 14:08:47

标签: c# .net active-directory ldap directorysearcher

我有一个从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));
}

结果完全相同,相同的属性以相同的顺序返回。我在another thread找到了一些关于内存泄漏的信息,但他们没有提到性能(我在.Net 3.5上)。


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

我想在一个查询中获取所有属性,但我无法让DirectorySearcher一次性返回所有想要的属性(它省略了在PropertiesToLoad中指定的大约30%的属性(也尝试在构造函数中设置它)这没什么区别),我发现其他人有同样的问题和this is his solution (to loop through them)。当我像这样循环它们时,使用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");
}

现在只需不到一个18秒即可获得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);

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

2 个答案:

答案 0 :(得分:2)

新内容的新答案。您的第一种方法是使用全局编录,因此就像使用

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

此外,Microsoft LDAP库通常可以告诉它您是否提供服务器名称,因为如果您不说它是服务器名称,它会使某些优化变得非常慢。对于DirectoryEntry,它是constructor with the most argumentsAuthenticationTypes.ServerBind

答案 1 :(得分: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范围。