Reputation: 1145
Using C# I am trying to create a list with multiple elements adding new items if the main key is not already in the list, otherwise updating the item if it does. I've found quite a bit about each part, but am struggling with combining into a workable solution what I have found so far.
Below is commented code which compiles.
Problem: Solution is adding all items as new items even if the key (ReceiptID) is already present in the list so there is something wrong with how I'm doing my checking.
Maybe problem: (Edit: not a problem as it works as expected) Because I haven't been able to test the update if exist part, I don't know if I have that right.
Any guidance is appreciated.
Edit: (Note: as per WhoIsRich comment, this could have been done using a Dictionary instead of a list. That would probably be a better and more efficient solution. Thanks WhoIsRich).
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
IList<Receipts> receiptList = new List<Receipts>()
// The real app reads through a temporary table in the database
// to pick up the line items of a sale. The objective is to combine those line
// items into a summary list called receipts based on the receipt number. For this
// runnable example, I add some data to the receipt list here.
{
new Receipts() { ReceiptID = 1, TotalPrice = 10, TotalCost = 5, Profit = 5, EmployeeID = 1 },
new Receipts() { ReceiptID = 2, TotalPrice = 15, TotalCost = 6, Profit = 9, EmployeeID = 1 },
new Receipts() { ReceiptID = 3, TotalPrice = 20, TotalCost = 7, Profit = 13, EmployeeID = 1 },
new Receipts() { ReceiptID = 4, TotalPrice = 25, TotalCost = 10.50M, Profit = 14.50M, EmployeeID = 1 },
};
// some dummy data to update list item with. Note: if receiptID is the same as one already in the list, employeeID
// will also be the same. (It's always the same employee that completes a whole transaction).
int[] receiptID = { 3, 4, 5, 5, 6, 7, 7, 8 };
decimal[] totalPrice = { 5, 6, 7, 8, 9, 10, 11, 12 };
decimal[] totolCost = { 2, 2.5M, 3, 3.5M, 4, 4.5M, 5, 5.5M };
decimal[] profit = { 3, 3.5M, 4, 4.5M, 5, 5.5M, 6, 6.5M };
int[] employeeID = { 1, 1, 1, 1, 2, 1, 1, 2 };
// This for loop represents the while loop reading the database table
for (int i = 0; i < 8; i++)
{
// first, check to see if the receiptID is already in the list. This is the
// part I am having trouble with.
Receipts r = new Receipts();
//EDIT:
//if (receiptList.Contains(new Receipts { ReceiptID = receiptID[i] })) <== original in question
//if (receiptList.Any(rc => rc.ReceiptID == receiptID[i])) <== Keyur PATEL's comment to question - Works
//if (receiptList.Any(o => o.ReceiptID == receiptID[i])) <== kurakura88 answer - Works
if (receiptList.Any(receipt => receipt.ReceiptID == receiptID[i])) // <== Eric Wu - Works
// END EDIT
{
// The code never enters here! <<=== This is what I need help with fixing
var tu = receiptList.Single(x => x.ReceiptID == receiptID[i]);
tu.TotalPrice += totalPrice[i];
tu.TotalCost += totolCost[i];
tu.Profit += profit[i];
// receiptID and employeeID are not updated as they don't change in this if loop.
}
else
{
// This should happen if the receiptID is not in the list, but it's happening
// every time.
r.ReceiptID = receiptID[i];
r.EmployeeID = employeeID[i];
r.TotalPrice = totalPrice[i];
r.TotalCost = totolCost[i];
r.Profit = profit[i];
receiptList.Add(r);
}
}
// Below here just displays the results in a sorted maner which is working ok.
var orderByValue = from s in receiptList
orderby s.ReceiptID
ascending
select s;
foreach (var item in orderByValue)
{
Console.WriteLine("Receipt: {0} Employee: {1} TotalPrice: {2} TotalCost: {3} Profit: {4}", item.ReceiptID.ToString(), item.EmployeeID.ToString(), item.TotalPrice.ToString(), item.TotalCost.ToString(), item.Profit.ToString());
}
Console.ReadLine();
}
}
public class Receipts
{
public int ReceiptID { get; set; }
public int EmployeeID { get; set; }
public decimal TotalPrice { get; set; }
public decimal TotalCost { get; set; }
public decimal Profit { get; set; }
}
}
/*
The output I am getting is (note each sale line is added to list):
Receipt: 1 Employee: 1 TotalPrice: 10 TotalCost: 5 Profit: 5
Receipt: 2 Employee: 1 TotalPrice: 15 TotalCost: 6 Profit: 9
Receipt: 3 Employee: 1 TotalPrice: 20 TotalCost: 7 Profit: 13
Receipt: 3 Employee: 1 TotalPrice: 5 TotalCost: 2 Profit: 3
Receipt: 4 Employee: 1 TotalPrice: 25 TotalCost: 10.50 Profit: 14.50
Receipt: 4 Employee: 1 TotalPrice: 6 TotalCost: 2.5 Profit: 3.5
Receipt: 5 Employee: 1 TotalPrice: 7 TotalCost: 3 Profit: 4
Receipt: 5 Employee: 1 TotalPrice: 8 TotalCost: 3.5 Profit: 4.5
Receipt: 6 Employee: 2 TotalPrice: 9 TotalCost: 4 Profit: 5
Receipt: 7 Employee: 1 TotalPrice: 10 TotalCost: 4.5 Profit: 5.5
Receipt: 7 Employee: 1 TotalPrice: 11 TotalCost: 5 Profit: 6
Receipt: 8 Employee: 2 TotalPrice: 12 TotalCost: 5.5 Profit: 6.5
What want to get is (same receiptID's should have values added together to one item in list):
Receipt: 1 Employee: 1 TotalPrice: 10 TotalCost: 5 Profit: 5
Receipt: 2 Employee: 1 TotalPrice: 15 TotalCost: 6 Profit: 9
Receipt: 3 Employee: 1 TotalPrice: 25 TotalCost: 9 Profit: 16
Receipt: 4 Employee: 1 TotalPrice: 31 TotalCost: 13 Profit: 18
Receipt: 5 Employee: 1 TotalPrice: 15 TotalCost: 6.5 Profit: 8.5
Receipt: 6 Employee: 2 TotalPrice: 9 TotalCost: 4 Profit: 5
Receipt: 7 Employee: 1 TotalPrice: 21 TotalCost: 9.5 Profit: 11.5
Receipt: 8 Employee: 2 TotalPrice: 12 TotalCost: 5.5 Profit: 6.5
*/
EDIT: After correcting the if statement, now get the following result which is correct:
Receipt: 1 Employee: 1 TotalPrice: 10 TotalCost: 5 Profit: 5
Receipt: 2 Employee: 1 TotalPrice: 15 TotalCost: 6 Profit: 9
Receipt: 3 Employee: 1 TotalPrice: 25 TotalCost: 9 Profit: 16
Receipt: 4 Employee: 1 TotalPrice: 31 TotalCost: 13.00 Profit: 18.00
Receipt: 5 Employee: 1 TotalPrice: 15 TotalCost: 6.5 Profit: 8.5
Receipt: 6 Employee: 2 TotalPrice: 9 TotalCost: 4 Profit: 5
Receipt: 7 Employee: 1 TotalPrice: 21 TotalCost: 9.5 Profit: 11.5
Receipt: 8 Employee: 2 TotalPrice: 12 TotalCost: 5.5 Profit: 6.5
Upvotes: 2
Views: 6243
Reputation: 917
The answer is that two objects of the same type may not always be equal, even if they have the same properties.
Thus,
receiptList.Contains(new Receipts { ReceiptID = receiptID[i] })
may never be true.
If you really want to check it in the list, and ReceiptID
is the ID
with which you'll check, then do
receiptList.Any(receipt=>receipt.ReceiptID == receiptID[i])
Any()
will check for elements within the list and return true
if any are found with the conditional provided.
UPDATE
A new data type was introduced starting in C# 8, record
.
According to the docs,
Records are distinct from classes in that record types use value-based equality. Two variables of a record type are equal if the record type definitions are identical, and if for every field, the values in both records are equal`.
So, in your case, if all properties under the Receipt
class are equatable, it can be converted to a record
type. Then, .Contains
will work as you initially expected, since all the necessary Equals
overrides are automatically created by the compiler.
A record is a reference type and follows value-based equality semantics. To enforce value semantics, the compiler generates several methods for your record type:
- An override of
Object.Equals(Object)
.- A virtual
Equals
method whose parameter is the record type.- An override of
Object.GetHashCode()
.- Methods for operator
==
and operator!=
.- Record types implement
System.IEquatable<T>
.
Upvotes: 5