How to use linq `Except` with multiple properties with different class?
Asked Answered
F

3

13

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?

Felicle answered 28/2, 2015 at 13:49 Comment(4)
I do not see any evidence in your code of you, using GetHashCode . I know this is a delicate matter myself, but if yo want us to help you out, you must at least submit code that is SUPPOSED to use GetHashCode.Feminism
As I said I am currently using two seprate queries to get result. I thought of implementing the IEqualityComparer but I was not sure how to implement the GetHashCode so I din't included here. As you Know if you implement the IEqualityComparer . Then you have to implement GetHashCode Felicle
Sorry, while reading more throw, your post was most informative, so my apologies .I understand you want to implement IEqualityComparer.Feminism
So, if you post your implementation of IEqualityComparer, I will help you out!Feminism
E
25

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();
Ectoderm answered 28/2, 2015 at 13:55 Comment(6)
Hey thanks for quick answer. Now when I Look at the expression. Ifeel dumb to not realising to use the where with || thanks. But I was just wondring is it possible to implement IEqualityComparer for two diffrent type of classes.Felicle
@Felicle No, IEqualityComparer<T> is an interface for an object that performs the comparison on two objects of the type T.Ectoderm
Yes Thanks I just started reading about it. I got it now how that works. I will try your way and try to find if there is any other way to get it as I am trying to learn the linq. And I will wait for 2 or 3 days to get the more answers with diffrent aproaches if not any new approch then I will Accept it as answer. Thanks for answer once again.Felicle
@Felicle Thanks. By the way, I have updated my answer. The filtering could be more simple. Also, if you want to try different ways for learnign purposes, then you can try my update.Ectoderm
Nice I did the same thing when trying new approches. Thanks for the update though. I wish I had more than one upvote option.Felicle
@Felicle Glad for you. Good luck.Ectoderm
F
0

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();
  }
}
Feminism answered 28/2, 2015 at 14:11 Comment(3)
Ouups, some formating errors. anyway, My idea, which I used hundreds of times, is to get a "unique string" and then get a hash.Feminism
I am sorry but how can it work. Here T is type of DataRow that one single type. My problem is my T type can be either TestA or TestB not a one type as mentioned above. As the GetHashcode get only one type as argument I am not sure how can I pass my two diffrent type of class to the GetHashCodeFelicle
Well, As I said, it's a concept, not an exact solution for your case.Feminism
H
-1
Public Class dosyalar
    Public adi As String
    Public tarih As DateTime
End Class


Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim dosyalarList1 As New List(Of dosyalar)
    dosyalarList1.Add(New dosyalar With {.adi = "dosya1", .tarih = New DateTime(2020, 3, 30, 10, 10, 10)})
    dosyalarList1.Add(New dosyalar With {.adi = "dosya2", .tarih = New DateTime(2020, 3, 30, 10, 10, 20)})
    dosyalarList1.Add(New dosyalar With {.adi = "dosya3", .tarih = New DateTime(2020, 3, 30, 10, 10, 30)})
    dosyalarList1.Add(New dosyalar With {.adi = "dosya4", .tarih = New DateTime(2020, 3, 30, 10, 10, 40)})
    dosyalarList1.Add(New dosyalar With {.adi = "dosya5", .tarih = New DateTime(2020, 3, 30, 10, 10, 50)})

    Dim dosyalarList2 As New List(Of dosyalar)
    dosyalarList2.Add(New dosyalar With {.adi = "dosya1", .tarih = New DateTime(2020, 3, 30, 10, 10, 11)})
    dosyalarList2.Add(New dosyalar With {.adi = "dosya2", .tarih = New DateTime(2020, 3, 30, 10, 10, 21)})
    dosyalarList2.Add(New dosyalar With {.adi = "dosya3", .tarih = New DateTime(2020, 3, 30, 10, 10, 30)})
    dosyalarList2.Add(New dosyalar With {.adi = "dosya4", .tarih = New DateTime(2020, 3, 30, 10, 10, 40)})
    dosyalarList2.Add(New dosyalar With {.adi = "dosya5", .tarih = New DateTime(2020, 3, 30, 10, 10, 50)})


    Dim result As List(Of dosyalar) = dosyalarList1.Where(Function(a) Not dosyalarList2.Any(Function(b) a.adi = b.adi AndAlso a.tarih = b.tarih)).ToList

End Sub
Habitant answered 30/3, 2020 at 23:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.