Медленный поиск свойств AD в C # - PullRequest
3 голосов
/ 08 февраля 2011

Привет, все, что я портирую через мой VBScript на C #. И я столкнулся с проблемой, что поиск свойств Active Directory намного медленнее в C #.

Это мой неполный код 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

Есть идеи, в чем проблема? Пожалуйста, скажите мне, если я не даю достаточно информации. Спасибо.

Директория поиска. 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 ]

3 голосов
/ 08 февраля 2011

Оба DirectoryEntry и ADOConnection используют базовые 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 голос
/ 20 октября 2015

Это правильный способ прочитать свойство:

If searchResult.Properties.Contains(PropertyName) Then
        Return searchResult.Properties(PropertyName)(0).ToString()
    Else
        Return String.Empty
End If
1 голос
/ 08 февраля 2011

используйте 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();
1 голос
/ 08 февраля 2011

Вот несколько вещей, которые вы можете сделать

  1. Включить журнал аудита на сервере LDAP и посмотреть, как проходят ваши запросы.Журналы аудита покажут, сколько времени уходит на каждый запрос вашего приложения и сколько соединений открыто и т. Д.

  2. Используйте System.DirectoryServices.Protocols, который может выполнять асинхронные вызовы LDAP,Проверьте это образец сообщения .Другое преимущество использования этого пространства имен состоит в том, что вы можете указывать атрибуты.

  3. Правильно закрыть соединение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...