web-dev-qa-db-fra.com

Différence entre PrincipalSearcher et DirectorySearcher

Je vois des exemples Active Directory qui utilisent PrincipalSearcher et d'autres exemples qui font la même chose mais utilisent DirectorySearcher. Quelle est la différence entre ces deux exemples?

Exemple utilisant 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);
}

Exemple utilisant 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());
}
35
Drew Chapin

J'ai passé beaucoup de temps à analyser les différences entre ces deux. Voici ce que j'ai appris.

  • DirectorySearcher provient du System.DirectoryServices espace de noms.

  • PrincipalSearcher provient du System.DirectoryServices.AccountManagement espace de noms, qui est construit au-dessus de System.DirectoryServices. PrincipalSearcher utilise en interne DirectorySearcher.

  • L'espace de noms AccountManagement (c'est-à-dire PrincipalSearcher) a été conçu pour simplifier la gestion des objets Utilisateur, Groupe et Ordinateur (c'est-à-dire les principaux). En théorie, son utilisation devrait être plus facile à comprendre et produire moins de lignes de code. Bien que dans ma pratique jusqu'à présent, cela semble dépendre fortement de ce que vous faites.

  • DirectorySearcher est plus bas niveau et peut traiter plus que des objets Utilisateur, Groupe et Ordinateur.

  • Pour une utilisation générale, lorsque vous travaillez avec des attributs de base et seulement quelques objets, PrincipalSearcher entraînera moins de lignes de code et un temps d'exécution plus rapide.

  • L'avantage semble disparaître à mesure que les tâches que vous effectuez deviennent plus avancées. Par exemple, si vous attendez plus de quelques centaines de résultats, vous devrez obtenir le DirectorySearcher sous-jacent et définir le PageSize

    DirectorySearcher ds = search.GetUnderlyingSearcher() as DirectorySearcher;
    if( ds != null )
        ds.PageSize = 1000;
    
  • DirectorySearcher peut être beaucoup plus rapide que PrincipalSearcher si vous utilisez PropertiesToLoad.

  • DirectorySearcher et les classes similaires peuvent fonctionner avec tous les objets dans AD, tandis que PrincipalSearcher est beaucoup plus limité. Par exemple, vous ne pouvez pas modifier une unité d'organisation à l'aide de PrincipalSearcher et de classes similaires.

Voici un graphique que j'ai fait pour analyser en utilisant PrincipalSearcher, DirectorySearcher sans utiliser PropertiesToLoad, et DirectorySearcher avec en utilisant PropertiesToLoad. Tous les tests ...

  • Utilisez un PageSize de 1000
  • Interrogez un total de 4 278 objets utilisateur
  • Précisez les critères suivants
    • objectClass=user
    • objectCategory=person
    • Pas une ressource de planification (c'est-à-dire !msExchResourceMetaData=ResourceType:Room)
    • Activé (c'est-à-dire !userAccountControl:1.2.840.113556.1.4.803:=2)

DirectorySearcher vs. PrincipalSearcher Performance Chart


Code pour chaque test


en utilisant 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);
}


en utilisant 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);
}


en utilisant DirectorySearcher avec PropertiesToLoad

Identique à "Utilisation de DirectorySearcher mais ajoutez cette ligne

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

Après

search.PageSize = 1000;
87
Drew Chapin

PrincipalSearcher est utilisé pour interroger le répertoire des groupes ou des utilisateurs. DirectorySearcher est utilisé pour interroger toutes sortes d'objets.

J'ai utilisé DirectorySearcher pour obtenir des groupes avant de découvrir PrincipalSearcher donc quand j'ai remplacé le premier par le second, la vitesse de mon programme s'est améliorée (peut-être que c'était juste PrincipalSearcher qui a créé une meilleure requête pour moi. Pour ce qui m'importe, PrincipalSearcher était juste plus facile à utiliser et plus adapté à la tâche d'obtenir des pricipaux.

DirectorySearcher d'autre part est plus général car il peut obtenir d'autres types d'objets. C'est pourquoi il ne peut pas être fortement tapé comme mentionné dans les commentaires. PrincipalSearcher concerne les principaux donc il aura des objets fortement typés qui se rapportent aux principaux, et c'est pourquoi vous n'avez pas besoin de le dire pour vous obtenir un objet de type utilisateur ou groupe, cela sera implicite par les classes principales que vous utilisez.

3
Mzn

DirectorySearcher est de loin plus rapide. L'exemple de @DrewChapin peut être pris encore plus loin. D'après mes tests, environ 10 fois plus loin/plus vite. J'ai pu extraire 721 ordinateurs CN en 3,8 secondes avec son code. Assez vite. Avec mes changements, je l'ai fait en 0,38 seconde. Selon ce que vous faites, cela pourrait être énorme. Je l'ai utilisé dans une recherche de compte prédictive (commencez à taper le nom et une zone de liste déroulante se remplit. Un système très court. )

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++;
    }
}
0
awsnap