What would be an effective way to do pagination with Active Directory searches in .NET? There are many ways to search in AD but so far I couldn't find how to do it effectively. I want to be able to indicate Skip
and Take
parameters and be able to retrieve the total number of records matching my search criteria in the result.
在.NET中对Active Directory搜索进行分页的有效方法是什么?在AD中搜索的方法有很多,但到目前为止我找不到如何有效地进行搜索。我希望能够指示Skip和Take参数,并能够在结果中检索与我的搜索条件匹配的记录总数。
I have tried searching with the PrincipalSearcher
class:
我尝试过使用PrincipalSearcher类进行搜索:
using (var ctx = new PrincipalContext(ContextType.Domain, "FABRIKAM", "DC=fabrikam,DC=com"))
using (var criteria = new UserPrincipal(ctx))
{
criteria.SamAccountName = "*foo*";
using (var searcher = new PrincipalSearcher(criteria))
{
((DirectorySearcher)searcher.GetUnderlyingSearcher()).SizeLimit = 3;
var results = searcher.FindAll();
foreach (var found in results)
{
Console.WriteLine(found.Name);
}
}
}
Here I was able to limit the search results to 3 but I wasn't able to get the total number of records corresponding to my search criteria (SamAccountName
contains foo
) neither I was able to indicate to the searcher to skip the first 50 records for example.
在这里,我能够将搜索结果限制为3但是我无法获得与我的搜索条件相对应的记录总数(SamAccountName包含foo)我既没有能够向搜索者指示跳过前50条记录例。
I also tried using the System.DirectoryServices.DirectoryEntry
and System.DirectoryServices.Protocols.SearchRequest
but the only thing I can do is specify the page size.
我也尝试使用System.DirectoryServices.DirectoryEntry和System.DirectoryServices.Protocols.SearchRequest,但我唯一能做的就是指定页面大小。
So is the only way to fetch all the results on the client and do the Skip and Count there? I really hope that there are more effective ways to achieve this directly on the domain controller.
那么获取客户端上的所有结果并在那里进行跳过和计数的唯一方法是什么?我真的希望有更有效的方法直接在域控制器上实现这一点。
4 个解决方案
#1
6
You may try the virtual list view search. The following sort the users by cn, and then get 51 users starting from the 100th one.
您可以尝试虚拟列表视图搜索。下面按cn对用户进行排序,然后从第100个用户开始获取51个用户。
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://domain.com/dc=domain,dc=com", "user", "pwd");
DirectorySearcher searcher = new DirectorySearcher(rootEntry);
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = "(&(objectCategory=person)(objectClass=user))";
searcher.Sort = new SortOption("cn", SortDirection.Ascending);
searcher.VirtualListView = new DirectoryVirtualListView(0, 50, 100);
foreach (SearchResult result in searcher.FindAll())
{
Console.WriteLine(result.Path);
}
For your use case you only need the BeforeCount, AfterCount and the Offset properties of DirectoryVirtualListView (the 3 in DirectoryVirtualListView ctor). The doc for DirectoryVirtualListView is very limited. You may need to do some experiments on how it behave.
对于您的用例,您只需要DirectoryVirtualListView的BeforeCount,AfterCount和Offset属性(DirectoryVirtualListView ctor中的3)。 DirectoryVirtualListView的文档非常有限。您可能需要对其行为进行一些实验。
#2
4
If SizeLimit is set to zero and PageSize is set to 500, the search will return all 12,000 results in pages of 500 items, with the last page containing only 200 items. The paging occurs transparently to the application and the application does not have to perform any special processing other than setting the PageSize property to the proper value.
如果SizeLimit设置为零并且PageSize设置为500,则搜索将返回500个项目的页面中的所有12,000个结果,最后一页仅包含200个项目。分页对应用程序透明地发生,除了将PageSize属性设置为正确的值之外,应用程序不必执行任何特殊处理。
SizeLimit limits the amount of results that you can retrieve at once - so your PageSize needs to be less than or equal to 1000 (Active Directory limits the maximum number of search results to 1000. In this case, setting the SizeLimit property to a value greater than 1000 has no effect.). The paging is done automagically behind the scenes when you call FindAll() etc.
SizeLimit限制您可以一次检索的结果数量 - 因此您的PageSize需要小于或等于1000(Active Directory将最大搜索结果数限制为1000.在这种情况下,将SizeLimit属性设置为更大的值超过1000没有效果。)当您调用FindAll()等时,分页在后台自动完成。
For more details please refer MSDN
有关详细信息,请参阅MSDN
https://msdn.microsoft.com/en-us/library/ms180880.aspx
https://msdn.microsoft.com/en-us/library/ms180880.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.pagesize.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.pagesize.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.sizelimit.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.sizelimit.aspx
#3
0
Waaaay late to the party, but this is what I'm doing:
Waaaay迟到了,但这就是我正在做的事情:
I'm using FindOne()
instead of FindAll()
and member;range=<start>-<end>
on PropertiesToLoad
.
我在PropertiesToLoad上使用FindOne()而不是FindAll()和member; range =
There's a catch on member;range
: when it's the last page, even if you pass member;range=1000-1999
(for instance), it returns member;range=1000-*
, so you have to check for the *
at the end to know if there is more data.
成员有一个问题;范围:当它是最后一页时,即使你传递成员;范围= 1000-1999(例如),它返回成员;范围= 1000- *,所以你必须检查*最后知道是否有更多数据。
public void List<string> PagedSearch()
{
var list = new List<string>();
bool lastPage = false;
int start = 0, end = 0, step = 1000;
var rootEntry = new DirectoryEntry("LDAP://domain.com/dc=domain,dc=com", "user", "pwd");
var filter = "(&(objectCategory=person)(objectClass=user)(samAccountName=*foo*))";
using (var memberSearcher = new DirectorySearcher(rootEntry, filter, null, SearchScope.Base))
{
while (!lastPage)
{
start = end;
end = start + step - 1;
memberSearcher.PropertiesToLoad.Clear();
memberSearcher.PropertiesToLoad.Add(string.Format("member;range={0}-{1}", start, end));
var memberResult = memberSearcher.FindOne();
var membersProperty = memberResult.Properties.PropertyNames.Cast<string>().FirstOrDefault(p => p.StartsWith("member;range="));
if (membersProperty != null)
{
lastPage = membersProperty.EndsWith("-*");
list.AddRange(memberResult.Properties[membersProperty].Cast<string>());
end = list.Count;
}
else
{
lastPage = true;
}
}
}
return list;
}
#4
-1
private static DirectoryEntry forestlocal = new DirectoryEntry(LocalGCUri, LocalGCUsername, LocalGCPassword);
private DirectorySearcher localSearcher = new DirectorySearcher(forestlocal);
public List<string> GetAllUsers()
{
List<string> users = new List<string>();
localSearcher.SizeLimit = 10000;
localSearcher.PageSize = 250;
string localFilter = string.Format(@"(&(objectClass=user)(objectCategory=person)(!(objectClass=contact))(msRTCSIP-PrimaryUserAddress=*))");
localSearcher.Filter = localFilter;
SearchResultCollection localForestResult;
try
{
localForestResult = localSearcher.FindAll();
if (resourceForestResult != null)
{
foreach (SearchResult result in localForestResult)
{
if (result.Properties.Contains("mail"))
users.Add((string)result.Properties["mail"][0]);
}
}
}
catch (Exception ex)
{
}
return users;
}
#1
6
You may try the virtual list view search. The following sort the users by cn, and then get 51 users starting from the 100th one.
您可以尝试虚拟列表视图搜索。下面按cn对用户进行排序,然后从第100个用户开始获取51个用户。
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://domain.com/dc=domain,dc=com", "user", "pwd");
DirectorySearcher searcher = new DirectorySearcher(rootEntry);
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = "(&(objectCategory=person)(objectClass=user))";
searcher.Sort = new SortOption("cn", SortDirection.Ascending);
searcher.VirtualListView = new DirectoryVirtualListView(0, 50, 100);
foreach (SearchResult result in searcher.FindAll())
{
Console.WriteLine(result.Path);
}
For your use case you only need the BeforeCount, AfterCount and the Offset properties of DirectoryVirtualListView (the 3 in DirectoryVirtualListView ctor). The doc for DirectoryVirtualListView is very limited. You may need to do some experiments on how it behave.
对于您的用例,您只需要DirectoryVirtualListView的BeforeCount,AfterCount和Offset属性(DirectoryVirtualListView ctor中的3)。 DirectoryVirtualListView的文档非常有限。您可能需要对其行为进行一些实验。
#2
4
If SizeLimit is set to zero and PageSize is set to 500, the search will return all 12,000 results in pages of 500 items, with the last page containing only 200 items. The paging occurs transparently to the application and the application does not have to perform any special processing other than setting the PageSize property to the proper value.
如果SizeLimit设置为零并且PageSize设置为500,则搜索将返回500个项目的页面中的所有12,000个结果,最后一页仅包含200个项目。分页对应用程序透明地发生,除了将PageSize属性设置为正确的值之外,应用程序不必执行任何特殊处理。
SizeLimit limits the amount of results that you can retrieve at once - so your PageSize needs to be less than or equal to 1000 (Active Directory limits the maximum number of search results to 1000. In this case, setting the SizeLimit property to a value greater than 1000 has no effect.). The paging is done automagically behind the scenes when you call FindAll() etc.
SizeLimit限制您可以一次检索的结果数量 - 因此您的PageSize需要小于或等于1000(Active Directory将最大搜索结果数限制为1000.在这种情况下,将SizeLimit属性设置为更大的值超过1000没有效果。)当您调用FindAll()等时,分页在后台自动完成。
For more details please refer MSDN
有关详细信息,请参阅MSDN
https://msdn.microsoft.com/en-us/library/ms180880.aspx
https://msdn.microsoft.com/en-us/library/ms180880.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.pagesize.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.pagesize.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.sizelimit.aspx
https://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.sizelimit.aspx
#3
0
Waaaay late to the party, but this is what I'm doing:
Waaaay迟到了,但这就是我正在做的事情:
I'm using FindOne()
instead of FindAll()
and member;range=<start>-<end>
on PropertiesToLoad
.
我在PropertiesToLoad上使用FindOne()而不是FindAll()和member; range =
There's a catch on member;range
: when it's the last page, even if you pass member;range=1000-1999
(for instance), it returns member;range=1000-*
, so you have to check for the *
at the end to know if there is more data.
成员有一个问题;范围:当它是最后一页时,即使你传递成员;范围= 1000-1999(例如),它返回成员;范围= 1000- *,所以你必须检查*最后知道是否有更多数据。
public void List<string> PagedSearch()
{
var list = new List<string>();
bool lastPage = false;
int start = 0, end = 0, step = 1000;
var rootEntry = new DirectoryEntry("LDAP://domain.com/dc=domain,dc=com", "user", "pwd");
var filter = "(&(objectCategory=person)(objectClass=user)(samAccountName=*foo*))";
using (var memberSearcher = new DirectorySearcher(rootEntry, filter, null, SearchScope.Base))
{
while (!lastPage)
{
start = end;
end = start + step - 1;
memberSearcher.PropertiesToLoad.Clear();
memberSearcher.PropertiesToLoad.Add(string.Format("member;range={0}-{1}", start, end));
var memberResult = memberSearcher.FindOne();
var membersProperty = memberResult.Properties.PropertyNames.Cast<string>().FirstOrDefault(p => p.StartsWith("member;range="));
if (membersProperty != null)
{
lastPage = membersProperty.EndsWith("-*");
list.AddRange(memberResult.Properties[membersProperty].Cast<string>());
end = list.Count;
}
else
{
lastPage = true;
}
}
}
return list;
}
#4
-1
private static DirectoryEntry forestlocal = new DirectoryEntry(LocalGCUri, LocalGCUsername, LocalGCPassword);
private DirectorySearcher localSearcher = new DirectorySearcher(forestlocal);
public List<string> GetAllUsers()
{
List<string> users = new List<string>();
localSearcher.SizeLimit = 10000;
localSearcher.PageSize = 250;
string localFilter = string.Format(@"(&(objectClass=user)(objectCategory=person)(!(objectClass=contact))(msRTCSIP-PrimaryUserAddress=*))");
localSearcher.Filter = localFilter;
SearchResultCollection localForestResult;
try
{
localForestResult = localSearcher.FindAll();
if (resourceForestResult != null)
{
foreach (SearchResult result in localForestResult)
{
if (result.Properties.Contains("mail"))
users.Add((string)result.Properties["mail"][0]);
}
}
}
catch (Exception ex)
{
}
return users;
}