JsonStatham
JsonStatham

Reputation: 10364

Filtering a list using except on multiple properties

I am trying to write a method which will ultimately give me a collection of records which no longer have a corresponding record within a different collection to which I am referencing.

The structure is something similar to:

public class A
{
    int id {get; set;}
    int recId {get; set;}
    string category {get; set;}
}


public class B
{
    int recId {get; set;}
    string category {get; set;}
}

So far I have the following bit of LINQ to give me my collection of missing records but this only giving me any recId that is missing:

var noLongerHaveRecordInCollectionB =
CollectionA.Select(x => x.recId).Except(CollectionB.Select(x => x.recId));

I need a List<A> of every record which:

1.) No longer has a recId held in the CollectionB.

OR

2.) No longer has a record which has a matching recId and category in CollectionB

For example it is possible that within CollectionA there are 2 records with the same recId but different category.

If CollectionB now only contains 1 record for that recId I would want to remove the record in CollectionA which does not have the corresponding category.

So the ultimate question is how can I populate noLongerHaveRecordInCollectionB with all <T> A's that have been checked against CollectionB and not just the recId as it currently does, I want the whole object.

edit: input/result

CollectionA

1,2254,Category A

2,2236,Category A

3,2415,Category B

4,1275,Category B <--- same person as below, diff category

5,1275,Category C <--- same person as above, diff category

CollectionB

2254,Category A

2415,Category B

1275,Category C

Expected Result

I would expect (from CollectionA) the following id's to be highlighted in my list for deletion: 2, 4

Upvotes: 1

Views: 2872

Answers (2)

Mahesh
Mahesh

Reputation: 8892

You can simply achive what you want by using Where clause with || condition. No need to go for the complex solution.

 var result = collectionA.Where(a => collectionB.Any(b => a.recId == b.recId && a.Category != b.Category)
              || !collectionB.Any(b => b.recId == a.recId));

OLD ANSWER

You can implement IEqualityComparer for your class and compare the objects based on the fields you are interested in.For example implement the Equals method like this:

public bool Equals(A a, B b)
{
    return a.recId  == b.recId && a.category == b.category ;
}

Then just call Except method and pass your comparer:

var resultList = CollectionA .Except(CollectionB, new MyEqualityComparer());

Or if you implement it on your class, instead of a separate comparer you can just call Except:

var resultList = CollectionA .Except(CollectionB);

Be aware that you need also implement GetHashCode method.

UPDATE

If you want to go with the IEqualityComparer<T>you can create a new List from the second list:

  var secondListA = CollectionB.Select(x=> new A(){Category=x.Category, recId=x.recId});

And then create your Comparer:

sealed class MyComparer : IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
        if (x == null)
            return y == null;
        else if (y == null)
            return false;
        else
            return x.recId== y.recId&& x.Category == y.Category;
    }

    public int GetHashCode(A obj)
    {
        return obj.recId.GetHashCode();
    }
}

And use Except() overload which produces the set difference of two sequences by using the specified IEqualityComparer<T> to compare values.:

    var result = CollectionA.Except(secondListA, new MyComparer ());

But I think you should go with the simple Where filter as creating new List of A type can cause lot of performance overhade.

Upvotes: 2

Tim Schmelter
Tim Schmelter

Reputation: 460098

You can always use set methods like Enumerable.Except and use another set method Join to get the complete data.

I need a List<A> of every record which: 1.) No longer has a recId held in the CollectionB.

var onlyInA = CollectionA.Select(x => x.recId).Except(CollectionB.Select(x => x.recId));
var dataOnlyInA = from a in CollectionA
                  join recId in onlyInA 
                  on a.onlyInA equals recId 
                  select a;
List<A> result =  dataOnlyInA.ToList();

2.) No longer has a record which has a matching recId and category in CollectionB

Maybe:

var canBeRemovedFromA = CollectionA
    .Where(a => CollectionB.Any(b => a.recID == b.recId && a.category!= b.category));

this doesn't check if there is at least one matching category. It's not clear if that's a must.

Upvotes: 1

Related Questions