性能问题 - 在.net中添加/删除大型Active Directory组中的用户

时间:2017-04-24 09:56:33

标签: c# .net active-directory ldap active-directory-group

我们的Active Directory组包含500,000个用户,其中一个用户甚至超过一百万。

我们正在使用System.DirectoryServices.AccountManagement命名空间从群组中添加和删除用户,如下所述:https://stackoverflow.com/a/2143742/1099519

代码本身工作得很好,除了超级慢的事实,添加用户需要一分钟,有时甚至更多!

我可以找出以下代码行,似乎在.net中触发了一个延迟加载机制:

adGroupPrincipal.Members.Add(userPrincipal);

我使用Wireshark查看发生了什么,在致电GroupPrincipal.Members.Add(UserPrincipal)时我看到很多的网络流量。我的假设:访问Members属性会触发一个延迟加载方法来获取组的所有成员。

Members的官方文档中 - 属性(https://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement.groupprincipal.members(v=vs.110).aspx)没有关于其行为的信息。

比较添加用户"旧学校"使用DirectoryEntry命名空间System.DirectoryServices的方式:

DirectoryEntry groupEntry = new DirectoryEntry("LDAP://server/CN=GROUPNAME,OU=Groups,OU=_CUSTOMERS,DC=srv,DC=tld", "USERNAME", "PASSWORD");
string userDn = String.Concat("LDAP://server/CN=", samAccountName, ",OU=Groups,OU=_CUSTOMERS,DC=srv,DC=tld"));    
groupEntry.Invoke("Add", new object[] { userDn });
groupEntry.CommitChanges();

大约需要50ms。

请注意,我在此Stackoverflow文章Server is unwilling to process the request - Active Directory - Add User via C#中建议使用Invoke("Add", new object[] { userDn })方法,以避免"服务器不愿意处理请求"例外

基本上我的解决方法完成了这项工作,但不知何故我不是百分之百满意,因为我实际上更喜欢使用System.DirectoryServices.AccountManagement命名空间,任何想法如何使用该命名空间来避免性能问题?

1 个答案:

答案 0 :(得分:2)

我在这个问题上为微软打开了一个“咨询电话”,这是他们的答案(德语,英语如下):

  

S.DS.AM(System.DirectoryServices.Accountmanagement)ist nun nicht der   Renner unter den Programmierschnittstellen,Bequemlichkeit ist Trumpf,   perf-issuesmitgroßenGruppensind也是由Design设计的。 Wenn er auf   性能优势,解决方案S.DS.P   (System.DirectoryServices.Protocols)或纯LDAP verwenden。“

英语中有意义的翻译将是:

  

比较API,S.DS.AM(System.DirectoryServices.Accountmanagement)不是“赛车手”,但舒适性是特朗普。大型组的性能问题是设计问题。当性能很重要时,请使用S.DS.P(System.DirectoryServices.Protocols)或普通LDAP。

我创建了一个控制台应用程序,以便以毫秒为单位衡量在组中添加和删除用户的差异。

<强> AccountManagement

public static void InsertGroupAccountManagement(UserPrincipal userPrincipal)
{
    using (GroupPrincipal adGroup = GroupPrincipal.FindByIdentity(_principalGroupContext, IdentityType.Guid, PRODUCT_USER_GROUP_ID))
    {
        adGroup.Members.Add(userPrincipal);
        adGroup.Save();
        adGroup.Members.Remove(userPrincipal);
        adGroup.Save();
    }
}

<强>的DirectoryServices

public static void InsertGroupDirectoryServices(string samAccountName)
{
    DirectoryEntry groupEntry = new DirectoryEntry("LDAP://server.address/CN=PSO_PRODUCT_USER,OU=PSO_,OU=Groups,OU=_PRODUCT,DC=address,DC=server", "USERNAME", "PASSWORD");
    string userDn = String.Concat("LDAP://server.address/CN=", samAccountName, ",OU=Users,OU=_PRODUCT,DC=address,DC=server");
    DirectoryEntry userEntry = new DirectoryEntry(userDn, "USERNAME", "PASSWORD");
    groupEntry.Invoke("Add", new object[] { userDn });
    groupEntry.CommitChanges();            
    groupEntry.Invoke("Remove", new object[] { userDn });
    groupEntry.CommitChanges();            
    groupEntry.Close();
}

<强>协议

public static void InsertGroupProtocols(string samAccountName)
{
    LdapDirectoryIdentifier ldapDirectoryIdentifier = new LdapDirectoryIdentifier("server.address");
    NetworkCredential credentials = new NetworkCredential("USERNAME", "PASSWORD");
    LdapConnection ldapConnection = new LdapConnection(ldapDirectoryIdentifier, credentials);
    ldapConnection.SessionOptions.ProtocolVersion = 3;
    ldapConnection.SessionOptions.Signing = true;
    ldapConnection.SessionOptions.Sealing = true;
    ldapConnection.AuthType = AuthType.Negotiate;
    ldapConnection.Bind();

    // Add
    DirectoryAttributeModification addDirectoryModification = new DirectoryAttributeModification();
    addDirectoryModification.Name = "member";
    addDirectoryModification.Add(String.Concat("CN=", samAccountName, ",OU=Users,OU=_PRODUCT,DC=address,DC=server"));
    addDirectoryModification.Operation = DirectoryAttributeOperation.Add;

    ModifyRequest addRequest = new ModifyRequest("CN=PSO_PRODUCT_USER,OU=PSO_,OU=Groups,OU=_PRODUCT,DC=address,DC=server", addDirectoryModification);
    ModifyResponse addResponse = ldapConnection.SendRequest(addRequest) as ModifyResponse;

    // Remoove
    DirectoryAttributeModification deleteDirectoryModification = new DirectoryAttributeModification();
    deleteDirectoryModification.Name = "member";
    deleteDirectoryModification.Add(String.Concat("CN=", samAccountName, ",OU=Users,OU=_PRODUCT,DC=address,DC=server"));
    deleteDirectoryModification.Operation = DirectoryAttributeOperation.Delete;

    ModifyRequest deleteRequest = new ModifyRequest("CN=PSO_PRODUCT_USER,OU=PSO_,OU=Groups,OU=_PRODUCT,DC=address,DC=server", deleteDirectoryModification);
    ModifyResponse deleteResponse = ldapConnection.SendRequest(deleteRequest) as ModifyResponse;
}

结果表(以毫秒为单位)

连续运行10次测试

Result table of time taken

因此,在我的特定情况下,通过DirectoryServices / DirectoryEntry的解决方案是最快的。