Reputation: 1631
Scenario:
I have a list of the object 'Order
and would like to group by the identical's List<OrderLine>
property, by identical I mean same quantity of lines as well as same Sku/Quantity values in the same order' and return the list of order values grouped:
class Order
{
public int OrderNumber { get; set; }
public List<OrderLine> Lines { get; set; }
}
class OrderLine
{
public string Sku { get; set; }
public int Quantity { get; set; }
}
Input sample:
+-------------+-----+----------+
| OrderNumber | Sku | Quantity |
+-------------+-----+----------+
| 1 | A | 10 |
| 1 | B | 20 |
| 2 | A | 10 |
| 3 | A | 10 |
| 3 | B | 20 |
+-------------+-----+----------+
Output desired:
Lines = Lines.Count();
the count of lines for each grouped identical
Pieces = SUM(OrderLine.Quantity);
the sum of all quantities for each grouped identical orders.
+-----------------+-------+--------+
| TotalIdenticals | Lines | Pieces |
+-----------------+-------+--------+
| 1 | 1 | 10 |
| 2 | 2 | 30 |
+-----------------+-------+--------+
I used a table representation to make it clearer. So as above there is only 1 record with 1 line (order 2) and qty 10. on the other hand, there are two orders with the same list of lines (order 1 and 3)
So I need that after running a linq algorithm, it would generate for me a object kind of
> "identical 1".Orders -> [2]
> "identical 2".Order -> [1,3]
What I tried to do?
var identicals = orderList.GroupBy(x => x.Lines)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
The code above did not work, basicaly I just need to be able to group the Lines property (so its equality to other OrderLines), then I will be able to generate my output of lines/pieces... the only issue now is to be able to group my object order list by Lines list object similarity.
I hope I was clear in my question, if you need more details please let me know and I will add here.
Upvotes: 3
Views: 3345
Reputation: 18014
First step - in order to use GroupBy()
with the Order
and OrderItem
classes, you have to implement Equals()
and GetHashCode()
OR create an EqualityComparer
for both classes.
Overriding Equals()
and GetHashCode()
in Order
(based on the Lines
property only):
public class Order
{
public int OrderNumber { get; set; }
public List<OrderLine> Lines { get; set; }
protected bool Equals(Order other)
{
var equals = OrderLinesEquals(Lines, other.Lines);
return equals;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Order) obj);
}
public override int GetHashCode()
{
if (Lines == null)
{
return 0;
}
unchecked
{
int hash = 19;
foreach (OrderLine item in Lines.OrderBy(x => x.Sku, StringComparer.OrdinalIgnoreCase))
{
hash = hash * 31 + item.GetHashCode();
}
return hash;
}
}
private bool OrderLinesEquals(List<OrderLine> x, List<OrderLine> y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
bool areEquivalent = x.Count == y.Count && !x.Except(y).Any();
return areEquivalent;
}
public override string ToString()
{
return $"Sku: {Sku ?? "[null]"}, Quantity: {Quantity}";
}
}
Overriding Equals()
and GetHashCode()
in OrderItem
(based on both the Sku
and Quantity
properties):
public class OrderLine
{
public string Sku { get; set; }
public int Quantity { get; set; }
protected bool Equals(OrderLine other)
{
return string.Equals(Sku, other.Sku) && Quantity == other.Quantity;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((OrderLine) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((Sku != null ? Sku.GetHashCode() : 0) * 397) ^ Quantity;
}
}
}
Testing code - list of orders:
var order1 = new Order
{
OrderNumber = 1,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 10,
Sku = "A"
},
new OrderLine
{
Quantity = 20,
Sku = "B"
}
}
};
var order2 = new Order
{
OrderNumber = 2,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 10,
Sku = "A"
}
}
};
var order3 = new Order
{
OrderNumber = 3,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 20,
Sku = "B"
},
new OrderLine
{
Quantity = 10,
Sku = "A"
}
}
};
var order4 = new Order
{
OrderNumber = 4,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 20,
Sku = "B"
},
new OrderLine
{
Quantity = 10,
Sku = "A"
}
}
};
var order5 = new Order
{
OrderNumber = 5,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 30,
Sku = "C"
}
}
};
var order6 = new Order
{
OrderNumber = 6,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 40,
Sku = "C"
}
}
};
var order7 = new Order
{
OrderNumber = 7,
Lines = new List<OrderLine>
{
new OrderLine
{
Quantity = 30,
Sku = "C"
}
}
};
var orderList = new List<Order>(new[] {order1, order2, order3, order4, order5, order6, order7});
Grouping the orders:
var identicalOrders = orderList.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => new
{
Count = g.Count(),
OrderItems = g.Key.Lines,
OrderNumbers = orderList.Where(x => x.Equals(g.Key))
.Select(x => x.OrderNumber)
.ToList()
})
.ToList();
Output:
Upvotes: 9
Reputation: 56172
To be able to group by Lines
you need to implement IEqualityComparer<List<OrderLine>>
and pass it into GroupBy
method: var groups = orders.GroupBy(o => o.Lines, o => o, new OrderLineEqualityComparer());
internal class OrderLineEqualityComparer : IEqualityComparer<List<OrderLine>>
{
public bool Equals(List<OrderLine> x, List<OrderLine> y)
{
throw new NotImplementedException();
}
public int GetHashCode(List<OrderLine> obj)
{
throw new NotImplementedException();
}
}
Upvotes: 2