2008-10-10 17 views
11

Próbuję uzyskać wszystkie bezpośrednie raporty użytkownika za pośrednictwem Active Directory, rekursywnie. Tak więc, biorąc pod uwagę użytkownika, otrzymam listę wszystkich użytkowników, którzy mają tę osobę jako menedżera lub mają osobę jako menedżera, który ma osobę jako menedżera ... który ostatecznie ma użytkownika wejściowego jako menedżera.Uzyskiwanie wszystkich bezpośrednich raportów z Active Directory

Moja obecna próba jest raczej powolny:

private static Collection<string> GetDirectReportsInternal(string userDN, out long elapsedTime) 
{ 
    Collection<string> result = new Collection<string>(); 
    Collection<string> reports = new Collection<string>(); 

    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 

    long allSubElapsed = 0; 
    string principalname = string.Empty; 

    using (DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}",userDN))) 
    { 
     using (DirectorySearcher ds = new DirectorySearcher(directoryEntry)) 
     { 
      ds.SearchScope = SearchScope.Subtree; 
      ds.PropertiesToLoad.Clear(); 
      ds.PropertiesToLoad.Add("directReports"); 
      ds.PropertiesToLoad.Add("userPrincipalName"); 
      ds.PageSize = 10; 
      ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2); 
      SearchResult sr = ds.FindOne(); 
      if (sr != null) 
      { 
       principalname = (string)sr.Properties["userPrincipalName"][0]; 
       foreach (string s in sr.Properties["directReports"]) 
       { 
        reports.Add(s); 
       } 
      } 
     } 
    } 

    if (!string.IsNullOrEmpty(principalname)) 
    { 
     result.Add(principalname); 
    } 

    foreach (string s in reports) 
    { 
     long subElapsed = 0; 
     Collection<string> subResult = GetDirectReportsInternal(s, out subElapsed); 
     allSubElapsed += subElapsed; 

     foreach (string s2 in subResult) 
     { 
     result.Add(s2); 
     } 
    } 



    sw.Stop(); 
    elapsedTime = sw.ElapsedMilliseconds + allSubElapsed; 
    return result; 
} 

Zasadniczo funkcja ta przyjmuje nazwę wyróżniającą jako wejście (CN = Michael Stum, OU = test, DC = sub, DC = domena, DC = com) , a wraz z tym wywołanie ds.FindOne() jest powolne.

Stwierdziłem, że wyszukiwanie nazwy użytkownikaPrincipalName jest o wiele szybsze. Mój problem: sr.Properties ["directReports"] jest po prostu listą ciągów znaków, a jest to nazwa wyróżniająca, która wydaje się wolna w wyszukiwaniu.

Zastanawiam się, czy istnieje szybki sposób na konwersję pomiędzy distinguishedName a userPrincipalName? Czy istnieje szybszy sposób wyszukiwania użytkownika, jeśli mam tylko nazwę wyróżniającą do pracy?

Edytuj: Dzięki odpowiedzi! Przeszukanie pola menedżera poprawiło funkcję z 90 sekund do 4 sekund. Oto nowy i ulepszony kod, który jest szybszy i bardziej czytelne (należy pamiętać, że jest najprawdopodobniej błąd w funkcjonalności ElapsedTime, ale rzeczywista rdzeń funkcji działa):

private static Collection<string> GetDirectReportsInternal(string ldapBase, string userDN, out long elapsedTime) 
{ 
    Collection<string> result = new Collection<string>(); 

    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    string principalname = string.Empty; 

    using (DirectoryEntry directoryEntry = new DirectoryEntry(ldapBase)) 
    { 
     using (DirectorySearcher ds = new DirectorySearcher(directoryEntry)) 
     { 
      ds.SearchScope = SearchScope.Subtree; 
      ds.PropertiesToLoad.Clear(); 
      ds.PropertiesToLoad.Add("userPrincipalName"); 
      ds.PropertiesToLoad.Add("distinguishedName"); 
      ds.PageSize = 10; 
      ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2); 
      ds.Filter = string.Format("(&(objectCategory=user)(manager={0}))",userDN); 

      using (SearchResultCollection src = ds.FindAll()) 
      { 
       Collection<string> tmp = null; 
       long subElapsed = 0; 
       foreach (SearchResult sr in src) 
       { 
        result.Add((string)sr.Properties["userPrincipalName"][0]); 
        tmp = GetDirectReportsInternal(ldapBase, (string)sr.Properties["distinguishedName"][0], out subElapsed); 
        foreach (string s in tmp) 
        { 
        result.Add(s); 
        } 
       } 
      } 
      } 
     } 
    sw.Stop(); 
    elapsedTime = sw.ElapsedMilliseconds; 
    return result; 
} 
+0

Możesz uzyskać dodatkową prędkość, wyłączając program DirectoryEntry i DirectorySearcher z rekursji. Nie zmieniają się między nimi, prawda? – Tomalak

+0

Już nie. Czego nie powiedziałem: Używam tego w środowisku SharePoint, w którym wywołanie jest zawijane w wywołaniu SPSecurity.RunWithElevatedPrivileges, co oznacza, że ​​parametry ref nie są możliwe, i nie jestem pewien, czy przekazanie go jako normalnego parametru działa (dziwne Sharepoint Security) –

+0

Myślę, że to powinno działać. Obiekty są zawsze przekazywane jako ref, AFAIK. Zobacz: http://stackoverflow.com/questions/186891/why-use-ref-keyword-when-passing-an-object – Tomalak

Odpowiedz

10

pierwsze, ustawienie Zakres "subtree" jest niepotrzebne, gdy masz już DN, którego szukasz.

Co więcej, można znaleźć wszystkie obiekty, których właściwością "manager" jest osoba, której szukasz, a następnie iterować je. Zasadniczo powinno to być szybsze niż na odwrót.

(&(objectCategory=user)(manager=<user-dn-here>)) 

EDIT: Poniżej jest ważna, ale nie tylko wspomniano w komentarzach do tej odpowiedzi do tej pory:

Kiedy łańcuch filtr jest zbudowany jak wskazano powyżej, istnieje ryzyko zerwania go ze znakami, które są ważne dla nazwy wyróżniającej, ale mają specjalne znaczenie w filtrze. Są must be escaped:

* as \2a 
( as \28 
) as \29 
\ as \5c 
NUL as \00 
/ as \2f 

// Arbitrary binary data can be represented using the same scheme. 

EDIT: Ustawianie SearchRoot do DN obiektu, a SearchScope do Base również to szybki sposób wyciągnąć pojedynczy obiekt z AD.

+0

Dzięki. Zobaczę jak to działa bez Subtree. Twoja druga sugestia brzmi interesująco. Muszę trochę otoczyć mój mózg, ponieważ funkcja wciąż musi być rekurencyjna, ale natychmiast to sprawdzę. –

+1

Gdybym mógł oddać Ci 10 głosów, zrobiłbym to. Wywołanie funkcji wyszukiwania menedżera poprawiło ją z 90 sekund do zaledwie 4 sekund. –

+0

Pamiętaj, że przy takim podejściu musisz zmigrować ryzyko przerwania ciągu filtrów znakami, które są poprawne w DN, ale są zarezerwowane w ciągu filtru. Na czubku mojej głowy przynajmniej "#" trzeba uciec. – Tomalak

Powiązane problemy