Разница между PrincipalSearcher и DirectorySearcher

Я вижу примеры Active Directory, которые используют PrincipalSearcher и другие примеры, которые делают то же самое, но используют DirectorySearcher. В чем разница между этими двумя примерами?

Пример использования PrincipalSearcher

PrincipalContext context = new PrincipalContext(ContextType.Domain);
PrincipalSearcher search = new PrincipalSearcher(new UserPrincipal(context));
foreach( UserPrincipal user in search.FindAll() )
{
    if( null != user )
        Console.WriteLine(user.DistinguishedName);
}

Пример использования DirectorySearcher

DirectorySearcher search = new DirectorySearcher("(&(objectClass=user)(objectCategory=person))");
search.PageSize = 1000;
foreach( SearchResult result in search.FindAll() )
{
    DirectoryEntry user = result.GetDirectoryEntry();
    if( null != user )
        Console.WriteLine(user.Properties["distinguishedName"].Value.ToString());
}

Ответ 1

Я потратил много времени на анализ различий между этими двумя. Вот что я узнал.

  • DirectorySearcher происходит из System.DirectoryServices имен System.DirectoryServices.

  • PrincipalSearcher происходит из System.DirectoryServices.AccountManagement имен System.DirectoryServices.AccountManagement, которое построено поверх System.DirectoryServices. PrincipalSearcher внутренне использует DirectorySearcher.

  • Пространство имен AccountManagement (то есть PrincipalSearcher) было разработано для упрощения управления объектами User, Group и Computer (т.е. Principals). Теоретически, его использование должно быть проще для понимания и создавать меньше строк кода. Хотя в моей практике до сих пор она сильно зависит от того, что вы делаете.

  • DirectorySearcher более низкоуровневый и может иметь дело не только с объектами User, Group и Computer.

  • Для общего использования, когда вы работаете с базовыми атрибутами и несколькими объектами, PrincipalSearcher приведет к сокращению числа строк кода и ускорению работы.

  • Преимущество, похоже, исчезает из-за более сложных задач, которые вы делаете. Например, если вы ожидаете более нескольких сотен результатов, вам придется получить базовый DirectorySearcher и установить параметр PageSize

    DirectorySearcher ds = search.GetUnderlyingSearcher() as DirectorySearcher;
    if( ds != null )
        ds.PageSize = 1000;
    
  • DirectorySearcher может быть значительно быстрее, чем PrincipalSearcher если вы используете PropertiesToLoad.

  • DirectorySearcher и подобные классы могут работать со всеми объектами в AD, тогда как PrincipalSearcher гораздо более ограничен. Например, вы не можете изменить организационную единицу, используя PrincipalSearcher и подобные классы.

Вот диаграмма, которую я сделал для анализа с использованием PrincipalSearcher, DirectorySearcher без использования PropertiesToLoad и DirectorySearcher с использованием PropertiesToLoad. Все тесты...

  • Использовать PageSize 1000
  • Запросите в общей сложности 4 278 пользовательских объектов
  • Укажите следующие критерии
    • objectClass=user
    • objectCategory=person
    • Не ресурс планирования (т.е. !msExchResourceMetaData=ResourceType:Room)
    • Включено (т.е. !userAccountControl:1.2.840.113556.1.4.803:=2)

DirectorySearcher vs. PrincipalSearcher Performance Chart


Код для каждого теста


Использование PrincipalSearcher

