Retrieve AD Custom Attribute in One Batch

2019-07-20 08:45发布

问题:

Is it possible to use the System.DirectoryServices.AccountManagement library and the PrincipalSearcher class to retrieve a custom attribute for all Principals returned from the call to FindAll()?

I'm currently using this example: http://msdn.microsoft.com/en-us/library/bb552835%28v=vs.90%29.aspx.

However, when accessing my custom property/attribute, it seems to be making an additional trip to the AD store. I would like it to eagerly load this property at the initial call to FindAll().

回答1:

Yes, it is possible to eagerly load the custom attribute for all principals at the initial call to FindAll(). You only have to specify your custom attributes as described in the Microsoft sample using [DirectoryProperty("YOUR_PROP_NAME")] attribute.

By accessing the underlying DirectorySearcher's property PropertiesToLoad using the GetUnderlyingSearcher() method of the UserPrincipal class you can see that your custom attribute is included in the collection of properties to load. You can examine the PropertiesToLoad collection in the debugger.

On my machine the collection contains a total of 68 properties.

And here begins the problem and the performance penalty. The more properties are included in this collection the more round trips to Active Directory are necessary to retrieve them.

I did some performance tests:

Using the Microsoft sample to retrieve 200 InetOrgPerson objects took about 500ms.

Using the DirectorySearcher class directly and only requesting the properties of interest took only 70ms (see sample below).

using (DirectoryEntry e = new DirectoryEntry("LDAP://server10/CN=users,DC=treyresearch,DC=net", 
                                         "treyresearch\\administrator", "P@$$W0rd", AuthenticationTypes.FastBind | AuthenticationTypes.Secure))
  {
    using (DirectorySearcher ds = new DirectorySearcher(e, "(&(objectCategory=inetorgperson)(logonCount=0))"))
    {
      ds.SearchScope = SearchScope.OneLevel;
      ds.PropertiesToLoad.Clear();
      ds.PropertiesToLoad.Add("logonCount");
      ds.PropertiesToLoad.Add("sAMAccountName");


      Stopwatch sw = new Stopwatch();
      sw.Start();
      int countPerson = 0;
      using (SearchResultCollection searchResultCol = ds.FindAll())
      {
        foreach (SearchResult sr in searchResultCol)
        {
          ResultPropertyValueCollection propCol = sr.Properties["logonCount"];

          if (propCol.Count > 0)
          {
            countPerson++;
            object cou = propCol[0];
          }
        }
        sw.Stop();
        Console.Out.WriteLine(sw.ElapsedMilliseconds);
        Console.Out.WriteLine(countPerson);
      }
    }
  }

By the same token I've used objectCategory in the search filter instead of objectClass because objectCategory is a so called indexed property. Accessing indexed properties is faster than accessing non indexed properties.

Furthermore I've specified AuthenticationTypes.FastBind to improve performance.

To further improve performance see this article on MSDN describing how to create efficient search queries.

To summarize, using DirectorySearcher class and specifing only the properties you are interested in creatly improves the performance of your search (reducing round trips to Active Directory).

Hope, this helps.



回答2:

Yes, you can; Cast GetUnderlyingObject() for each Principal object to DirectoryEntry, then you may access the custom attribute like oDE.Properties[YOUR_CUSTOM_ATTRIBUTE].Value.