2017-02-05 3 views
3

Я пишу метод на C#, который должен запрашивать Active Directory и находить всех пользователей и группы с отображаемым именем формата {displayName} (поиск подстановочных знаков с использованием как стартового, так и конечного шаблона), этот метод будет использоваться для поля автозаполнения.Активный запрос каталога с подстановочными знаками имеет низкую производительность

Проблема в производительности метода, который я написал, очень низок, попытка запроса AD занимает от 30 секунд до полной минуты в зависимости от строки запроса.

AD моей организации очень большой, но если это займет много времени, поле автозаполнения будет бессмысленным.

Вот код, я использую прямо сейчас:

// Intialize the results list. 
result.queryResult = new List<Classses.ADSearchObject>(); 

// Set up domain context. 
PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain, Constants.adQueryUser, Constants.adQueryPassword); 

// Set up a directory searcher. 
DirectorySearcher dSearcher = new DirectorySearcher(); 
// Define a SearchCollection to store the results. 
SearchResultsCollection searchCol; 
// Define returned result paging for performance. 
dSearcher.PageSize = 1000; 
// Define the properties to retrieve 
dSearcher.PropertiesToLoad.Add("sAMAccountName"); 
dSearcher.PropertiesToLoad.Add("displayName"); 
// Define the filter for users. 
dSearcher.Filter = $"(|(&(displayName = {result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))"; 

// Search based in filter and save the results. 
searchCol = dSearcher.FindAll(); 

// Add the results to the returned object 
foreach (SearchResult searchResult in searchCol) 
{ 
    DirectoryEntry de = searchResult.GetDirectoryEntry(); 
    // Code to get data from the results... 
} 

// Define the filter for groups. 
dSearcher.Filter = $"(|(&(displayName={result.querystring}*)(objectCategory=person))(&(displayName=*{result.querystring})(objectCategory=person)))"; 

// Search based in filter and save the results. 
searchCol = dSearcher.FindAll(); 

// Add the results to the returned object 
foreach (SearchResult searchResult in searchCol) 
{ 
    DirectoryEntry de = searchResult.GetDirectoryEntry(); 
    // Code to get data from the results... 
} 

В настоящее время поиск разделяется для пользователей и групп, чтобы сделать его легко отличить их, но если это повышает производительность существенно я унифицировать их к один поиск.

Edit: Как предложила Rene пользователя, я использовал Stopwatch, чтобы проверить время, необходимое для FindAll и я также проверил, как долго мои foreach петель взять.

Я узнал, что вызовы FindAll занимают около 100 мс (очень быстро) даже при поиске с помощью главного шаблона (который нет), индексированного AD.

По-видимому, звонки, которые занимают больше всего времени, - это мои петли foreach, которые занимают около 40 секунд (40 000 мс).

Я обновляю вопрос с блоком кода в моих foreach петлях, как я не понял, как улучшить свою работу:

// --- I started a stopwatch here 
foreach (SearchResult searchResult in searchCol) 
{ 
    // --- I stopped the stopwatch here and noticed it takes about 30,000ms 
    result.code = 0; 

    DirectoryEntry de = searchResult.GetDirectoryEntry(); 

    ADSearchObject adObj = new ADSearchObject(); 

    adObj.code = 0; 

    if (de.Properties.Contains("displayName") 
    { 
     adObj.displayName = de.Properties["displayName"].Value.ToString(); 
    } 

    adObj.type = "user"; 

    result.queryResults.Add(adObj); 
} 

Примечания, где я начал и остановился мой «Секундомер» в моем обновленном код, я не знаю, почему начало цикла занимает так много времени.

+0

Так оно работает, когда вы имеете дело с Microsoft AD.Возможно, вы можете придумать синхронизацию дерева AD с каким-то БД, который позволит вам выполнять запросы в миллисекундах, а не запрашивать AD каждый раз. – r1verside

+2

Не будет '(& (displayName = * {result.querystring} *) (objectCategory = person))' делать то же, что и ваш фильтр? И вы можете StopWatch называть «FindAll». Они занимают больше всего времени? – rene

+0

@rene, пожалуйста, посмотрите на редактирование, которое я сделал для сообщения, я добавил несколько раз из «Секундомера», который я открыл в разных частях кода. –

ответ

1

Конечно, соответствие подстрок более дорогостоящее, чем соответствие равенства для уникального значения. Также не удивительно, что львиная доля прошедшего времени попадает в ваш блок итератора, который потребляет 40 с в зависимости от вашего профилирования.

Если вы уверены, что огромное снижение производительности происходит только путем создания итератора, я не - и это из-за вашего выбора точек времени.

StartClock("foreach"); 
foreach (SearchResult searchResult in searchCol) 
{ 
    // use an empty block to speed things up or 
    StopClock("foreach"); 
    // whatever 
    RestartClock("foreach"); 
} 
StopClock("foreach"); 
LogClock("foreach"); 

Я ожидаю, что огромный прирост производительности (для большого количества въездного), если вы обратите внимание на передовую практику я уже комментировал: Отправить один запрос к серверу ПОЛУЧАТЬ все, что вам нужно в результате вашего поиска, и дон 't отправить другой запрос для каждого элемента. Хотя один вызов GetDirectoryEntry() будет потреблять только < 1 мс, большое количество записей сделает ваш код бесполезным для функции автозаполнения вашего приложения.

Kudos to @rene для представления нормальной формы для этого выражения фильтра. Я не знаю об оптимизации фильтра в Active Directory, поэтому я бы взял верный путь с

(&(objectCategory=person)(displayName=*{result.querystring}*))