Split List to Categories using Linq - c#

I have class that is used to get and send data to database. Data example:
Id
Amount
ProductName
RecordDateTime
1
2
Fresh Apples
23/2/2021
2
3
Sweet Bananas
13/6/2021
3
1
Yellow Bananas
12/7/2021
4
7
Green Apples
31/5/2021
5
9
Juicy Apples
12/9/2021
6
4
Young Potato's
5/2/2021
7
5
Orange Carrots
4/6/2021
Class:
public class LogModel
{
public int Id { get; set; }
public int Amount { get; set; }
public string ProductName { get; set; }
public DateTime RecordDateTime { get; set; }
}
Then I have another class that is used for user customization so that the user can build their own category structure. This is treeview with user-built structure like:
- Categories
- Fruits
- Apples | Bananas
- Vegetables
- Potato's | Carrots
Here is the class:
public class CategoryModel : BaseViewModel
{
private ObservableCollection<CategoryModel> categoryItems;
public ObservableCollection<CategoryModel> CategoryItems
{
get => this.categoryItems;
set
{
this.categoryItems = value;
this.OnPropertyChanged();
}
}
private string itemName;
public string ItemName
{
get => this.itemName;
set
{
this.itemName = value;
this.OnPropertyChanged();
}
}
}
I need to create a new list that will assign each record from the database to own category based on string.Contains after splitting by |. I have managed to get method with Linq to sort only needed data to work with from BD. How to do actual assign of data to each category? No need to match my structure. Basic linq example would be enough.
public List<LogModel> GetCategorySplit(DateTime startDate, DateTime endDate)
{
using (var db = new SQLDBContext.SQLDBContext())
{
List<LogModel> result = db.LogModel
.Where(w => w.RecordDateTime >= startDate.Date && w.RecordDateTime.Date <= endDate.Date && w.Amount != 0)
.Select(
s => new LogModel
{
ProductName = s.ProductName,
Amount = s.Amount,
})
.ToList();
// I have tried some foreach loop after that, but it is not so clear in my head how this process
// should go overall
//foreach (var item in result)
//{
// if (ExtensionMethods.StringContains(item.ProductName, value))
// {
// }
//}
return result;
}
}
Expected Output:
Fruits 22
Vegetables 9
P.S. Please comment if something is unclear, I am quite new here.

