We have the following recursive function which is used for looking up the member objects, which are listed in the following property of the group object. We use the group list for the recursive check in the groups.
This function with approximately 30k users and 40k groups takes about 20 minutes to run, which we want to speed up. Any ideas how to do this more efficient?
foreach (ad_group_source group in group_source)
{
List<ad_user_source> list = FindMembers(group, group_source, user_source);
}
public static List<ad_user_source> FindMembers(ad_group_source group, HashSet<ad_group_source> group_source, HashSet<ad_user_source> user_source)
{
List<String> members = group.Members.Split(';').ToList();
if (members.Equals(""))
{
return new List<ad_user_source>();
}
List<ad_user_source> members2 = new List<ad_user_source>();
foreach (String member in members)
{
if (!member.Equals(""))
{
Boolean isUser = false;
ad_user_source gebruiker = user_source.FirstOrDefault(u => u.DistinguishedName == member);
if (gebruiker != null)
{
members2.Add(gebruiker);
isUser = true;
}
if (!isUser)
{
ad_group_source group2 = group_source.FirstOrDefault(g => g.CN == member.Substring(3));
if (group2 != null)
{
List<ad_user_source> l = FindMembers(group2, group_source, user_source);
members2.AddRange(l);
}
}
}
}
List<ad_user_source> members3 = members2.Distinct().ToList();
return members3;
}
The problem is that your code keeps using hash sets as if they were lists. This is very inefficient.
To address this problem construct a Dictionary<string,ad_user_source> organized by DistinguishedName, and Dictionary<string,ad_group_source> organized by g.CN. Don't put groups with CN that is longer than three characters, in case there are any in the original set.
Related
I have a method that outputs a list of RSVPs for a class. The RSVP is based on a pull from a SQL table holding RSVP records, based on an input parameter of the class id. Then a dictionary of all students (people who RSVPd to that class) is made. Finally I output the IEnumerable of the RSVPs based on the data for each student.
The problem I'm running into is that I've got a couple students in the data that are "bad users": They aren't in the system. Potentially because of bad record deletions, or bad creations. Either way, I need to set up error handling for "bad student records" while building the IEnumerable.
My thought was to catch the potential error when evaluating the student id on this line:
var data = x.ToRsvpData(students[x.RawAgentId]);
And then just skip that record and move on to the next one. However, I'm not sure how to do that.
Here's the complete method:
public IEnumerable<RsvpData> GetAllRsvpsFor(Guid scheduledId)
{
var rsvps = _sors.AnyRsvpsIn(new[] { scheduledId })[scheduledId];
var certificates = _sors.CertificatesIn(rsvps.Select(x => x.RsvpId).ToList());
var students = _sors.StudentsBy(rsvps);
return rsvps.Select(x => {
var data = x.ToRsvpData(students[x.RawAgentId]);
if (x.Completed)
{
data.SignatureUrl = StaticContent.S3WebPrefixFor(string.Format("/schools/signatures/{0}.jpg", x.RsvpId.ToString()));
var cert = certificates.FirstOrDefault(c => c.Rsvp.RsvpId == x.RsvpId);
data.CertificateId = cert != null ? cert.CertId.ToString() : "";
}
return data;
}).OrderBy(x => x.LastName).ToList();
}
Update:
Here's the completed, working code:
public IEnumerable<RsvpData> GetAllRsvpsFor(Guid scheduledId)
{
var rsvps = _sors.AnyRsvpsIn(new[] { scheduledId })[scheduledId];
var certificates = _sors.CertificatesIn(rsvps.Select(x => x.RsvpId).ToList());
var students = _sors.StudentsBy(rsvps);
var cleanRsvpData = new List<RsvpData>();
foreach (var rsvp in rsvps)
{
try
{
var data = rsvp.ToRsvpData(students[rsvp.RawAgentId]);
if (rsvp.Completed)
{
data.SignatureUrl = StaticContent.S3WebPrefixFor(string.Format("/schools/signatures/{0}.jpg", rsvp.RsvpId.ToString()));
var cert = certificates.FirstOrDefault(c => c.Rsvp.RsvpId == rsvp.RsvpId);
data.CertificateId = cert != null ? cert.CertId.ToString() : "";
}
cleanRsvpData.Add(data);
}
catch (Exception ex)
{ //Bad Student record
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
}
Depending on what makes them particularly misbehaving records, simply wrapping your Enumerable in another Enumerable should fix the problem. Something like this:
IEnumerable<Record> GetCorrectRecords(IEnumerable<Record> records)
{
foreach(var record in records)
if(record.Valid) // up to you how you define this
yield return record;
}
Instead of using lambda expressions, your best bet would probably be to use a temp list variable and use a try catch block - so only the "clean" records will make it into your list:
List<RsvpData> cleanRsvpData = new List<RsvpData>();
foreach (RsvpData rsvp in rsvps)
{
try
{
RsvpData data = rsvp.ToRsvpData(students[x.RawAgentId]);
if (rsvp.Completed)
{
data.SignatureUrl = "test";
var cert = certificates.FirstOrDefault(c => c.Rsvp.RsvpId == x.RsvpId);
data.CertificateId = cert != null ? cert.CertId.ToString() : "";
}
cleanRsvpData.Add(data);
}
catch (Exception ex)
{ // handle error here
}
}
Since the try catch is INSIDE the loop, it won't break your whole loop if one of the items in the list throws an error.
You can do that using the famous try...catch block, something like this:
public IEnumerable<int> GetAllRsvpsFor(Guid scheduledId)
{
//all the code that precedes the loop
//for or foreach loop
{
//any code that you have to perform after this block
try
{
var data = x.ToRsvpData(students[x.RawAgentId]);
}
catch
{
continue; //continues to the next for iteration in case of any error
}
//any code that you have to perform after this block
}
}
I have this bit of code in a class:
public class TicketSummary
{
//get all the development tickets
public List<IncidentSummary> AllDevelopmentTickets { get; set; }
public List<string> TicketNames()
{
List<string> v = new List<string>();
foreach (var developmentTicket in AllDevelopmentTickets)
{
var ticketIds = developmentTicket.id.ToString(CultureInfo.InvariantCulture);
v.Add(ticketIds);
}
return v;
}
}
}
And I am trying to see if my API connection (plus all the code) did it's job and pulls back the tickets and their info, more specifically the ids.
In my main program I have no clue how to check if it did the job. I tried something but it isn't quite right and doesn't return anything ( I know I need a Console.WriteLine)
static void Main(string[] args)
{
Console.ReadLine();
var tickets = new TicketSummary();
tickets.TicketNames();
while ( tickets != null )
{
Console.WriteLine(tickets);
}
}
Any suggestions, please?
Thank you!
You've dropped the returned result: tickets.TicketNames(); returns List<String> that you have to assign and then itterate:
var tickets = new TicketSummary();
var names = tickets.TicketNames(); // <- names, List<String> according to the code
// printing out all the names
foreach(var name in names)
Console.WriteLine(name);
Do you mean you just want to print all the tickets out?
foreach (var ticket in tickets.TicketNames())
{
Console.WriteLine(ticket);
}
You have several problems in your code, that should keep it from even compiling, but aside from that, It seem's what you're really after is rather transforming the data in AllDevelopmentTickets, rather than moving it somewhere. So you could probably do it with a Select call (from LINQ). So, in your main method:
var tickets = new TicketSummary();
// add some tickets to tickets.AllDevelopmentTickets here...
var ticketNames = tickets.AllDevelopmentTickets.Select(ticket => ticket.id.ToString();
// Yes, you should probably use an UI culture in the ToString call.
// I'm just trying to limit my line width =)
Now, ticketNames should be an IEnumerable<string> holding all the ticket ids. To, for example, print them out, you can iterate over them and write to console output:
foreach (var name in ticketNames) {
Console.WriteLine(name);
}
You need to assign / use the return value of the TicketNames() method. This seems a lot of work just to return the string version of the TicketId. This can be reduced
public List<string> TicketNames()
{
return AllDevelopmentTickets
.Select(t => t.id.ToString(CultureInfo.InvariantCulture))
.ToList();
}
var ticketSummary = new TicketSummary();
var ticketNames = ticketSummary.TicketNames();
foreach(var ticketName in ticketNames)
{
Console.WriteLine(ticketName);
}
or Even just:
foreach(var ticketName in AllDevelopmentTickets
.Select(t => t.id.ToString(CultureInfo.InvariantCulture)))
{
Console.WriteLine(ticketName);
}
You're ignoring the returned value.
static void Main(string[] args)
{
Console.ReadLine();
var tickets = new TicketSummary();
var res = tickets.TicketNames();
while ( for r in res )
{
Console.WriteLine(r);
}
}
In my view i am having two types of values and i am passing into string arrays. I can't concatenate both. I am using multiselect checkbox..
My code:
string[] func= { };
string[] role= { };
if (!string.IsNullOrEmpty(collection["Area"]))
func = collection["Area"].Split(',');
if (!string.IsNullOrEmpty(collection["Role"]))
roles= collection["Role"].Split(',');
foreach (string pf in func )
{
if (!string.IsNullOrEmpty(collection["role_" + func]))
role= collection["role_" + func].Split(',');
if (role!= null && role.Length > 0)
{
foreach (string rl in role)
{
prefunccpf = new prefunc();
cpf.CID= cid;
cpf.FID= Convert.ToInt32(func);
cpf.RID= Convert.ToInt32(rl);
}
}
}
In this first foreach statement i am checking functions array then it will come inside. Then Second foreach statement i am checking roles array. If roles array having count means it will save with roleId. But This conditions are goodly work if i select single function. If I select 2 and more function the loop again goes to top and again come to pref.roles. It didn't check the already those roles are saved. it is increasing again. Actually what will do for this?
I found the solution. By using Zip function in C# i got the exact result.
MY CODE HERE
string[] func= { };
string[] rol= { };
if (!string.IsNullOrEmpty(collection["Area"]))
func= collection["Area"].Split(',');
if (!string.IsNullOrEmpty(collection["Role"]))
roles= collection["Role"].Split(',');
var lstCombined = roles.Zip(func, (role, func) => new { Role = role, Function = func }).ToList();
foreach (var pf in lstCombined)
{
var cpf= new prefunc
{
CID= candidateId,
FID= Convert.ToInt32(pf .Function),
RID= Convert.ToInt32(pf .Role)
};
}
I have a list that contains 3 items, two of type_1, and one of type_2. I want to return a second list that contains the type and number of that type that exists. When stepping through the breakpoints set at the foreach loop, the IF statement is never true. I assume there is something wrong with my attempt to use Contains() method.
The output should be something like:
type_1 2
type_2 1
Instead, it evaluates as:
type_1 1
type_1 1
type_2 1
Is my use of Contains() not correct?
public List<item_count> QueryGraphListingsNewAccountReport()
List<item> result = new List<items>();
var type_item1 = new item { account_type = "Type_1" };
var type_item2 = new item { account_type = "Type_1" };
var type_item3 = new item { account_type = "Type_2" };
result.Add(type_item1);
result.Add(type_item2);
result.Add(type_item3);
//Create a empty list that will hold the account_type AND a count of how many of that type exists:
List<item_count> result_count = new List<item_count>();
foreach (var item in result)
{
if (result_count.Contains(new item_count { account_type = item.account_type, count = 1 } ) == true)
{
var result_item = result_count.Find(x => x.account_type == item.account_type);
result_item.count += 1;
result_count.Add(result_item);
}
else
{
var result_item = new item_count { account_type = item.account_type, count = 1 };
result_count.Add(result_item);
}
}
return result_count;
}
public class item
{
public string account_type { get; set; }
}
public class item_count
{
public int count {get; set;}
public string account_type { get; set; }
}
I think your problem is that you don't want to use contains at all. You are creating a new object in your contains statement and, obviously, it isn't contained in your list already because you only just created it. The comparison is comparing references, not values.
Why not just use the find statement that you do in the next line instead? If it returns null, then you know there isn't an item already with that type.
So you could do something like this:
var result_item = result_count.Find(x => x.account_type == item.account_type);
if (result_item != null)
{
result_item.count++;
// note here you don't need to add it back to the list!
}
else
{
// create your new result_item here and add it to your list.
}
Note: Find is o(n), so this might not scale well if you have a really large set of types. In that case, you might be better off with Saeed's suggestion of grouping.
You can do:
myList.GroupBy(x=>x.type).Select(x=>new {x.Key, x.Count()});
If you want use for loop, it's better to use linq Count function to achieve this, If you want use Contains you should implement equal operator as the way you used.
As the title mentions I need a way to get all groups a group is member of in ActiveDirectory.
To get all groups a user is member of I use
public static DirectoryEntry[] GetGroupsUserIsMemberOf(DirectoryEntry directoryEntry)
{
ArrayList groupsUserIsMemberOf = new ArrayList();
object groups = null;
DirectoryEntry[] userGroupEntries = null;
if (directoryEntry != null && directoryEntry.SchemaClassName == "user") {
groups = directoryEntry.Invoke("Groups", null);
foreach (object group in (IEnumerable)groups) {
groupsUserIsMemberOf.Add(new DirectoryEntry(group));
}
userGroupEntries = (DirectoryEntry[])groupsUserIsMemberOf.ToArray(typeof(DirectoryEntry));
}
return userGroupEntries;
}
but when trying
public static DirectoryEntry[] GetGroupsGroupIsMemberOf(DirectoryEntry directoyEntry)
{
ArrayList groupsGroupIsMemberOf = new ArrayList();
object groups = null;
DirectoryEntry[] groupEntry = null;
if (directoyEntry != null && directoyEntry.SchemaClassName == "group") {
groups = directoyEntry.Invoke("Groups", null); // throws exception (see below)
foreach (object group in (IEnumerable)groups) {
groupsGroupIsMemberOf.Add(new DirectoryEntry(group));
}
groupEntry = (DirectoryEntry[])groupsGroupIsMemberOf.ToArray(typeof(DirectoryEntry));
}
return groupEntry;
}
to get all groups a group is member of the line
groups = directoyEntry.Invoke("Groups", null); // throws exception (see below)
throws an exception:
"Unknown name. (exception HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))"
Does someone know a performant way to get all groups a group is member of?
Think I've got it on my own:
To get all groups a group is member of you can use
directoryEntry.Properties["memberOf"][0]
and you get a string object with all ADObjects your group is member of.
Split it into single AD-Object strings, check if group und you got it.
This code will get you a list of groups from the current logged on user, it is faster than querying the domain controller for the information because it comes out of the cached security identifer:
WindowsIdentity currentIdent = WindowsIdentity.GetCurrent();
IdentityReferenceCollection currentGroups = currentIdent.Groups;
List<String> groups = new List<string>();
foreach (IdentityReference indentity in currentGroups)
{
groups.Add(indentity.Translate(typeof(NTAccount)).ToString());
}