PrincipalSearchResult 和 System.OutOfMemoryException
PrincipalSearchResult and System.OutOfMemoryException
我正在使用 Domain PrincipalContext 查找用户组。我明白了。但是当我尝试使用组集合时,我得到 System.OutOfMemoryException
。所有 Principal 对象都是一次性的。我的代码中有使用部分。我曾尝试使用 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 循环 return 异常。甚至 foreach 都是空循环。
我发现 System.DirectoryServices.AccountManagement
命名空间(UserPrincipal
等)确实浪费了大量内存。例如,每次您创建 UserPrincipal
或 GroupPrincipal
时,它都会向 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
.
时包含该代码
我正在使用 Domain PrincipalContext 查找用户组。我明白了。但是当我尝试使用组集合时,我得到 System.OutOfMemoryException
。所有 Principal 对象都是一次性的。我的代码中有使用部分。我曾尝试使用 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 循环 return 异常。甚至 foreach 都是空循环。
我发现 System.DirectoryServices.AccountManagement
命名空间(UserPrincipal
等)确实浪费了大量内存。例如,每次您创建 UserPrincipal
或 GroupPrincipal
时,它都会向 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
.