In order to be able to test my solution I've used in-memory data structures.
var dataSource = new List<LogModel>
{
new LogModel { Id = 1, Amount = 2, ProductName = "Fresh Apples", RecordDateTime = new DateTime(2021, 02, 23)},
new LogModel { Id = 2, Amount = 3, ProductName = "Sweet Bananas", RecordDateTime = new DateTime(2021, 06, 13)},
new LogModel { Id = 3, Amount = 1, ProductName = "Yellow Bananas", RecordDateTime = new DateTime(2021, 07, 12)},
new LogModel { Id = 4, Amount = 7, ProductName = "Green Apples", RecordDateTime = new DateTime(2021, 05, 31)},
new LogModel { Id = 5, Amount = 9, ProductName = "Juicy Apples", RecordDateTime = new DateTime(2021, 09, 12)},
new LogModel { Id = 6, Amount = 4, ProductName = "Young Potato's", RecordDateTime = new DateTime(2021, 02, 05)},
new LogModel { Id = 7, Amount = 5, ProductName = "Orange Carrots", RecordDateTime = new DateTime(2021, 06, 04)}
};
var categories = new List<CategoryModel>
{
new CategoryModel
{
ItemName = "Fruits",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel {ItemName = "Apples"},
new CategoryModel {ItemName = "Bananas"}
}
},
new CategoryModel
{
ItemName = "Vegetables",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel {ItemName = "Potato's"},
new CategoryModel {ItemName = "Carrots"}
}
}
};
The grouping logic can be written like this:
var topLevelQuantities = new Dictionary<string, int>();
foreach (var topLevelCategory in categories)
{
var filters = topLevelCategory.CategoryItems.Select(leafLevel => leafLevel.ItemName);
var count = dataSource.Where(log => filters.Any(filter => log.ProductName.Contains(filter)))
.Sum(log => log.Amount);
topLevelQuantities.Add(topLevelCategory.ItemName, count);
}
Here I iterate through the top level categories foreach (var topLevelCategory in categories)
I assumed the the graph depth is 2 (so there are top level and leaf level entities)
Then I gather the leafs' ItemNames into the filters
I perform a filtering based on the filters dataSource.Where( ... filters.Any(...))
And finally I calculate the Sum of the filtered LogModels' Amount
In order to be able to pass the accumulated data to another function/layer/whatever I've used a Dictionary<string, int>
I've used the following command to examine the result:
foreach (var (categoryName, categoryQuantity) in topLevelQuantities.Select(item => (item.Key, item.Value)))
{
Console.WriteLine($"{categoryName}: {categoryQuantity}");
}
NOTES
Please bear in mind that this solution was designed against in-memory data. So, after data has been fetched from the database. (If you want to perform this on the database level then that requires another type of solution.)
Please also bear in mind that this solution requires multiple iterations over the dataSource. So, if the dataSource is very large (or there are lots of top level categories) than the solution might not perform well.
UPDATE: When the depth of the category hierarchy is 3.
If we can assume that there can be only 1 top-level entity then we should not need to change too much:
var rootCategory = new CategoryModel
{
ItemName = "Categories",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel
{
ItemName = "Fruits",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel {ItemName = "Apples"},
new CategoryModel {ItemName = "Bananas"}
}
},
new CategoryModel
{
ItemName = "Vegetables",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel {ItemName = "Potato's"},
new CategoryModel {ItemName = "Carrots"}
}
}
}
};
var midLevelQuantities = new Dictionary<string, int>();
foreach (var midLevelCategory in rootCategory.CategoryItems)
{
...
}
If there can be multiple top level categories:
var categories = new List<CategoryModel>
{
new CategoryModel
{
ItemName = "Categories",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel
{
ItemName = "Fruits",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel {ItemName = "Apples"},
new CategoryModel {ItemName = "Bananas"}
}
},
new CategoryModel
{
ItemName = "Vegetables",
CategoryItems = new ObservableCollection<CategoryModel>
{
new CategoryModel {ItemName = "Potato's"},
new CategoryModel {ItemName = "Carrots"}
}
}
}
}
};
then we need to use recursive graph traversal.
I've introduced the following helper class to store the calculations' result:
public class Report
{
public string Name { get; }
public string Parent { get; }
public double Quantity { get; }
public Report(string name, string parent, double quantity)
{
Name = name;
Parent = parent;
Quantity = quantity;
}
}
The traversal can be implemented like this:
private static List<Report> GetReports(CategoryModel category, string parent, List<Report> summary)
{
if (category.CategoryItems == null || category.CategoryItems.Count == 0)
{
var count = dataSource.Where(log => log.ProductName.Contains(category.ItemName)).Sum(log => log.Amount);
summary.Add(new Report(category.ItemName, parent,count));
return summary;
}
foreach (var subCategory in category.CategoryItems)
{
summary = GetReports(subCategory, category.ItemName, summary);
}
var subTotal = summary.Where(s => s.Parent == category.ItemName).Sum(s => s.Quantity);
summary.Add(new Report(category.ItemName, parent, subTotal));
return summary;
}
In the if block we handle that case when we are at the leaf level
That's where we perform the queries
In the foreach block we iterate through all of its children and we are calling the same function recursively
After the foreach loop we calculate the current category's subTotal by aggregating its children's Quantities
Please note: This design assumes that the category names are unique.
To display the results we need yet another recursive function:
private static void DisplayResult(int depth, IEnumerable<CategoryModel> categories, List<Report> report)
{
foreach (var category in categories)
{
var indentation = new string('\t', depth);
var data =report.Single(r => r.Name == category.ItemName);
Console.WriteLine($"{indentation}{data.Name}: {data.Quantity}");
if (category.CategoryItems == null || category.CategoryItems.Count == 0)
continue;
DisplayResult((byte)(depth +1), category.CategoryItems, report);
}
}
Based on the category's level (depth) we calculate the indentation
We lookup the corresponding report based on the ItemName property of the category
We print out the report
If the current category does not have subcategories then we move to the next item
Otherwise we call the same function for the subcategories recursively
Let's put all things together
var report = new List<Report>();
foreach (var category in categories)
{
var subReport = GetReports(category, "-", new List<Report>());
report.AddRange(subReport);
}
DisplayResult(0, categories, report);
The output will be:
Categories: 31
Fruits: 22
Apples: 18
Bananas: 4
Vegetables: 9
Potato's: 4
Carrots: 5

