Эффективная разбивка на страницы с помощью поиска в Active Directory
Что было бы эффективным способом разбиения на страницы с помощью поиска в Active Directory в.NET? Есть много способов поиска в AD, но до сих пор я не мог найти, как это сделать эффективно. Я хочу иметь возможность указывать параметры Skip
и Take
и иметь возможность получить общее количество записей, соответствующих моим критериям поиска в результате.
Я пробовал поиск в классе 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);
}
}
}
Здесь мне удалось ограничить результаты поиска до 3, но мне не удалось получить общее количество записей, соответствующих моим критериям поиска (SamAccountName
содержит foo
), ни я не смог указать поисковому пользователю пропустить первые 50 записей для пример.
Я также попытался использовать System.DirectoryServices.DirectoryEntry
и System.DirectoryServices.Protocols.SearchRequest
но единственное, что я могу сделать, это указать размер страницы.
Итак, единственный способ получить все результаты на клиенте и сделать там Пропустить и подсчитать? Я действительно надеюсь, что есть более эффективные способы достижения этого непосредственно на контроллере домена.
Ответы
Ответ 1
Вы можете попробовать виртуальный просмотр списка. Следующие пользователи сортируются cn, а затем получают 51 пользователя, начиная с 100-го.
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);
}
Для вашего случая использования вам нужны только свойства BeforeCount, AfterCount и Offset для DirectoryVirtualListView (3 в DirectoryVirtualListView ctor). Документ для DirectoryVirtualListView очень ограничен. Возможно, вам понадобятся некоторые эксперименты в отношении того, как они себя ведут.
Ответ 2
Если для параметра "РазмерLimit" установлено значение "0", а "PageSize" установлено значение "500", поиск возвращает все 12 000 результатов на страницах с 500 элементами, причем последняя страница содержит только 200 элементов. Пейджинг выполняется прозрачно для приложения, и приложение не должно выполнять какую-либо специальную обработку, отличную от установки свойства PageSize для правильного значения.
SizeLimit ограничивает количество результатов, которые вы можете получить сразу - поэтому ваш размер страницы должен быть меньше или равен 1000 (Active Directory ограничивает максимальное количество результатов поиска до 1000. В этом случае для установки свойства SizeLimit значение больше чем 1000 не имеет эффекта.). Пейджинг выполняется автоматически за кулисами при вызове FindAll() и т.д.
Для получения дополнительной информации см. MSDN
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.sizelimit.aspx
Ответ 3
Waaaay поздно на вечеринку, но это то, что я делаю:
Я использую FindOne()
вместо FindAll()
и member;range=<start>-<end>
в PropertiesToLoad
.
Там улов на member;range
: когда это последняя страницу, даже если вы передаете member;range=1000-1999
(например), он возвращает member;range=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
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;
}