C#中缓慢的AD属性检索

时间:2011-02-08 02:28:46

标签: c# vbscript active-directory

大家好我将我的VBScript移植到C#。我遇到了一个问题,即在C#中Active Directory属性检索要慢得多。

这是我不完整的C#代码

foreach(string s in dictLast.Keys)
{
    if(s.Contains("/"))
        str = s.Insert(s.IndexOf('/'), "\\");
    else
        str = s;
    dEntry = new DirectoryEntry("LDAP://" + str);
    strUAC = dEntry.Properties["userAccountControl"].Value.ToString();
    cmd.CommandText = "INSERT INTO [NOW](readTime) VALUES(\"" + test.Elapsed.Milliseconds.ToString() + "\")";
    cmd.ExecuteNonQuery();
    test.Reset();
    test.Start();
}

如果我注释掉这一行。 strUAC = dEntry.Properties [“userAccountControl”]。Value.ToString();

它以11秒的速度运行。但如果我不这样做,它会以2分35秒的速度运行。记录数为3700.平均每条记录的运行时间为50秒。我正在使用秒表课程。

我的VBscript只运行39秒(使用时间差)。每条记录为0或15毫秒。我正在使用Timer()的区别。

这是我的VBscript

strAttributes = "displayName, pwdLastSet, whenCreated, whenChanged, userAccountControl"
For Each strUser In objList.Keys
    prevTime = Timer()
    strFilter = "(sAMAccountName=" & strUser & ")"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
    adoCommand.CommandText = strQuery
    Set adoRecordset = adoCommand.Execute
    On Error Resume Next   
    If (adoRecordset.Fields("displayName") = null) Then
        strCN = "-"
    Else
        strCN   = adoRecordset.Fields("displayName")
    End If
    If (Err.Number <> 0) Then
        MsgBox(strUser)
    End If
    strCr8  = DateAdd("h", 8, adoRecordset.Fields("whenCreated"))
    strUAC  = adoRecordset.Fields("userAccountControl")
    If (strUAC AND ADS_UF_DONT_EXPIRE_PASSWD) Then
        strPW = "Never expires"
    Else
        If (TypeName(adoRecordset.Fields("pwdLastSet").Value) = "Object") Then
            Set objDate = adoRecordset.Fields("pwdLastSet").Value
            dtmPwdLastSet = Integer8Date(objDate, lngBias)
        Else
            dtmPwdLastSet = #1/1/1601#          
        End If
        If (dtmPwdLastSet = #1/1/1601#) Then
            strPW = "Must Change at Next Logon"
        Else
            strPW = DateAdd("d", sngMaxPwdAge, dtmPwdLastSet)
        End If
    End If
    retTime = Timer() - prevTime
    If (objList.Item(strUser) = #1/1/1601#) Then
        Wscript.Echo strCN & ";" & strUser & ";" & strPW & ";" & strCr8 & ";" & ObjChange.Item(strUser) & ";0;" & strUAC & ";" & retTime
    Else
        Wscript.Echo strCN & ";" & strUser & ";" & strPW & ";" & strCr8 & ";" & ObjChange.Item(strUser) & ";" & objList.Item(strUser) & ";" & strUAC & ";" & retTime
    End If
Next

任何想法是什么问题? 如果我没有提供足够的信息,请告诉我。谢谢。

DirectorySearcher方式。 1分8秒。

dEntry = new DirectoryEntry("LDAP://" + strDNSDomain);
string[] strAttr = {"userAccountControl"};
foreach(string s in dictLast.Keys)
{
    if(s.Contains("/"))
        str = s.Insert(s.IndexOf('/'), "\\");
    else
        str = s;
    ds = new DirectorySearcher(de, "(sAMAccountName=" + s + ")", strAttr, SearchScope.Subtree);
    ds.PropertiesToLoad.Add("userAccountControl");
    SearchResult rs = ds.FindOne();
    strUAC = rs.Properties["userAccountControl"][0].ToString();
    cmd.CommandText = "INSERT INTO [NOW](readTime) VALUES(\"" + test.Elapsed.Milliseconds.ToString() + "\")";
    cmd.ExecuteNonQuery();
    test.Reset();
    test.Start();
}

其中strDNSDomain是defaultNamingContext。我尝试过使用域名,但它的运行速度更差,只需3分30秒。

看着别人的代码。如果我们省略域名部分会有区别吗?

using (var LDAPConnection = new DirectoryEntry("LDAP://domain/dc=domain,dc=com", "username", "password"))

只需使用“LDAP:// dc = domain,dc = com”。

解决。而不是绑定每个用户并获取属性。我在第一次搜索lastLogon时存储了所有属性。并使用StreamWriter输出。

4 个答案:

答案 0 :(得分:3)

DirectoryEntryADOConnection都使用ADSI底层证券。不应该有任何性能差异。

性能差异的唯一原因是您正在尝试检索两组不同的数据。

在你的VBScript中,你将“”displayName,pwdLastSet,whenCreated,whenChanged,userAccountControl“设置为strAttributes。ADSI将仅从AD加载这五个属性。

在C#代码中,您没有调用RefreshCache方法来指定要加载的属性。因此,当您访问DirectoryEntry.Properties时,它会自动为您调用RefreshCache(),而不会为您传递任何属性。默认情况下,如果您未指定要加载的属性,ADSI将向您返回所有非构造属性(几乎所有属性)。

另一个问题是,在您的VBscript中,只运行一个LDAP查询,而在C#代码中,您正在运行许多LDAP查询。每个DirectoryEntry.RefreshCache()都将转换为一个LDAP查询。因此,如果您尝试访问1000个对象,则将运行1000个不同的LDAP查询。

使用关系数据库类比,在VBscript中,您正在运行

SELECT * FROM USER_TABLE

在C#代码中,您正在多次运行以下查询

SELECT * FROM USER_TABLE WHERE id = @id

当然,C#代码会更慢。

要在C#代码中执行类似的操作,您应该使用DirectorySearcher代替DirectoryEntry

同样,您需要记住指定DirectorySearcher.PropertiesToLoad以指定从LDAP查询返回的属性。如果您未指定,它将再次返回所有非构造属性。

答案 1 :(得分:1)

以下是您可以做的几件事

  1. 在LDAP服务器中启用审核日志,并查看您的请求是如何进行的。审核日志将显示您的应用程序的每个请求所花费的时间以及打开的连接数等。

  2. 使用可以对LDAP进行异步调用的System.DirectoryServices.Protocols。检查此sample post。使用此名称空间的另一个好处是您可以指定属性。

  3. 正确关闭连接。

答案 2 :(得分:1)

使用DirectorySearcher.PropertiesToLoad仅加载必需的属性而不是所有属性。

我从vbscript中看到的内容更接近了......从某个项目复制,善意测试

DirectoryEntry de = new DirectoryEntry("ldap://domainname");
                    DirectorySearcher deSearch = new DirectorySearcher();
                    deSearch.SearchRoot = de;
                    deSearch.Filter = "(&(ObjectCategory=user)(sAMAccountName="+strUser+"))";
                    deSearch.PropertiesToLoad.Add("displayName");
                    deSearch.PropertiesToLoad.Add("pwdLastSet");
                    deSearch.PropertiesToLoad.Add("whenCreated");
                    deSearch.PropertiesToLoad.Add("whenChanged");
                    deSearch.PropertiesToLoad.Add("userAccountControl);


                    deSearch.SearchScope = SearchScope.Subtree;
                    SearchResult sr = deSearch.FindOne();

答案 3 :(得分:1)

这是阅读该属性的正确方法:

If searchResult.Properties.Contains(PropertyName) Then
        Return searchResult.Properties(PropertyName)(0).ToString()
    Else
        Return String.Empty
End If