How to use linq `Except` with multiple properties

2019-01-24 16:20发布

问题:

I am trying to learn the Linq/Lambda expressions and was stuck at somewhere.

What I was Doing

I have created two classes with properties which have some common properties in them. The classes are like(It's test code).

class TestA
    {
        public int Id { get; set; }
        public int ProductID { get; set; }
        public string Category { get; set; }

        public TestA(int id, int procid, string category)
        {
            this.Id = id;
            this.ProductID = procid;
            this.Category = category;
        }
    }

    class TestB
    {
        public int ProductID { get; set; }
        public string Category { get; set; }

        public TestB(int procid, string category)
        {
            this.ProductID = procid;
            this.Category = category;
        }
    }

Then I created the two list for them as,

        List<TestA> testListA = new List<TestA>();
        List<TestB> testListB = new List<TestB>();
        TestA t1 = new TestA(1, 254, "ProductA");
        TestA t2 = new TestA(1, 236, "ProductA");
        TestA t3 = new TestA(1, 215, "ProductB");
        TestA t4 = new TestA(1, 175, "ProductB");
        TestA t5 = new TestA(1, 175, "ProductC");
        testListA.Add(t1);
        testListA.Add(t2);
        testListA.Add(t3);
        testListA.Add(t4);
        testListA.Add(t5);

        TestB tt1 = new TestB(254, "ProdcutA");
        TestB tt2 = new TestB(215, "ProductB");
        TestB tt3 = new TestB(175, "ProductC");
        testListB.Add(tt3);
        testListB.Add(tt2);
        testListB.Add(tt1);

Now for result I wanted t2 as it's ProductID match is not in the testListB And t4 as it has matching ProductID in testListB but don't have the same Category.
1) I need a List<A> of every record which: No longer has a ProductID held in the testListB

which I can get as ,

  testListA.Select(x => x.ProductID).Except(testListB.Select(x => x.ProductID ));

2)No longer has a record which has a matching ProductID and Category in testListB

which I Can get using,

   testListA.Where(a => testListB.Any(b => a.ProductID == b.ProductID && a.Category != b.Category));

**My Question **
Is it possible two make single linq expression to get result.I thought of using the implement IEqualityComparer but I am not sure how to implement the GetHashCode it for two different type of classes. So either combining the above query into single query or any other way to impliment custom Comparer for two different Type of classes. Or is there any other simple way?

回答1:

You have said that you need only these objects from testListA:

  • there is not matching ProductID in testListB
  • there is existing mathing ProductID, but with different Category

So, your filter must be:

!testListB.Any(b => a.ProductID == b.ProductID && a.Category == b.Category)

So, change your code as:

testListA.Where(a => !testListB.Any(b => a.ProductID == b.ProductID && a.Category == b.Category));

Second approach:

Or you can create a new List<TestA> from the second list:

 var secondListA = testListB.Select(x=> new TestA(){Category=x.Category, ProductID=x.ProductID}).ToList();

And then create your Comparer:

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

    public int GetHashCode(TestA obj)
    {
        return obj.ProductID.GetHashCode();
    }
}

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

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


回答2:

This is an example implementing IEqualityComparer. It should work as a concept for your purpose:

private class PKExclusiveComparer<T> : IEqualityComparer<T> where T : DataRow
{
  public bool Equals(T x, T y)
  {
    bool result = true;
    foreach (DataColumn col in (x as DataRow).Table.Columns)
    {
      if (!(x as DataRow).Table.PrimaryKey.Contains(col))
      {
        result &= x[col].Equals(y[col]);
      }
    }
    return result;
  }

  public int GetHashCode(T x)
  {
    string code = string.Empty;
    foreach (DataColumn col in (x as DataRow).Table.Columns)
    {
      if (!(x as DataRow).Table.PrimaryKey.Contains(col))
      {
        code += x[col].ToString();
      }
    }
    return code.GetHashCode();
  }
}