[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEx: UserPrincipal
{

    private AdvancedFiltersEx _advancedFilters;

    public UserPrincipalEx( PrincipalContext context ): base(context)
    {
        this.ExtensionSet("objectCategory","User");
    }

    public new AdvancedFiltersEx AdvancedSearchFilter
    {
        get {
            if( null == _advancedFilters )
                _advancedFilters = new AdvancedFiltersEx(this);
                return _advancedFilters;
        }
    }

}

public class AdvancedFiltersEx: AdvancedFilters 
{

    public AdvancedFiltersEx( Principal principal ): 
        base(principal) { }

    public void Person()
    {
        this.AdvancedFilterSet("objectCategory", "person", typeof(string), MatchType.Equals);
        this.AdvancedFilterSet("msExchResourceMetaData", "ResourceType:Room", typeof(string), MatchType.NotEquals);
    }
}

//...

for( int i = 0; i < 10; i++ )
{
    uint count = 0;
    Stopwatch timer = Stopwatch.StartNew();
    PrincipalContext context = new PrincipalContext(ContextType.Domain);
    UserPrincipalEx filter = new UserPrincipalEx(context);
    filter.Enabled = true;
    filter.AdvancedSearchFilter.Person();
    PrincipalSearcher search = new PrincipalSearcher(filter);
    DirectorySearcher ds = search.GetUnderlyingSearcher() as DirectorySearcher;
    if( ds != null )
        ds.PageSize = 1000;
    foreach( UserPrincipalEx result in search.FindAll() )
    {
        string canonicalName = result.CanonicalName;
        count++;
    }

    timer.Stop();
    Console.WriteLine("{0}, {1} ms", count, timer.ElapsedMilliseconds);
}


Использование DirectorySearcher

for( int i = 0; i < 10; i++ )
{
    uint count = 0;
    string queryString = "(&(objectClass=user)(objectCategory=person)(!msExchResourceMetaData=ResourceType:Room)(!userAccountControl:1.2.840.113556.1.4.803:=2))";

    Stopwatch timer = Stopwatch.StartNew();

    DirectoryEntry entry = new DirectoryEntry();
    DirectorySearcher search = new DirectorySearcher(entry,queryString);
    search.PageSize = 1000;
    foreach( SearchResult result in search.FindAll() )
    {
        DirectoryEntry user = result.GetDirectoryEntry();
        if( user != null )
        {
            user.RefreshCache(new string[]{"canonicalName"});
            string canonicalName = user.Properties["canonicalName"].Value.ToString();
            count++;
        }
    }
    timer.Stop();
    Console.WriteLine("{0}, {1} ms", count, timer.ElapsedMilliseconds);
}


Использование DirectorySearcher с PropertiesToLoad

То же, что и "Использование DirectorySearcher но добавьте эту строку

search.PropertiesToLoad.AddRange(new string[] { "canonicalName" });

После

search.PageSize = 1000;

Ответ 2

PrincipalSearcher используется для запроса Каталога для групп или пользователей. DirectorySearcher используется для запроса всех видов объектов.

Я использовал DirectorySearcher для получения групп до того, как я обнаружил PrincipalSearcher поэтому, когда я заменил первое на последнее, скорость моей программы улучшилась (возможно, это был просто PrincipalSearcher который создал лучший запрос для меня. Что мне очень важно, PrincipalSearcher был просто проще для использования и более подходящего для выполнения задач.

DirectorySearcher с другой стороны, более общий, так как он может получать другие виды объектов. Вот почему он не может быть строго типизирован, как указано в комментариях. PrincipalSearcher - это все о принципах, поэтому он будет иметь строго типизированные объекты, относящиеся к принципалам, и именно поэтому вам не нужно указывать ему, чтобы вы стали объектом добрых пользователей или групп, это будет подразумеваться основными классами, которые вы использовать.

Ответ 3

DirectorySearcher намного быстрее. Пример из @DrewChapin можно взять еще дальше. По моим тестам примерно в 10 раз дальше/быстрее. Мне удалось вытащить 721 компьютер cn за 3,8 секунды с его кодом. Достаточно быстро. С моими изменениями я сделал это за 0,38 секунды. В зависимости от того, что вы делаете, это может быть огромным. Я использовал это в интеллектуальном поиске по счету (Начните вводить имя и заполняется поле со списком. Очень короткий System.Timer запускается после каждого нажатия клавиши и отменяется нажатием клавиши. Если таймер истекает, список обновляется. Эффективность для этого огромна. )

DirectoryEntry entry = new DirectoryEntry();
DirectorySearcher search = new DirectorySearcher(entry,queryString);
search.PageSize = 1000;
// *** Added following line
search.PropertiesToLoad.AddRange(new string[] { "canonicalName" });
foreach( SearchResult result in search.FindAll() )
{
    //DirectoryEntry user = result.GetDirectoryEntry();
    // *** Work directly with result instead of user
    if( result != null )
    {
        //user.RefreshCache(new string[]{"canonicalName"});
        // *** Following line modified
        string canonicalName = result.Properties["canonicalName"][0].ToString();
        count++;
    }
}