【问题标题】:PrincipalSearchResult and System.OutOfMemoryExceptionPrincipalSearchResult 和 System.OutOfMemoryException
【发布时间】:2018-12-05 02:58:51
【问题描述】:

我正在使用 Domain PrincipalContext 来查找用户组。我明白了。但是当我尝试使用组集合时,我得到System.OutOfMemoryException。所有 Principal 对象都是一次性的。我的代码中有 using 部分。我曾尝试使用Dispose() 方法和GC.Collect() 但它没有帮助。

代码如下:

using (var ctx = new PrincipalContext(ContextType.Domain, _domain, _user, _password))
{
    using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx,IdentityType.SamAccountName, sAMAccountName))
    {
        PrincipalSearchResult<Principal> userGroups = user.GetGroups();                    
        using (userGroups)
        {
            foreach (Principal p in userGroups)
            {
                using (p)
                {
                    result.Add(p.Guid == null ? Guid.Empty : (Guid)p.Guid);
                }
            }
        }
    }
}

foreach 循环返回异常。甚至 foreach 都是空循环。

【问题讨论】:

    标签: c# active-directory out-of-memory principalcontext


    【解决方案1】:

    我发现System.DirectoryServices.AccountManagement 命名空间(UserPrincipal 等)确实浪费了大量内存。例如,每次创建 UserPrincipalGroupPrincipal 时,它都会向 AD 询问每个具有值的属性 - 即使您只使用过其中一个。

    如果用户是很多很多组的成员,这可能是原因,尽管我仍然感到惊讶。也许您的计算机没有足够的内存来加载所有内容。

    您可以通过直接使用 System.DirectoryServices 命名空间(无论如何,AccountManagement 命名空间在后台使用的名称)来做同样的事情并使用更少的内存(并且可能更少的时间)。

    这是一个示例,它将查看用户的memberOf 属性以查找组并拉取Guid。这确实有一些限制,我描述了我写的一篇文章:Finding all of a user’s groups(这个例子是从那篇文章中修改的)。但是,在大多数情况下(特别是如果您的环境中只有一个域并且没有受信任的域)会很好。

    public static IEnumerable<Guid> GetUserMemberOf(DirectoryEntry de) {
        var groups = new List<Guid>();
    
        //retrieve only the memberOf attribute from the user
        de.RefreshCache(new[] {"memberOf"});
    
        while (true) {
            var memberOf = de.Properties["memberOf"];
            foreach (string group in memberOf) {
                using (var groupDe = new DirectoryEntry($"LDAP://{group.Replace("/", "\\/")}") {
                    groupDe.RefreshCache(new[] {"objectGUID"});
                    groups.Add(new Guid((byte[]) groupDe.Properties["objectGUID"].Value));
                }
            }
    
            //AD only gives us 1000 or 1500 at a time (depending on the server version)
            //so if we've hit that, go see if there are more
            if (memberOf.Count != 1500 && memberOf.Count != 1000) break;
    
            try {
                de.RefreshCache(new[] {$"memberOf;range={groups.Count}-*"});
            } catch (COMException e) {
                if (e.ErrorCode == unchecked((int) 0x80072020)) break; //no more results
    
                throw;
            }
        }
        return groups;
    }
    

    您需要为其提供用户的DirectoryEntry 对象。如果您事先知道distinguishedName,则可以使用它(例如new DirectoryEntry($"LDAP://{distinguishedName}"))。但如果没有,你可以搜索一下:

    var ds = new DirectorySearcher(
                    new DirectoryEntry($"LDAP://{_domain}"),
                    $"(&(objectClass=user)(sAMAccountName={sAMAccountName}))");
    ds.PropertiesToLoad.Add("distinguishedName"); //add at least one attribute so it doesn't return everything
    
    var result = ds.FindOne();
    var userDe = result.GetDirectoryEntry();
    

    我注意到您还将用户名和密码传递给PrincipalContext。如果此处需要,constructor for DirectoryEntry 确实接受用户名和密码,因此您可以更新此代码以在每次创建新的 DirectoryEntry 时包含该内容。

    【讨论】:

    • 谢谢。 DirectorySearcher 效果更好,有助于修复内存泄漏
    • 太棒了。我很高兴它有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2016-08-31
    • 1970-01-01
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多