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?
I would suggest:
var result = ps.FindAll().ToList();
Since PrincipalSearchResult doesn't cache like other things, this will bring you down near an O(n) performance level.
It's surprising that Single()
should take quite so long on such a small list. I have to believe something else is going on here. The call to ps.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 within Single()
.
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. The PrincipalSearcher
class 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()
or ToDictionary()
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:
result.ToDictionary(u => u.SamAccountName)[usr.WEBUSER]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With