'how to use LINQ ".Except()" with AD user objects?

I am having trouble with the LINQ .Except() clause.

I am given a list of unique, sorted, current usernames. I can look these up in our company AD and generate a list of UserPrinciple items for each one. This list is to become the current membership of an AD group.

Assuming that the AD group has been populated sometime in the past, I want to modify the membership with the UserPrinciple list of current users. I know that I can adGroup.Members.Clear(); then loop over the current user list doing adGroup.Member.Add(user);. This takes a while and I hoped that there might be a more efficient way to do this.

I found the LINQ .Except() clause and it looks like it should do the trick... if I can make it work.

I have tried some simple examples like:

var t1 = new List<int>{1,2,3,4,5,6,7,8,9};
var t2 = new List<int>{1,55,9};
var t3 = new List<int>{9,8,7,6,5,4,3,2,1};

var t1_t2 = t1.Except(t2).ToList();
    // yeilds: 2,3,4,5,6,7,8

var t2_t1 = t2.Except(t1).ToList();
    // yeilds: 55

var t1_t3 = t1.Except(t3).ToList();
    // yeilds: empty list

However, trying this with lists of UserPrinciple is not behaving the same way.

Let's say that the AD group and the user list are identical. If I try to get the list of items to remove from the AD group I try:

using var ctx = new PrincipalContext(ContextType.Domain, null, "MyADuser", "a-password");

var groupDn =
    "CN=My Test Group,OU=Groups,OU=MyUnit,DC=ad,DC=MyCompany,DC=com";
    
using var group = GroupPrincipal.FindByIdentity(ctx, groupDn);

var userList = GetUserList();

var removeList = group.Members.Except(userList).ToList();

I would expect removeList to be empty, but instead it contains all of the member records. Likewise, if I go in the other direction:

var addList = userList.Except(group.Members).ToList();

I get the entire member list. I expected an empty list.

I have thought that I might need to implement the IEqualityComparitor but I am aparantly not clever enough to do that.

What am I missing? Should I just punt and .Clear() the AD group and rebuild it each time?



Solution 1:[1]

Thanks to @Ralf's prompts and an article (from dotnettutorials.net), I have a comparer.

Edit

private class MemberComparer : IEqualityComparer<Principal> {
    public bool Equals(Principal x, Principal y) {
        //First check if both object reference are equal then return true
        if (ReferenceEquals(x, y)) {
            return true;
        }

        //If either one of the object reference is null, return false
        if (x is null || y is null) {
            return false;
        }

        var xName = x.SamAccountName ?? "" + x.DistinguishedName ?? "";
        var yName = y.SamAccountName ?? "" + y.DistinguishedName ?? "";

        //Comparing all the properties one by one
        return xName == yName;
    }

    public int GetHashCode(Principal obj) {
        //Get the ID hash code value
        var idHashCode = obj.Sid == null ? 0 : obj.Sid.GetHashCode();

        //Get the Name HashCode Value
        var name = obj.SamAccountName ?? "" + obj.DistinguishedName ?? "";
        var nameHashCode = name == "" ? 0 : name.GetHashCode();

        return idHashCode ^ nameHashCode;
    }
}

The edits are based on some advice from our local AD expert on which fields to use.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1