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
