I am trying to write a utility to see if a user has logged in to windows since a date that I have stored in a database.
private void bwFindDates_DoWork(object sender, DoWorkEventArgs e)
{
UserPrincipal u = new UserPrincipal(context);
u.SamAccountName = "WebLogin*";
PrincipalSearcher ps = new PrincipalSearcher(u);
var result = ps.FindAll();
foreach (WebAccess.WebLoginUsersRow usr in webAccess.WebLoginUsers)
{
UserPrincipal b = (UserPrincipal)result.
Single((a) => a.SamAccountName == usr.WEBUSER);
if (b.LastLogon.HasValue)
{
if (b.LastLogon.Value < usr.MODIFYDATE)
usr.LastLogin = "Never";
else
usr.LastLogin = b.LastLogon.Value.ToShortDateString();
}
else
{
usr.LastLogin = "Never";
}
}
}
However the performance is very slow. The user list I am pulling from has about 150 Windows users, so when I hit the line UserPrincipal b = (UserPrincipal)result.Single((a) => a.SamAccountName == usr.CONVUSER); it takes 10 to 15 seconds for it to complete per user (stepping through i can see it is doing the step a.SamAccountName == usr.CONVUSE is run for every person so the worst case is running O(n^2) times)
Any recommendations on ways to improve my efficiency?
It’s surprising thatSingle()should take quite so long on such a small list. I have to believe something else is going on here. The call tops.FindAll()may be returning an object that does not cache it’s results, and is forcing you to make an expensive call to some resource on each iteration withinSingle().You may want to use a profiler to investigate where time is going when you hit that line. I would also suggest looking at the implementation of
FIndAll()because it’s returning something unusually expensive to iterate over.So after reading your code a little more closely, it makes sense why
Single()is so expensive. ThePrincipalSearcherclass uses the directory services store as the repository against which to search. It does not cache these results. That’s what’s affecting your performance.You probably want to materialize the list using either
ToList()orToDictionary()so that accessing the principal information happens locally.You could also avoid this kind of code entirely, and use the
FindOne()method instead, which allows you to query directly for the principal you want.But if you can’t use that, then something like this should work better: