How can I get the nested rows ordered by nesting level? - c#

I have the following table with attributes: Id, ParentId, Name etc.
The table contains a nesting level as: Objects contain Elements that contain Components).
Id | Parent | Name
---------------------------------------
1 | NULL | Name (this is object 1)
2 | 1 | Name (this is element 1)
3 | 2 | Name (this is component 1)
4 | 2 | Name (this is component 2)
5 | 2 | Name (this is component 3)
6 | 1 | Name (this is element 2)
7 | 6 | Name (this is component 4)
8 | 1 | Name (this is element 3)
9 | NULL | Name (this is object 2)
10 | 9 | Name (this is element 4)
11 | 10 | Name (this is component 5)
12 | 10 | Name (this is component 6)
I need a single LINQ query that handles this result as in the presented table.
I would not like to use nested for loops to get the objects, then for each object to get its elements, then for each element to get its components - the same is for recursion on the table and I do not want this.

Not appropriate for one linq statement. Use a recursive method :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication7
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Parent", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Rows.Add(new object[] { 1, null, "Name (this is object 1)" });
dt.Rows.Add(new object[] { 2, 1, "Name (this is element 1)" });
dt.Rows.Add(new object[] { 3, 2, "Name (this is component 1)" });
dt.Rows.Add(new object[] { 4, 2, "Name (this is component 2)" });
dt.Rows.Add(new object[] { 5, 2, "Name (this is component 3)" });
dt.Rows.Add(new object[] { 6, 1, "Name (this is element 2)" });
dt.Rows.Add(new object[] { 7, 6, "Name (this is component 4)"});
dt.Rows.Add(new object[] { 8, 1, "Name (this is element 3)" });
dt.Rows.Add(new object[] { 9, null, "Name (this is object 2)" });
dt.Rows.Add(new object[] { 10, 9, "Name (this is element 4)" });
dt.Rows.Add(new object[] { 11, 10, "Name (this is component 5)" });
dt.Rows.Add(new object[] { 12, 10, "Name (this is component 6)" });
Node.RecursiveFill(dt, null, Node.root);
}
}
public class Node
{
public static Node root = new Node();
public List<Node> children { get; set; }
public int id { get; set; }
public string name { get; set; }
public static void RecursiveFill(DataTable dt, int? parentId, Node parentNode)
{
foreach (DataRow row in dt.AsEnumerable().Where(x => x.Field<int?>("Parent") == parentId))
{
Node newChild = new Node();
if (parentNode.children == null) parentNode.children = new List<Node>();
parentNode.children.Add(newChild);
newChild.id = row.Field<int>("Id");
newChild.name = row.Field<string>("Name");
RecursiveFill(dt, newChild.id, newChild);
}
}
}
}

Related

c# LINQ query from master and masterdetail table

