Эффективная разбивка на страницы с помощью поиска в 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;
    }