Home > database >  Compare two different generic lists based on some common columns and return unmatched items
Compare two different generic lists based on some common columns and return unmatched items

Time:01-06

I've 2 different generic lists having some common fields that I want to use for comparison and return unmatched rows from other lists and vice-versa.

First list: List freightCharges Second list: List shippingCharges

I want to compare these two lists where "Family" is not matching in second list and generating a new list of differences and want to repeat vice-versa in next step but the following query is not returning anything while there are differences:

var list0 = shippingCharges.Where(item => freightCharges.All(f => item.Buid == f.BUID && item.Catalog == (f.Catalog != null ? f.Catalog : null)
                            && item.Productline == (f.ProductLine != null ? f.ProductLine : null)
                            && item.Brand == (f.Brand != null ? f.Brand : null) && item.Family != (f.Family != null ? f.Family : null))).ToList();

There was some possibility of null so, I handled them as well.

I want to compare each list items of one list with another. I can use foreach loop but thought there would be something available in Linq for the same.

CodePudding user response:

If you will be using this more often for different classes, my advice would be to make a generic extension method for this, so you can reuse it as if it was a standard LINQ method. If you are not familiar with extension methods, read Extension Methods Demystified.

So we have to input sequences of different type. The objects in sequence 1 have a property X, the objects in sequence to have a property Y. You can check for equality between the values of properties X and Y.

Requirement: return all objects in sequence 1 that have a value for property X that is not equal to any of the Y values of the objects in sequence 2. Concatenate this with the vice-versa.

So let's first make a procedure for one side. Then we reuse it with Concat for the vice-versa

public static WhereNotMatched<Touter, Tinner, TKey>(
    this IEnumerable<Touter> outer,
    IEnumerable<Tinner> inner,
    Func<Touter,TKey> outerKeySelector,
    Func<TInner,TKey> innerKeySelector)
{
    return WhereNotMatched(outer, inner, outerKeySelector, innerKeySelector, null);
{

public static IEnumerable<Touter> WhereNotMatched<Touter, Tinner, TKey>(
    this IEnumerable<Touter> outer,
    IEnumerable<Tinner> inner,
    Func<Touter,TKey> outerKeySelector,
    Func<TInner,TKey> innerKeySelector  
    IEqualityComparer<TKey> comparer)
{
    // if no comparer provided, use the default comparer
    if (comparer == null) comparer = EqualityComparer<TKey>.Default;

    // TODO: check outer != null, inner != null, etc.

    // extract all inner keys, for efficient lookup put them in a HashSet<TKey>
    HashSet<TKey> innerKeys = new HashSet(inner.Select(i => innerKeySelector(i)), comparer);

    // return all outer that have no corresponding value in the HashSet
    return outer.Where(o => !innerKeys.Contains(innerKeySelector(o));
}

Usage:

Suppose in an ordering system you have Products, Orders and OrderLines. Every OrderLine has a foreign key ProductId. This ProductId refers to the Product that is mentioned in this OrderLine. Every Order contains zero or more OrderLines.

IEnumerable<Order> orders = ...
IEnumerable<Product> products = ...

// Get all Products that have never been ordered yet
IEnumerable<OrderLine> orderLines = orders.SelectMany(order => order.OrderLines);

IEnumerable<Product> neverOrderedProducts = products.WhereNotMatched(orderLines,
    product => product.Id,
    orderLine => orderLine.ProductId);

The following is more what you asked for:

IEnumerable<Student> maleStudents = ...
IEnumerable<Student> femaleStudents = ...

Suppose every Student has a PromCompanion. We're not old fashioned, so some male Students have a PromCompanion that is not a femaleStudent, and similarly, some female Students have a non-maleStudent as PromCompanion. Find all these Students.

IEnumerable<Student> result = maleStudents.WhereNotMatched(femaleStudents,
    man => man.PromCompanion,
    woman => woman.PromCompanion)
// concat with vice-versa
.Concat(femaleStudents.WhereNotMatched(maleStudents,
    woman => woman.PromCompanion,
    man => man.PromCompanion));



IEnumreable<Student> uncompaniedStudents = maleStudents.WhereNon
    

CodePudding user response:

Let's take two classes as an example:

public class ShippingCharges
{
    public string ProductLine { get; set; }
    public int Family { get; set; }
}

public class FreightCharges
{
    public string Brand { get; set; }
    public int? Family { get; set; }
}

We add some values:

var ListA = new List<ShippingCharges>() {
    new ShippingCharges()
    {
        ProductLine = "1",
        Family = 1
    },
    new ShippingCharges()
    {
        ProductLine = "1",
        Family = 2
    },
};

var ListB = new List<FreightCharges>(){
    new FreightCharges()
    {
        Brand = "2",
        Family = 2
    },
    new FreightCharges()
    {
        Brand = "3",
        Family = 3
    },
};

Add some LINQ extensions:

public static class LinqExtensions
{
    public static IEnumerable<TSource> Except<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
    {
        return first.Where(x => second.Count(y => comparer(x, y)) == 0);
    }

    public static IEnumerable<TSource> Contains<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
    {
        return first.Where(x => second.FirstOrDefault(y => comparer(x, y)) != null);
    }

    public static IEnumerable<TSource> Intersect<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
    {
        return first.Where(x => second.Count(y => comparer(x, y)) == 1);
    }
}

Get all elements from list A that are not in list b:

var newData = ListA.Except(ListB, (a,b) => a.Family == b.Family);

Short note: f.Brand != null ? f.Brand : null is equal to f.Brand ?? null is equal to f.Brand

CodePudding user response:

You will have to override the Equal method for the model being compared. See this link from the MSDocs for more information of how and why to do so.

https://docs.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-6.0

  •  Tags:  
  • Related