I have two tables.
One is Master
Id
Date
1
2022-03-12
2
2022-02-14
3
2021-10-15
4
2021-04-09
5
2020-06-24
Another one is Detail
Id
MasterId
Name
Quantity
1
1
item1
25
2
1
item2
30
3
1
item3
20
4
2
item1
25
5
2
item2
20
6
3
item1
35
7
4
item4
25
8
5
item1
25
9
5
item3
29
From above two table I need a query which will give me 3rd table
Year
item1
item2
item3
item4
2020
25
0
29
0
2021
35
0
0
25
2022
50
50
20
0
You want a pivot table
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication18
{
class Program
{
public static void Main(String[] args)
{
DataTable masterTable = new DataTable("Master");
masterTable.Columns.Add("Id", typeof(int));
masterTable.Columns.Add("Date", typeof(DateTime));
masterTable.Rows.Add(new object[] { 1, DateTime.Parse("2022-03-12") });
masterTable.Rows.Add(new object[] { 2, DateTime.Parse("2022-02-14") });
masterTable.Rows.Add(new object[] { 3, DateTime.Parse("2021-10-15") });
masterTable.Rows.Add(new object[] { 4, DateTime.Parse("2021-04-09") });
masterTable.Rows.Add(new object[] { 5, DateTime.Parse("2020-06-24") });
DataTable detailTable = new DataTable("Detail");
detailTable.Columns.Add("Id", typeof(int));
detailTable.Columns.Add("IMasterId", typeof(int));
detailTable.Columns.Add("Name", typeof(string));
detailTable.Columns.Add("Quantity", typeof(int));
detailTable.Rows.Add(new object[] { 1, 1, "item1", 25 });
detailTable.Rows.Add(new object[] { 2, 1, "item2", 30 });
detailTable.Rows.Add(new object[] { 3, 1, "item3", 20 });
detailTable.Rows.Add(new object[] { 4, 2, "item1", 25 });
detailTable.Rows.Add(new object[] { 5, 2, "item2", 20 });
detailTable.Rows.Add(new object[] { 6, 3, "item1", 35 });
detailTable.Rows.Add(new object[] { 7, 4, "item4", 25 });
detailTable.Rows.Add(new object[] { 8, 5, "item1", 25 });
detailTable.Rows.Add(new object[] { 9, 5, "item3", 29 });
string[] items = detailTable.AsEnumerable().Select(x => x.Field<string>("Name")).OrderBy(x => x).Distinct().ToArray();
DataTable pivot = new DataTable("Pivot");
pivot.Columns.Add("Year", typeof(int));
foreach (string item in items)
{
pivot.Columns.Add(item, typeof(int));
}
var joinTable = (from m in masterTable.AsEnumerable().OrderBy(x => x.Field<DateTime>("Date"))
join d in detailTable.AsEnumerable() on m.Field<int>("Id") equals d.Field<int>("Id")
select new {id = m.Field<int>("Id"), year = m.Field<DateTime>("Date").Year, d = d}
).GroupBy(x => x.year).ToList();
foreach (var date in joinTable)
{
DataRow row = pivot.Rows.Add();
row["Year"] = date.Key;
var names = date.GroupBy(x => x.d.Field<string>("Name")).Select(x => new {name = x.Key, quant = x.Sum(y => y.d.Field<int>("Quantity"))});
foreach (var name in names)
{
row[name.name] = name.quant;
}
}
}
}
}

Merging Duplicate entries in a C# List [duplicate]

I have the following C# models:
public class RawData
{
public int questionnaireId { get; set; }
public int coachNodeId { get; set; }
public int questionnaireNumber { get; set; }
public float score { get; set; }
}
public class AveragedData
{
public int coachNodeId { get; set; }
public int questionnaireNumber { get; set; }
public float averageScore { get; set; }
}
I have an API endpoint which is returning data from a database, mapped as List<RawData>. The values are like this:
questionnaireId | coachNodeId | questionnaireNumber | score
1 | 30 | 1 | 2
2 | 40 | 1 | 3
3 | 30 | 2 | 1
4 | 30 | 3 | 4
5 | 40 | 2 | 5
6 | 40 | 1 | 5
7 | 30 | 1 | 1
8 | 30 | 1 | 2
9 | 40 | 1 | 2
10 | 30 | 2 | 4
What I need to do now, in a LINQ query, is to average out the score values grouped by coachNodeId and questionnaireNumber and return a list of type AveragedData.
The values returned by averaging and grouping the example data above, should be:
coachNodeId | questionnaireNumber | averageScore
30 | 1 | 1.66666666 (calculated by: (2 + 1 + 2) / 3))
30 | 2 | 2.5 (calculated by: (1 + 4) / 2))
30 | 3 | 4 (calculated by: (4 / 1))
40 | 1 | 3.33333333 (calculated by: (3 + 5 + 2) / 3))
40 | 2 | 5 (calculated by: (5 / 1))
I'm not experienced with LINQ so am struggling to put together a query that groups by both coachNodeId and questionnaireNumber and averages the score, returning an object of type List<AveragedData>. Could anyone suggest how to accomplish this?
Many thanks.
assuming you have a List<RawData> called list, you are wanting:
var results = list.GroupBy(x => new
{
questionnaire = x.questionnaireId,
coach = x.coachNodeId
})
.Select(x => new AveragedData
{
coachNodeId = x.Key.coach,
questionnaireNumber = x.Key.questionnaire,
averageScore = x.Average(xx => xx.score)
})
.ToList();
Do the grouping, then use a Select to project the data to your type, using LINQ's Average as well.
Try following :
DataTable dt = new DataTable();
dt.Columns.Add("questionnaireId", typeof(int));
dt.Columns.Add("coachNodeId", typeof(int));
dt.Columns.Add("questionnaireNumber", typeof(int));
dt.Columns .Add("score", typeof(int));
dt.Rows.Add(new object[] {1,30, 1, 2});
dt.Rows.Add(new object[] {2,40, 1, 3});
dt.Rows.Add(new object[] {3,30, 2, 1});
dt.Rows.Add(new object[] {4,30, 3, 4});
dt.Rows.Add(new object[] {5,40, 2, 5});
dt.Rows.Add(new object[] {6,40, 1, 5});
dt.Rows.Add(new object[] {7,30, 1, 1});
dt.Rows.Add(new object[] {8,30, 1, 2});
dt.Rows.Add(new object[] {9,40, 1, 2});
dt.Rows.Add(new object[] {10,30, 2, 4});
var averages = dt.AsEnumerable()
.GroupBy(x => new { coachNodeId = x.Field<int>("coachNodeId"), questionnaireNumber = x.Field<int>("questionnaireNumber") })
.Select(x => new { coachNodeId = x.Key.coachNodeId, questionnaireNumber = x.Key.questionnaireNumber, average = x.Average(y => y.Field<int>("score")) })
.ToList();

Querying a many-to-many table by grouping

I have a many-to-many relationship table that I am trying to do a lookup on like a dictionary.
Item
|---------------------|------------------|-----------------|
| Id | Name | Desc |
|---------------------|------------------|-----------------|
| 1 | One | First Item |
| 2 | Two | Second Item |
| 3 | Three | Third Item |
| 4 | Four | Fourth Item |
| 5 | Five | Fifth Item |
|---------------------|------------------|-----------------|
Collection
|---------------------|------------------|
| Id | Name |
|---------------------|------------------|
| 1 | First Collection |
| 2 | Second Collecton |
|---------------------|------------------|
Inventory
|---------------------|------------------|--------------|
| CollectionId | ItemId | Amount |
|---------------------|------------------|--------------|
| 1 | 1 | 14 |
| 1 | 2 | 4 |
| 1 | 5 | 4 |
| 2 | 1 | 2 |
| 2 | 5 | 9 |
|---------------------|------------------|--------------|
I am trying to design a query to get all CollectionIds where some the CollectionId has a relation to every ItemId in some input set?
It am struggling to find words to describe my intention, but the pseudo code would look like this
SearchFromIncludes(HashSet<Item> list)
{
Dictionary<HashSet<Item>, ItemContainer> lookupTable = MakeTable();
List<ItemContainer> matches = [];
for (key in getKeys(lookupTable))
{
// if all items in the item list exist in the lookup key, add it for return
if (AllItemsExist(key, list))
{
matches.Add(lookupTable.getValue(key));
}
return matches;
}
}
So if MakeTable creates the relationship above as a dictionary
{
[1, 2, 3]: 1,
[1, 5]: 2
}
SearchFromIncludes([1, 5]) would return
[1]
SearchFromIncludes([1, 3, 5]) would return
[1, 2]
Is there a SQL strategy for querying information from a many-to-many table in this way, where you want to get an item from a relation where its related item match an input set exactly? I am thinking GROUP BY may be helpful here but I have not gotten it to work successfully
Ultimately I will be doing this with Entity Framework Core/LINQ, but I would also like to know how this would look as SQL
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable itemTable = new DataTable();
itemTable.Columns.Add("Id", typeof(int));
itemTable.Columns.Add("Name", typeof(string));
itemTable.Columns.Add("Desc", typeof(string));
itemTable.Rows.Add(new object[] { 1, "One", "First Item" });
itemTable.Rows.Add(new object[] { 2, "Two", "Second Item" });
itemTable.Rows.Add(new object[] { 3, "Three", "Third Item" });
itemTable.Rows.Add(new object[] { 4, "Four", "Fourth Item" });
itemTable.Rows.Add(new object[] { 5, "Five", "Fifth Item" });
DataTable collectionTable = new DataTable();
collectionTable.Columns.Add("Id", typeof(int));
collectionTable.Columns.Add("Name", typeof(string));
collectionTable.Rows.Add(new object[] { 1, "First Collection" });
collectionTable.Rows.Add(new object[] { 2, "Second Collection" });
DataTable inventoryTable = new DataTable();
inventoryTable.Columns.Add("CollectionId", typeof(int));
inventoryTable.Columns.Add("ItemId", typeof(int));
inventoryTable.Columns.Add("Amount", typeof(int));
inventoryTable.Rows.Add(new object[] { 1, 1, 14 });
inventoryTable.Rows.Add(new object[] { 1, 2, 4 });
inventoryTable.Rows.Add(new object[] { 1, 5, 4 });
inventoryTable.Rows.Add(new object[] { 2, 1, 2 });
inventoryTable.Rows.Add(new object[] { 2, 5, 9 });
Dictionary<int, List<object>> dict = (from inTable in inventoryTable.AsEnumerable()
join cTable in collectionTable.AsEnumerable() on inTable.Field<int>("CollectionId") equals cTable.Field<int>("Id")
join iTable in itemTable.AsEnumerable() on inTable.Field<int>("ItemId") equals iTable.Field<int>("Id")
select new { inTable = inTable, cTable = cTable, iTable = iTable }
).ToList()
.GroupBy(x => x.cTable.Field<int>("Id"), y => new { itemTableRow = y.iTable, amount = y.inTable.Field<int>("Amount") })
.ToDictionary(x => x.Key, y => y.ToList<object>());
}
}
}

How to select records with MAX Id that group by multiple columns in LINQ to SQL

I need to select the last record of particular columns. I have the following records
WarehouseId | ItemId | SubItemId | DeliveryGroupId | Other Columns
1 | 1 | 1 | 1 | ...
1 | 1 | 1 | 2 | ...
1 | 1 | 1 | 3 | ...
1 | 1 | 2 | 1 | ...
1 | 1 | 2 | 2 | ...
1 | 2 | 1 | 1 | ...
Then I only want to select the MAX(DeliveryGroupId) for each WarehouseId | ItemId | SubItemId. The result should be:
WarehouseId | ItemId | SubItemId | DeliveryGroupId | Other Columns
1 | 1 | 1 | 3 | ...
1 | 1 | 2 | 2 | ...
1 | 2 | 1 | 1 | ...
In SQL, it is very simple to do:
SELECT *
FROM [dbo].[tblOrderDeliveryGroup] t1
WHERE [DeliveryGroupId] IN
(
SELECT MAX([DeliveryGroupId])
FROM [dbo].[tblOrderDeliveryGroup] t2
WHERE (t1.[WarehouseId] = t2.[WarehouseId]) AND (t1.[ItemId] = t2.[ItemId]) AND (t1.[SubItemId] = t2.[SubItemId])
GROUP BY [WarehouseId], [ItemId], [SubItemId]
);
The question is, how do I translate that SQL statement into LINQ-to-SQL?
Thanks
UPDATE
So far, this is my solution. It is very ugly and surely not efficient.
var vLastRecs = (from rec in tblOrderDeliveryGroups.AsNoTracking()
group rec by new { rec.WarehouseId, rec.ItemId, rec.SubItemId } into grec
select new
{
grec.Key.WarehouseId,
grec.Key.ItemId,
grec.Key.SubItemId,
DeliveryGroupId = grec.Max(rec => rec.DeliveryGroupId)
});
return (from rec in tblOrderDeliveryGroups.AsNoTracking()
where vLastRecs.Any(lrec => (rec.WarehouseId == lrec.WarehouseId) && (rec.ItemId == lrec.ItemId) && (rec.SubItemId == lrec.SubItemId) && (rec.DeliveryGroupId == lrec.DeliveryGroupId))
select rec).ToList();
Is it possible to improve it?
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("WarehouseId", typeof(int));
dt.Columns.Add("ItemId", typeof(int));
dt.Columns.Add("SubItemId", typeof(int));
dt.Columns.Add("DeliveryGroupId", typeof(int));
dt.Rows.Add(new object[] {1,1,1,1});
dt.Rows.Add(new object[] {1,1,1,2});
dt.Rows.Add(new object[] {1,1,1,3});
dt.Rows.Add(new object[] {1,1,2,1});
dt.Rows.Add(new object[] {1,1,2,2});
dt.Rows.Add(new object[] {1,2,1,1});
DataTable dt2 = dt.AsEnumerable()
.OrderByDescending(x => x.Field<int>("DeliveryGroupId"))
.GroupBy(x => new { warehouse = x.Field<int>("WarehouseId"), item = x.Field<int>("ItemId"), subitem = x.Field<int>("SubItemId")})
.Select(x => x.FirstOrDefault())
.CopyToDataTable();
}
}
}
Here is a solution using classes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
OrderDeliveryGroups tblOrderDeliverGroups = new OrderDeliveryGroups();
List<AsNoTracking> vLastRecs = tblOrderDeliverGroups.AsNoTracking()
.OrderByDescending(x => x.DeliverGroupId)
.GroupBy(x => new { x.WarehouseId, x.ItemId, x.SubItemId})
.Select(x => x.FirstOrDefault())
.ToList();
}
}
public class OrderDeliveryGroups
{
public List<AsNoTracking> AsNoTracking()
{
return new List<AsNoTracking>() {
new AsNoTracking() { WarehouseId = 1, ItemId = 1, SubItemId = 1, DeliverGroupId = 1 },
new AsNoTracking() { WarehouseId = 1, ItemId = 1, SubItemId = 1, DeliverGroupId = 2 },
new AsNoTracking() { WarehouseId = 1, ItemId = 1, SubItemId = 1, DeliverGroupId = 3 },
new AsNoTracking() { WarehouseId = 1, ItemId = 1, SubItemId = 2, DeliverGroupId = 1 },
new AsNoTracking() { WarehouseId = 1, ItemId = 1, SubItemId = 2, DeliverGroupId = 2 },
new AsNoTracking() { WarehouseId = 1, ItemId = 2, SubItemId = 1, DeliverGroupId = 1 }
};
}
}
public class AsNoTracking
{
public int WarehouseId { get; set; }
public int ItemId { get; set; }
public int SubItemId { get; set; }
public int DeliverGroupId { get; set; }
}
}

