CSharp · Console
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using System.Linq.Expressions;
using Newtonsoft.Json;
using NUnit.Framework; // Throws NUnit.Framework.AssertionException
//https://stackoverflow.com/questions/62976822/how-to-create-merge-item-method-in-c-sharp
public class Items
{
public int ItemNumber { get; set; }
public DateTime ItemDate { get; set; }
public List<ItemsLine> ItemsLines { get; set; } = new List<ItemsLine>();
/// MergeItem appends the items from the sourceItem to the current item
/// </summary>
/// <param name="sourceItem">Item to merge from</param>
public void MergeItem( Items sourceItem)
{
var mergedItems = ItemsLines.FullOuterJoin(sourceItem.ItemsLines, i => i.ItemLineId, i => i.ItemLineId, (i1, i2) =>
{
if (i1 == default)
return i2;
else if (i2 == default)
return i1;
//It's not clear from your question whether the average cost should be weighted by the quantities.
//i1.Cost = (i1.Cost + i2.Cost) / 2.0,
i1.Cost = (i1.Quantity * i1.Cost + i2.Quantity * i2.Cost) / (i1.Quantity + i2.Quantity);
i1.Quantity = i1.Quantity + i2.Quantity;
return i1;
})
.ToList();
ItemsLines.Clear();
ItemsLines.AddRange(mergedItems);
}
}
public class ItemsLine
{
public int ItemLineId { get; set; }
public int Quantity { get; set; }
public double Cost { get; set; }
// public double Total { get { return this.Cost * this.Quantity; } }
public ItemsLine(int itemLineId, int quantity, double cost)
{
this.ItemLineId = itemLineId;
this.Quantity = quantity;
this.Cost = cost;
}
public ItemsLine() { }
}
class TestClass
{
public static void Test()
{
Console.WriteLine("\nTesting code from question:");
MergeItem();
Console.WriteLine("\nTesting with merge required:");
TestTestWithMerge();
}
private static void MergeItem()
{
var item1 = new Items();
item1.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 1,
Cost = 10.33,
Quantity = 4,
});
var item2 = new Items();
item2.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 2,
Cost = 5.22,
Quantity = 1,
});
item2.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 3,
Cost = 6.27,
Quantity = 3,
});
item1.MergeItem(item2);
Console.WriteLine("Grand total after merge = {0}", item1.ItemsLines.Sum(i => i.Cost * i.Quantity));
}
public static void TestTestWithMerge()
{
var item1 = new Items();
// Fixed
item1.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 1,
Cost = 10.33,
Quantity = 4,
});
item1.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 101,
Cost = 1,
Quantity = 1,
});
var item2 = new Items();
item2.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 2,
Cost = 5.22,
Quantity = 1,
});
item2.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 3,
Cost = 6.27,
Quantity = 3,
});
item2.ItemsLines.Add(new ItemsLine()
{
ItemLineId = 101,
Cost = 1,
Quantity = 1,
});
var originalQualtity1 = item1.ItemsLines.Sum(i => i.Quantity);
var originalQualtity2 = item2.ItemsLines.Sum(i => i.Quantity);
item1.MergeItem(item2);
Console.WriteLine("Merged items: ");
Console.WriteLine(JsonConvert.SerializeObject(item1, Formatting.Indented));
Console.WriteLine("Grand total after merge = {0}", item1.ItemsLines.Sum(i => i.Cost * i.Quantity));
Assert.IsTrue(item1.ItemsLines.Count == 4);
Assert.AreEqual(1, item1.ItemsLines.Where(i => i.ItemLineId == 101).Count());
Assert.AreEqual(2, item1.ItemsLines.Where(i => i.ItemLineId == 101).Single().Quantity);
Assert.AreEqual(1.0, item1.ItemsLines.Where(i => i.ItemLineId == 101).Single().Cost);
Assert.AreEqual(originalQualtity1 + originalQualtity2, item1.ItemsLines.Sum(i => i.Quantity));
}
}
// Copied from this answer https://stackoverflow.com/a/43669055/3744182
// To https://stackoverflow.com/questions/5489987/linq-full-outer-join
// By https://stackoverflow.com/users/2557128/netmage
public static class Ext {
public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
return from left in leftItems
join right in rightItems on leftKeySelector(left) equals rightKeySelector(right) into temp
from right in temp.DefaultIfEmpty()
select resultSelector(left, right);
}
public static IEnumerable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
return from right in rightItems
join left in leftItems on rightKeySelector(right) equals leftKeySelector(left) into temp
from left in temp.DefaultIfEmpty()
select resultSelector(left, right);
}
public static IEnumerable<TResult> FullOuterJoinDistinct<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Union(leftItems.RightOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
public static IEnumerable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) {
var hashLK = new HashSet<TKey>(from l in leftItems select leftKeySelector(l));
return rightItems.Where(r => !hashLK.Contains(rightKeySelector(r))).Select(r => resultSelector(default(TLeft),r));
}
public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
this IEnumerable<TLeft> leftItems,
IEnumerable<TRight> rightItems,
Func<TLeft, TKey> leftKeySelector,
Func<TRight, TKey> rightKeySelector,
Func<TLeft, TRight, TResult> resultSelector) where TLeft : class {
return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
}
// IQueryable implementations omitted.
}
// End of copied code
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
public static string GetNetCoreVersion()
{
//https://techblog.dorogin.com/how-to-detect-net-core-version-in-runtime-ecd65ad695be
//
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];
return null;
}
}
#if false
It sounds as though you want to do a [full outer join](https://www.w3schools.com/sql/sql_join_full.asp) of the `ItemsLines` lists, matching on `ItemLineId` and using the projection logic as described in your question. So I grabbed an implementation of full outer join from [this answer](https://stackoverflow.com/a/43669055/3744182) to [LINQ - Full Outer Join](https://stackoverflow.com/q/5489987/3744182) by [NetMage](https://stackoverflow.com/users/2557128/netmage) and it worked perfectly, see https://dotnetfiddle.net/AuDSPI. Does that answer your question?
#endif