我们的 Active Directory 组包含 50 万用户,甚至超过 100 万用户。
我们正在使用
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();
大约需要 50 毫秒。
请注意,我使用的
Invoke("Add", new object[] { userDn })
方法是在这篇 Stackoverflow 文章中推荐的 服务器不愿意处理请求 - Active Directory - 通过 C# 添加用户以避免“服务器不愿意处理请求”异常
所以基本上我的解决方法可以完成这项工作,但不知怎的,我并不是 100% 满意,因为我实际上更喜欢使用
System.DirectoryServices.AccountManagement
命名空间,有什么想法如何避免使用该命名空间的性能问题吗?
我为此问题向 Microsoft 拨打了“咨询电话”,这是他们的答案(德语,下面是英语):
S.DS.AM(System.DirectoryServices.Accountmanagement)是nun nicht der Renner unter den Programmierschnittstellen,Bequemlichkeit ist Trumpf, perf-issues mit großen Gruppen sind 也由设计。温纳奥夫 性能由 S.DS.P 解决 (System.DirectoryServices.Protocols) 或普通 LDAP verwenden。“
有意义的英文翻译是:
对比 API,S.DS.AM (
) 算不上“赛车手”,但舒适度才是王道。较大群体的性能问题是设计使然。当性能很重要时,请使用 S.DS.P (System.DirectoryServices.Accountmanagement
) 或普通 LDAP。System.DirectoryServices.Protocols
我创建了一个控制台应用程序,以便测量在组中添加和删除用户(以毫秒为单位)的差异。
账户管理
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();
}
}
目录服务
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;
// Remove
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 次测试
因此,在我的特定情况下,通过 DirectoryServices / DirectoryEntry 的解决方案是最快的。