how to DataTable split into mutiple datatables c#

Gender Age Category
--------------------------------
Male | 10 | 2
Female | 15 | 1
Trans | 13 | 3
Female | 10 | 1
Male | 20 | 2
i have a datatable with above values. Male CategoryId is 2. in above table there are total 2 Males rows. based on Category, merged two rows and divide into a seperate datatable.
My required output is :-
Datatable 1
Gender Age Category
--------------------------------
Male | 10 | 2
Male | 20 | 2
DataTable 2
Gender Age Category
--------------------------------
Female | 15 | 1
Female | 10 | 1
DataTable 3
Gender Age Category
--------------------------------
Trans | 13 | 3
var view = sourceDataTable.DefaultView;
view.RowFilter = "Category = 2";
var maleDataTable = view.ToTable();
view.RowFilter = "Category = 1";
var femaleDataTable = view.ToTable();
view.RowFilter = "Category = 3";
var transDataTable = view.ToTable();
Here you go:
List<DataTable> result = DTHead.AsEnumerable()
.GroupBy(row => row.Field<DataType>("Category"))
.Select(g => g.CopyToDataTable())
.ToList();
For more details please check this post:Split Tables
See following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication48
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Gender", typeof(string));
dt.Columns.Add("Age", typeof(int));
dt.Columns.Add("Category", typeof(int));
dt.Rows.Add(new object[] {"Male", 10, 2});
dt.Rows.Add(new object[] {"Female", 15, 1});
dt.Rows.Add(new object[] {"Trans", 13, 3});
dt.Rows.Add(new object[] {"Female", 10, 1});
dt.Rows.Add(new object[] {"Male", 20, 2});
DataTable dt1 = dt.AsEnumerable().Where(x => x.Field<string>("Gender") == "Male").CopyToDataTable();
DataTable dt2 = dt.AsEnumerable().Where(x => x.Field<string>("Gender") == "Feale").CopyToDataTable();
DataTable dt3 = dt.AsEnumerable().Where(x => x.Field<string>("Gender") == "Trans").CopyToDataTable();
}
}
}
Here is a more generic solution that get every type in a column :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication48
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Gender", typeof(string));
dt.Columns.Add("Age", typeof(int));
dt.Columns.Add("Category", typeof(int));
dt.Rows.Add(new object[] { "Male", 10, 2 });
dt.Rows.Add(new object[] { "Female", 15, 1 });
dt.Rows.Add(new object[] { "Trans", 13, 3 });
dt.Rows.Add(new object[] { "Female", 10, 1 });
dt.Rows.Add(new object[] { "Male", 20, 2 });
//updated code
string[] rowNames = dt.AsEnumerable().Select(x => x.Field<string>("Gender")).Distinct().ToArray();
DataSet ds = new DataSet();
foreach (string gender in rowNames)
{
DataTable newDt = dt.AsEnumerable().Where(x => x.Field<string>("Gender") == gender).CopyToDataTable();
newDt.TableName = gender;
ds.Tables.Add(newDt);
}
}
}
}

Categories