我有一个循环从 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);
因此,这提供了更多的见解,但并没有真正解释太多。
新事物的新答案。您的第一个方法是使用全局目录,所以就像使用
private static DirectoryEntry GetEntry3()
{
return new DirectoryEntry(@"GC://SERVERNAME/OU=MyUnit,DC=SERVERNAME,DC=local", "USERNAME", "PASSWORD");
}
此外,Microsoft LDAP 库通常有一种方法来告诉它您是否提供了服务器名称,因为如果您不说它是服务器名称,它会进行一些优化,这些优化可能会非常慢。对于 DirectoryEntry,它是 具有最多参数 和
AuthenticationTypes.ServerBind
的构造函数。
循环不是一个好主意。我将分析那个人的代码:
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
范围。
您可以通过使用 FindAll() 并将 DirectorySearch.PageSize 设置为 1 轻松模拟 FindOne(),并且搜索返回的速度与使用 FindOne() 一样快。
此外,您可以使用 DirectoryEntry 构造函数(如上所述)并使用 GC:// 而不是 LDAP:// 来加快速度……不多,但如果您正在加载无数个对象或你陷入了一个紧密的循环中。
此外,如果您使用搜索器,如果您不传入propertiesToLoad 数组,默认情况下它将返回所有属性。但是,如果您有一个复杂的 AD,您可能会获得比您想要的更多的属性,这会减慢网络上的数据传输速度。
最后,关于使用 GC,并非所有属性都会传播到 GC。 IIRC,所有内置属性都可以,但自定义属性肯定不会,除非您明确配置它们这样做。
最后,我偶尔使用的另一个技巧是使用 GC 进行快速搜索,仅检索 GUID,将 GUID 存储在 List 集合中以尽快完成搜索循环,然后返回并使用 LDAP 和 GUID 语法加载对象:
“LDAP://