So you have a displayed sequence of CategoryModels, with ItemNames like "Fruits", "Vegetables", etc.
Every CategoryModel has some sub-CategoryModels in property CategoryItems. In your example, CategoryModel "Fruits" has CategoryItems with ItemNames like "Apples" and "Bananas". "Vegetables has sub category ItemNames like "Potato's" and "Carrots".
You forgot to tell us, can a CategoryItem with ItemName "Apples" also have sub-categories, so you have sub-sub-categories? And can these sub-sub-categories have more sub-sub-sub-categories?
I need to create a new list that will assign each LogModel from the database to a CategoryModel.
How do you decide which LogModels belongs to "Apples". Is it if LogModel.ProductName contains "Apples"?
Apparently you need an extend class LogModel with a method that says: "Yes, a Juicy Apple is an Apple", or "No, a Tasty Pomodoro is not an Apple", neither is a "Granny Smith"
By the way: do you see the flaw in your requirement: why is a "Granny Smith" not an apple, like a "Juicy Apple"?
But let's design for change: if you later want to change how you match a LogModel with a CategoryModel, changes will be small.
Luckily, you already fetched the LogModels that you want to process from the database, so we can work "AsEnumerable", instead of "AsQueryable". This gives us more freedom in the functions that we can use.
public static bool IsMatch(LogModel log, CateGoryModel category)
{
// TODO: compare log with category, and decide whether it is a match
}
Usage will be as if IsMatch is a method of LogModel:
LogModel log = ...
CategoryModel category = ...
bool isMatch = IsMatch(log, category);
Your current requirement seems to be that a log matches a category, if the ProductName of the log contains the ItemName of the category. So if "Juicy Apples" contains "Apples" (not sure if you want case sensitivity)
public static bool IsMatch(LogModel log, CateGoryModel category)
{
// TODO: decide what to do if log == null, category == null.
// TODO: decide what to do if log.ProductName == null or category.ItemName == null
return log.ProductName.Contains(category.ItemName, StringComparison.CurrentCulture);
}
If later you decide that you don't check on names, but for instance on a FruitType, than changes will only have to be in this method, nowhere else.
Why is fruits 22, and vegetables 9?
Well, fruits has apples and bananas, and the sum of all matching apples (according to the method defined above) and all matching bananas is 22. Similarly: vegetables is potato's and carrots and the sum of all matching potato's (4) and matching carrots (5) is 9
So for every Category, we take all SubCategories, and find the LogModels that match. We sum all Amounts of the matching LogModels.
So as a matter of fact, you would like to extend class CategoryModel with a method that takes all fetched LogModels and returns the Amount of items. Something like this:
class CategoryModel
{
...
public int GetAmount(IEnumerable<LogModel> logModels)
{
...
}
}
If you don't want or if you cannot add this method to CategoryModel, you can always create an extension method. For those who are not familiar with extension methods, see Extension Methods Demystified
public static int GetAmount(this CategoryModel category, IEnumerable<LogModel> logModels)
{
// TODO: exception if items null or empty
int amount = logModels.Where(log => IsMatch(log, category)
.Select(log => log.Amount)
.Sum();
// Add all amounts of the subCategories:
if (category.CategoryItems != null && category.CategoryItems.Count != 0)
{
amount += category.CategoryItems
.Select(catgoryItem => categoryItem.GetAmounts(logModels))
.Sum();
}
return amount;
}
Nota bene: this method uses recursion, so it works even if you have sub-sub-sub-... categories.
Usage:
var fetchedLogModels = db.LogModels.Where(...).Select(...).ToList();
IEnumerable<CategoryModel> categories = ...
categoryies are "fruits" and "vegetables" etc.
var result = categories.Select(category => new
{
CategoryName = category.ItemName,
Amount = category.GetAmounts(fetchedLogModels),
});
Well, doesn't that seem like some nice piece of code? Every difficulty is hidden somewhere deep inside and the code is easily changeable and unit testable. If you want to change how you make your Matches, or want to change that GetAmounts is not recursive anymore, or that it becomes a method of CategoryModels: none of the users have to change.

Related

Linq Flatten List of List [duplicate]

This question already has answers here:
Flatten a list which one of its properties is another list of object
(3 answers)
Closed 1 year ago.
Using Linq what will be the best way of making a flattened list with these 2 objects lists
public class DataDto
{
public string StudentID { get; set; }
public List<ScoreDto> Scores { get; set; }
}
public class ScoreDto
{
public string ExamID { get; set; }
public double Mark { get; set; }
}
into a list that will present as
public class FinalDto
{
public string StudentID { get; set; }
public string ExamID { get; set; }
public double Mark { get; set; }
}
With StudentID repetitions for the number of ScoreDtos present for a particular student.
Example data -
var data = new List<DataDto>()
{
new DataDto
{
StudentID = "S1",
Scores = new List<ScoreDto>()
{
new ScoreDto { ExamID = "01", Mark = 5},
new ScoreDto { ExamID = "02", Mark = 15},
new ScoreDto { ExamID = "03", Mark = 25}
}
},
new DataDto
{
StudentID = "S2",
Scores = new List<ScoreDto>()
{
new ScoreDto { ExamID = "01", Mark = 1},
new ScoreDto { ExamID = "02", Mark = 5},
new ScoreDto { ExamID = "03", Mark = 20}
}
}
};
So it produces -
StudentID, ExamID, Mark
S1, 01, 5
S1, 02, 15
S1, 03, 25
S2, 01, 1
S2, 02, 5
S2, 03, 20
You use SelectMany to flatten a list of lists:
var final = data.SelectMany(
student => student.Scores,
(student, score) => new FinalDto {
StudentID = student.StudentID,
ExamID = score.ExamID,
Mark = score.Mark
}
);
Console.WriteLine("StudentID, ExamID, Mark");
foreach (var result in final)
{
Console.WriteLine("{0}, {1}, {2}", result.StudentID, result.ExamID, result.Mark);
}
Alternatively, you can use a different overload of SelectMany along with a nested Select projection:
var final = data.SelectMany(
student => student.Scores.Select(
score => new FinalDto {
StudentID = student.StudentID,
ExamID = score.ExamID,
Mark = score.Mark
}
)
);
You can also use query syntax:
var final = (
from student in data
from score in student.Scores
select new FinalDto {
StudentID = student.StudentID,
ExamID = score.ExamID,
Mark = score.Mark
}
);
Note that this is just translated to the second form shown above.
There's no "best" here. Forms two and three literally compile to the same exact code. Form one is just a slight variation. There is no impact (speed, memory) for choosing one over the other. Pick the one that is visually appealing to you. Personally I don't write query syntax (the last form) and I always forget the first overload exists so I end up using the second one.
var result = (
from item in data
from score in item.Scores
select new FinalDto {
StudentID = item.StudentID,
ExamID = score.ExamID,
Mark = score.Mark
}).ToList();
You have two tables with a one-to-many relation: every item of table A has zero or more items from table B, and every item from table B belongs to exactly one item of table A, namely the one item that the foreign key refers to.
Whenever you have this, and you want "items with their sub-items", like Schools with their Students, Customers with their Orders and Authors with their Books, consider to use one of the overloads of Queryable.GroupJoin
If you don't want the items with their sub-items, but a flat table,consider to use Queryable.Join
Apparently you already have fetched the "Students with their zero or more Scores", and you want a flat result. In that case you use SelectMany
IEnumerable<DataDto> studentsWithTheirScores = ...
var result = studentsWithTheirScores.SelectMany(
// parameter collectionSelector: where are the sub-items stored?
student => student.Scores,
// parameter resultSelector: take each student, and each one of his Scores
// to make one new
(student, score) => new FinalDto
{
StudentId = student.StudentId,
ExamId = score.ExamId,
Mark = score.Mark,
});

C#: find objects within array with same id and sum their quantity values. Add those to the new object

I have an array or list of objects returned from the Database. Let's take this as an example:
this is the class:
public class products
{
public string name { get; set; }
public int quantity { get; set; }
public long id{ get; set; }
}
List<Product> prod = new List<Product>()
prod = (call to the DB to get data back...)
array is returned with object of products
What I need is to loop through this array and add up the quantities for the same ids. Meaning, add up 2+7 for id 3 and add that data to another object so that new object would have something like: total: 9, id: 3
then same again for id 5, total: 7, id: 5
and so on.
I am at a loss of the right way to do this. I was looking at Linq but the only way I used it is by providing specific values. Please help me with this
`
foreach (var p in prod){ Now what do i do?}
`
The easiest way is with GroupBy and Sum (Both use System.Linq):
List<Product> products = new List<Product>()
{
new Product(){Id = 1, Cost = 20.0M },
new Product(){Id = 1, Cost = 30.0M },
new Product(){Id = 2, Cost = 20.0M },
new Product(){Id = 3, Cost = 20.0M },
new Product(){Id = 3, Cost = 5.0M }
};
products.GroupBy(g => g.Id).ToList().ForEach(e =>
{
Console.WriteLine($"Total: {
e.Sum(s => s.Cost)} for {e.Key}");
});
Edit
With the new information provided: You can do this to a concrete class:
public class Product
{
public string name { get; set; }
public int quantity { get; set; }
public long id{ get; set; }
}
List<Product> products = new List<Product>();
var products = (call to the DB to get data back...).GroupBy(g => g.Id).ToList().ForEach(e =>
{
products.Add(new Product()
{
Id = e.Key,
Quantity = e.Sum(s => s.Quantity)
})
});
Per your code snippet, prod is single product of type products.
So, assuming your code which invokes database call returns prod list something like below:
List<products> prod = new List<products>();
prod = _productRepository.GetProductData(prodId);
You can use linq GroupBy (please include System.Linq to use GroupBy linq extension method) to find the total quantity of each product like below:
var anonymousList = prod.GroupBy(p => p.id).Select(g => new {id = g.Key, totalQuantity = g.Sum(p => p.quantity)}).ToList()
The above returns anonymous list of objects where each object contains id and totalQuantity.
If you are interested in dictionary of product id vs totalQuantity, then use something like below:
Dictionary<long, int> dictionary = prod.GroupBy(p => p.id).ToDictionary(k => k.Key, v => v.Sum(p => p.quantity));
UPDATE based on comments discussion:
You can invoke GroupBy on prod without verifying the count. No exception will be thrown.

Grouping Deeply Nested Objects Using LINQ

I have the following classes:
public class Item
{
public int Id { get; set; }
public string Sku { get; set; }
public List<Price> Prices { get; set; }
}
public class Price
{
public int Id { get; set; }
public double Cost { get; set; }
public PriceList List { get; set; }
}
public class PriceList
{
public int Id { get; set; }
public string FileName { get; set; }
public DateTime ImportDateTime { get; set; }
}
Essentially, it's for a list of items [Item] in which each item has a list of prices [List<Price> Prices] (one-to-many), and each price has one price list [PriceList List] (one-to-one).
What I need is a list of all price lists.
It seems like what I need is a group to a "grandchild", basically, grouping by the PriceList's Id (which is under Price, which in turn is under Item in the list).
So, as an example, if I have five price lists, it should return five rows.
I achieved it doing the following long way:
List<PriceList> priceLists = new List<PriceList>();
foreach (Item item in items)
{
foreach (Price price in item.Prices)
{
PriceList list = price.List;
if (!priceLists.Any(x => x.Id == list.Id))
{
priceLists.Add(list);
}
}
}
How can it be achieved using LINQ?
UPDATE:
Here's a basic sample set:
PriceList priceList1 = new PriceList { Id = 1, FileName = "Price List 1.csv" };
PriceList priceList2 = new PriceList { Id = 2, FileName = "Price List 2.csv" };
Price price1 = new Price { Id = 1, Cost = 2.65, List = priceList1 };
Price price2 = new Price { Id = 2, Cost = 14.23, List = priceList2 };
Price price3 = new Price { Id = 3, Cost = 29.01, List = priceList1 };
Price price4 = new Price { Id = 4, Cost = 1, List = priceList2 };
Price price5 = new Price { Id = 5, Cost = 56.12, List = priceList1 };
Item item1 = new Item { Id = 1, Sku = "item1", Prices = new List<Price> { price1, price2 } };
Item item2 = new Item { Id = 2, Sku = "item2", Prices = new List<Price> { price3, price4 } };
Item item3 = new Item { Id = 3, Sku = "item3", Prices = new List<Price> { price5 } };
List<Item> itemsList = new List<Item> { item1, item2, item3 };
Let's start from the bottom upwards:
I have a list of Item (itemsList), which contains three Items.
Each Item has a list of Price.
Each Price has one PriceList.
To clarify the reasoning behind all this: The user imports a spreadsheet of prices (price list) they get periodically from their supplier, prices fluctuate and each price list has different prices for the same items. Hence the reason of having an item with many prices and each price has its price list which was used to upload it (there's more information included in the PriceList class which I omitted to keep it simple).
I hope I'm clear.
This seems to be what you want, using an extension to do Distinct by a lambda expression:
var ans = itemsList.SelectMany(item => item.Prices.Select(price => price.List)).DistinctBy(price => price.Id);
The extension is as follows:
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> src, Func<T, TKey> keyFun) {
var seenKeys = new HashSet<TKey>();
foreach (T e in src)
if (seenKeys.Add(keyFun(e)))
yield return e;
}
If you changed your approach to use a HashSet<Int> to track the seen ids you would probably be a tiny bit more efficient than LINQ.
It looks like you can combine a SelectMany (to get all the Prices lists) with a Select (to get all the associated PriceList properties):
List<PriceList> priceLists = items.SelectMany(i => i.Prices).Select(p => p.List).ToList();
Edit
The above will return all the PriceLists, but since many Items could reference the same PriceList, then you need a way to filter on the PriceList.Id field.
One way to do that would be to override the Equals and GetHashCode property of the PriceList object. If you truly consider two PriceList objects to be equal if they have the same Id, then you could do the following:
public class PriceList
{
public int Id { get; set; }
public string FileName { get; set; }
public DateTime ImportDateTime { get; set; }
public override bool Equals(object obj)
{
var other = obj as PriceList;
return Id == other?.Id;
}
public override int GetHashCode()
{
return Id;
}
}
This allows you to use the Linq method, Distinct on your prices (it will call the Equals method to distinguish between PriceLists in the results:
List<PriceList> priceLists = items
.SelectMany(i => i.Prices)
.Select(i => i.List)
.Distinct()
.ToList();

using linq to merge items together in a collection

I have a list of objects (call them type salesItems) Lets say these items have 50 properties, with Name, price, quantity being 3 of them). I would like to know how to merge the list together combining any salesItems by name using the following logic:
If there are multiple salesOrders that have the same Name:
Combine them into one SalesOrder with the same Name
Set the quantity to the sum of the quantities
Set the price, and all of the other properties using the values of the first
I would like to do with linq. I realize i could use a big for each c# loop instead.
If there are additional items in the list I would like to follow similar logic for those as well.
EX: A salesOrder list with (A,B,C,D,E)
A: Name=ball Price= 2.24 Quantity=1 (other values = bla bla)
B: Name= ball Price = 15.33 Quantity=3 (other values)
c: Name= bat Price = 22.14 Quantity=3 (other values)
D: Name= bat Price= 19.22 Quantity=2 (other values)
E: Name = ball Price=4.32 Quantity=2 (other values)
Result list I want 2 Sales orders in list (A,C) A: Name=ball Price=
2.24 Quantity=6 (other values = bla bla from a's properties) c: Name= bat Price = 22.14 Quantity=5 (other values from c's
properties)
You want linq's .GroupBy method!!!
I've defined your class as:
public class SalesOrder
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
public SalesOrder(string Name, double Price, int Quantity)
{
this.Name = Name;
this.Price = Price;
this.Quantity = Quantity;
}
}
then I have created a list of your orders like this:
List<SalesOrder> Orders = new List<SalesOrder>()
{
new SalesOrder("Ball", 2.24, 1),
new SalesOrder("Ball", 15.33, 3),
new SalesOrder("Bat", 22.14, 3),
new SalesOrder("Bat", 19.22, 2),
new SalesOrder("Ball", 4.32, 2)
};
and grouped them by the name before selecting the values you want for each group into a new instance of the SalesOrder class like this:
List<SalesOrder> Combined_Orders = Orders
.GroupBy (o => o.Name)
.Select (o => new SalesOrder(o.Key, o.Select (x => x.Price).First(), o.Sum(x => x.Quantity)))
.ToList();
UPDATE: In response to OP's comment
As the real SalesOrder will have hundreds of properties, you can avoid typing them all out in the linq query by adding a constructor to the SalesOrder class that accepts the result of the group by as an argument, then do all the work in the constructor. While it doesn't stop you from having to type out all the properties, it does mean that its neatly abstracted away. Also this way it forces/enables you to decide on what to do with each of the properties (first/sum/average).
To do this you will need a second constructor that looks like this:
public SalesOrder(IGrouping<string, SalesOrder> Group)
{
this.Name = Group.Key;
this.Price = Group.First().Price;
this.Quantity = Group.Sum(g => g.Quantity);
// do all other properties here too
}
Then update the group by to look like this (note that only the result of the grouping "g" is passed into the constructor now):
List<SalesOrder> Combined_Orders = Orders
.GroupBy (o => o.Name)
.Select (g => new SalesOrder(g))
.ToList();
Hi You can use the following code,
class SalesItem
{
public string Name { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
}
class SalesOrder
{
public void LoadItems()
{
List<SalesItem> SalesItems = new List<SalesItem>();
SalesItem salesitem = new SalesItem()
{
Name = "Ball",
Price = 12,
Quantity = 1
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Ball",
Price = 36,
Quantity = 3
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Bat",
Price = 50,
Quantity = 1
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Ball",
Price = 84,
Quantity = 7
};
SalesItems.Add(salesitem);
salesitem = new SalesItem()
{
Name = "Bat",
Price = 150,
Quantity = 3
};
SalesItems.Add(salesitem);
GroupOrders(SalesItems);
}
public List<SalesItem> GroupOrders(List<SalesItem> SalesItems)
{
var list = from item in SalesItems
group item by item.Name into orders
select new SalesItem
{
Name = orders.Key,
Price = orders.Sum(X=>X.Price),
Quantity = orders.Sum(X=>X.Quantity)
};
List<SalesItem> resultList = new List<SalesItem>();
foreach (SalesItem saleitem in list)
{
resultList.Add(saleitem);
}
return resultList;
}
}

Mixing in new fields to an object without hard-coding its fields in a LINQ query?

I have a simple LINQ query here:
var Staffs = new[]
{
new { id = 1, name = "Jefferson", age = 42},
new { id = 2, name = "Jacobson", age = 54},
new { id = 3, name = "Zhang", age = 34}
};
var payroll = new[]
{
new { pid = 1, wage = 5000},
new { pid = 2, wage = 6500},
new { pid = 3, wage = 6700}
};
var q = from stf in Staffs
from pay in payroll
where stf.id == pay.pid
select new
{
stfObj = stf,
pay.pid,
pay.wage
};
Here, stfObj would be an object containing the id, name and age fields
Here comes the question:
Is it possible to turn the object into the fields themselves without explicitly hard-coding the field names like this:
select new
{
stf.id,
stf.name,
stf.age,
pay.pid,
pay.wage
};
In this way, there will be no need to change the select new block when I add a new field to Staffs, like Gender for example
Is that possible?
(ok, this looks like the question here... anyway, hoping to get better answers here)
Is this what you want!
select new
{
sId=stfObj.id,
sName=stfObj.name,
sAge=stdObj.age,
pId=pay.pid,
pWage=pay.wage
};
Why not simply embed your object ?
select new {
staff = stf,
pay = pay
};
I do not know, what you need this for. But you could try to use Dictionary<> for this. Say, we have a class:
public class Test
{
public string Name { get; set; }
public string Desc { get; set; }
}
So you can do the following:
List<Test> list = new List<Test>
{
new Test
{
Name = "Test 1",
Desc = "Desc 1"
}
};
var temp = list.Select(t =>
{
Dictionary<string, object> values = new Dictionary<string, object>();
foreach (PropertyInfo pi in t.GetType().GetProperties())
values[pi.Name] = pi.GetValue(t, null);
return values;
})
.FirstOrDefault();
temp.ToList().ForEach(p => Console.WriteLine(string.Format("{0}:\t{1}", p.Key, p.Value)));
So if you add a property to the Test class, say, like this:
public bool Extra { get; set; }
You'll get it in the dictionary automatically. Probably you'll have to work with reflection methods overloads to get exactly what you need...

Categories

Resources