Get child record using Linq - c#

This is my first time to hear about LINQ and I have no idea about it. Please be gentle on me.
I have this set of data.
+---------+--------+---------------+
| RadioID | NodeID | SourceRadioID |
+---------+--------+---------------+
| R0 | 1 | |
| R1 | 1 | |
| R2 | 1 | |
| R3 | 1 | |
| R4 | 1 | |
| R5 | 2 | |
| R6 | 2 | |
| R7 | 2 | R0 |
| R8 | 2 | |
| R9 | 2 | |
| R10 | 11 | |
| R11 | 11 | R9 |
| R12 | 11 | |
| R13 | 11 | |
+---------+--------+---------------+
What I need to do is to write a method that returns a list of NodeID. example,
List<int> dependentNode = GetChildNode(1); // int ParentNode
My expected results are NodeIDs: 2 and 11.
NodeID = 2 is included because there is a RadioID = R7 that is connected to RadioID = R0 which belongs to NodeID = 1.
NodeID = 11 is also included because RadioID = R11 is connected to Radio = R9 which belongs to NodeID = 2 (which is also connected to NodeID = 1).
I lookup this article but I always get StackOverFlowException
Rendering a hierarchy using LINQ
Here's the full code:
public class RadioEntity
{
public string RadioID { get; set; }
public int NodeID { get; set; }
public string SourceRadioID { get; set; }
}
public class SampleDemo
{
public void SampleMethod()
{
Func<int, int,List<int>> GetChildNode = null;
GetChildNode = (x, y) =>
{
return (from _x in GetRadio()
where (GetRadio().Where(i => i.NodeID == x).Select(i => i.RadioID)).Contains(_x.RadioID)
from _y in new[] { _x.NodeID }.Union(GetChildNode(_x.NodeID, y + 1))
select _y).ToList<int>();
};
var _res = GetChildNode(1, 0);
}
public List<RadioEntity> GetRadio()
{
List<RadioEntity> _returnVal = new List<RadioEntity>();
_returnVal.Add(new RadioEntity() { RadioID = "R0", NodeID = 1, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R1", NodeID = 1, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R2", NodeID = 1, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R3", NodeID = 1, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R4", NodeID = 1, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R5", NodeID = 2, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R6", NodeID = 2, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R7", NodeID = 2, SourceRadioID = "R0" });
_returnVal.Add(new RadioEntity() { RadioID = "R8", NodeID = 2, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R9", NodeID = 2, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R10", NodeID = 11, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R11", NodeID = 11, SourceRadioID = "R9" });
_returnVal.Add(new RadioEntity() { RadioID = "R12", NodeID = 11, SourceRadioID = "" });
_returnVal.Add(new RadioEntity() { RadioID = "R13", NodeID = 11, SourceRadioID = "" });
return _returnVal;
}
}
You can suggest if there is much better way to do it. Sorry Newbie here.

If you are newbie dont be too clever with recursion and lambdas.
public List<int> GetChildren(int id)
{
var nodes = GetRadio();
var parent = nodes.Single(n => n.NodeID == id);
var children = nodes.Where(n => n.SourceRadioID == parent.RadioID).Select(n => n.NodeID);
return children.Union(children.SelectMany(GetChildren)).ToList();
}
UPDATE 1
public List<int> GetChildren(int id)
{
IEnumerable<RadioEntity> parent = GetRadio().Where(x => x.NodeID == id);
IEnumerable<int> children = (
from r in GetRadio()
where parent.Select(x=>x.RadioID)
.Contains(r.SourceRadioID)
select r
).Select(n => n.NodeID);
return children.Union(children.SelectMany(GetChildren)).ToList();
}

Related

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 get average value using LEFT JOIN in LINQ and Lambda

I want to display each book average rating with its publisher using LINQ and Lambda.
Here's my book list
private List<Book> Books = new List<Book>(){
new Book {
Id: 1,
Name: Book A,
PublisherId: 1
}, new Book {
Id: 2,
Name: Book B,
PublisherId: 2
}, new Book {
Id: 3,
Name: Book C,
PublisherId: 1
}
};
First, i Join Book list with Publisher list using this query
var bookPublisher = Books.SelectMany( book => Publishers.Where(publisher => book.PublisherId == publisher.Id),
(book, publisher) => new { book, publisher });
Then, i try to use left join with BookTransaction list to get detail rating for each book. Here's my BookTransaction list.
private List<Book_Transaction> BookTransactions = new List<Book_Transaction>() {
new Book_Transaction {
Id = 1,
BookId = 1,
CustomerId = 1,
RatingStar = 4.5
}, new Book_Transaction {
Id = 2,
BookId = 2,
CustomerId = 1,
RatingStar = 4
}, new Book_Transaction {
Id = 3,
BookId = 1,
CustomerId = 2,
RatingStar = 5
},new Book_Transaction {
Id = 4,
BookId = 2,
CustomerId = 2,
RatingStar = 3.5
},new Book_Transaction {
Id = 5,
BookId = 1,
CustomerId = 3,
RatingStar = 4
}};
and here's my query
var bookPublisherRating = bookPublisher.SelectMany(bp => BookTransactions.Where(bt => bp.book.Id == bt.BookId).DefaultIfEmpty(),
(bp, bt) => new { BookPublish = bp, BookRating = bt.RatingStar });
in that query, i got the error saying that "'Object reference not set to an instance of an object.' bt was null."
I tried using different approach, but the result it just the same error.
var bookPublisherRating = bookPublisher.GroupJoin(BookTransactions,
bp => bp.book.Id,
bt => bt.BookId,
(bp, bt) => new { BookPublish = bp, BookTrans = bt })
.SelectMany(temp => temp.BookTrans.DefaultIfEmpty(),
(temp, y) => new { BookPublish = temp.BookPublish, bookRating = y.RatingStar });
Can someone tell me where i was wrong? When i deleted the DefaultIfEmpty function, it can display the data but only where both list have the bookId value included, like this
| BookName | PublisherName | Rating |
| Book A | Publisher A | 4.5 |
| Book A | Publisher A | 5 |
| Book A | Publisher A | 4 |
| Book B | Publisher B | 4 |
| Book B | Publisher B | 3.5 |
I expect the result of the query just like this
| BookName | PublisherName | Rating |
| Book A | Publisher A | 4.5 |
| Book B | Publisher B | 3.75 |
| Book C | Publisher A | 0 |
Linq has an Average Method. You can call it on a collection of numeric values, or for a collection of objects you can pass it the property name to average. You can also call GroupBy to create key<->collection pairs.
So you can group your data by the BookId and find the average rating.
Assuming the following
public class Book_Transaction
{
public int Id { get; set; }
public int BookId { get; set; }
public int CustomerId { get; set; }
public double RatingStar { get; set; }
}
var bookTransactions = new List<Book_Transaction>() {
new Book_Transaction {
Id = 1,
BookId = 1,
CustomerId = 1,
RatingStar = 4.5
}, new Book_Transaction {
Id = 2,
BookId = 2,
CustomerId = 1,
RatingStar = 4
}, new Book_Transaction {
Id = 3,
BookId = 1,
CustomerId = 2,
RatingStar = 5
},new Book_Transaction {
Id = 4,
BookId = 2,
CustomerId = 2,
RatingStar = 3.5
},new Book_Transaction {
Id = 5,
BookId = 1,
CustomerId = 3,
RatingStar = 4
}};
Then
bookTransactions.GroupBy(
bt => bt.BookId,
(bookId, collection) => new {
BookId = bookId,
AverageRating = collection.Average(x => x.RatingStar)
}).ToList()
Gives the following output in the C# interactive console:
List<<>f__AnonymousType0#5<int, double>>(2) {
{ BookId = 1, AverageRating = 4.5 },
{ BookId = 2, AverageRating = 3.75 }
}

SQL select multiple max rows value after group by

I have a table where each JunctionlistID is repeated many times. there is a JunctionlistID in front of each id in each row.
I want to select entire row for each JunctionlistID where ID is latest. There are total 5 columns in this table i want all columns to get selected when i select that row.
ID | MonitoringString| JunctionListId | area_id| CompanyProfileId
1 | 1006410001D0 | 267 | 910064 | 7
2 | 1206420001D0 | 268 | 910065 | 7
3 | 1306440001D0 | 267 | 910064 | 7
4 | 1506450001D0 | 268 | 910065 | 7
5 | 1606470001D0 | 267 | 910064 | 7
6 | 1806480001D0 | 268 | 910065 | 7
7 | 1006420001D0 | 267 | 910064 | 7
8 | 1006470001D0 | 268 | 910065 | 7
9 | 1006490001D0 | 267 | 910064 | 7
10 | 1006430001D0 | 268 | 910065 | 7
11 | 1006460001D0 | 285 | 910066 | 8
12 | 1006438001D0 | 268 | 910067 | 8
The Answer should be
ID | MonitoringString| JunctionListId | area_id| CompanyProfileId
9 | 1006490001D0 | 267 | 910064 | 7
10 | 1006430001D0 | 268 | 910065 | 7
I try the query as below -
Select ID,MonitoringString,JunctionListId,area_id,CompanyProfileId from tblMonitoring where CompanyProfileId=7
I need the query same as well in linq and SQL both, If anybody know please give me the proper solution.
Thanks
If I understand correctly, you just want the last records for each company and junction id, based on the id. You can use row_number():
Select m.*
from (select m.*,
row_number() over (partition by CompanyProfileId, JunctionListId order by id desc) as seqnum
from tblMonitoring m
) m
where CompanyProfileId = 7 and seqnum = 1;
https://dotnetfiddle.net/oiRkzO
using System;
using System.Data.Entity;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<Item> items = new List<Item>()
{
new Item() { ID = 1, MonitoringString = "1006410001D0", JunctionListId = 267, area_id = 910064 , CompanyProfileId = 7},
new Item() { ID = 2, MonitoringString = "1206420001D0", JunctionListId = 268, area_id = 910065 , CompanyProfileId = 7},
new Item() { ID = 3, MonitoringString = "1306440001D0", JunctionListId = 267, area_id = 910064 , CompanyProfileId = 7},
new Item() { ID = 4, MonitoringString = "1506450001D0", JunctionListId = 268, area_id = 910065 , CompanyProfileId = 7},
new Item() { ID = 5, MonitoringString = "1606470001D0", JunctionListId = 267, area_id = 910064 , CompanyProfileId = 7},
new Item() { ID = 6, MonitoringString = "1806480001D0", JunctionListId = 268, area_id = 910065 , CompanyProfileId = 7},
new Item() { ID = 7, MonitoringString = "1006420001D0", JunctionListId = 267, area_id = 910064 , CompanyProfileId = 7},
new Item() { ID = 8, MonitoringString = "1006470001D0", JunctionListId = 268, area_id = 910065 , CompanyProfileId = 7},
new Item() { ID = 9, MonitoringString = "1006490001D0", JunctionListId = 267, area_id = 910064 , CompanyProfileId = 7},
new Item() { ID = 10, MonitoringString = "1006430001D0", JunctionListId = 268, area_id = 910065 , CompanyProfileId = 7},
new Item() { ID = 11, MonitoringString = "1006460001D0", JunctionListId = 285, area_id = 910066 , CompanyProfileId = 8},
new Item() { ID = 12, MonitoringString = "1006438001D0", JunctionListId = 268, area_id = 910067 , CompanyProfileId = 8},
};
var result = items.GroupBy(item => item.JunctionListId).Select(g => g.FirstOrDefault(gx => gx.ID == g.Max(x => x.ID))).ToList();
var resultCmp7 = items.Where(item => item.CompanyProfileId == 7).GroupBy(item => item.JunctionListId).Select(g => g.FirstOrDefault(gx => gx.ID == g.Max(x => x.ID))).ToList();
foreach (var item in result)
{
Console.WriteLine(string.Format("{0},{1},{2},{3}",item.ID, item.MonitoringString, item.JunctionListId,item.area_id, item.CompanyProfileId));
}
Console.WriteLine();
foreach (var item in resultCmp7)
{
Console.WriteLine(string.Format("{0},{1},{2},{3}",item.ID, item.MonitoringString, item.JunctionListId,item.area_id, item.CompanyProfileId));
}
Console.ReadLine();
}
class Item
{
public int ID { get; set; }
public string MonitoringString { get; set; }
public int JunctionListId { get; set; }
public int area_id { get; set; }
public int CompanyProfileId { get; set; }
}
}
For linq, you could do the following. (Looks like you also want to filter by companyprofileid)
var result = items.Where(x=>x.CompanyProfileId==7)
.GroupBy(x=>x.JunctionListId)
.Select(x=>x.ToList()
.OrderByDescending(c=>c.ID)
.ThenBy(c=>c.JunctionListId)
.First());
Select MAX(ID) ID,
Max(MonitoringString) MonitoringString,
Max(JunctionListID) JunctionListID,
Max(area_id) area_id,
Max(CompanyProfileId) CompanyProfileId
from MonitorsList
where CompanyProfileId = 7
Group by JunctionListID
Try this one,
SELECT COUNT(ID), MonitoringString, JunctionListId, are_id, CompanyProfileId FROM tblMonitoring WHERE CompanyProfileID = '7' GROUP BY CompanyProfileId;

C# Linq that includes sorting and grouping

This is my code:
public class Photos
{
public long PhotoLabel { get; set; }
public int UserID { get; set; }
}
List<Photos> photolist = new List<Photos>();
var result1 = photolist.OrderByDescending(p => p.PhotoLabel).ThenBy(r => r.UserID).ToList();
If I display the contents now, this is what I get (First sorted in descending order of PhotoLabel and then sorted by UserID:
|------|---------------------|---------------------|
| Row | UserID | PhotoLabel |
|----------------------------|---------------------|
| 1 | 92 | 20180729181046 |
|----------------------------|---------------------|
| 2 | 92 | 20180729181041 |
|----------------------------|---------------------|
| 3 | 92 | 20180729181037 |
|----------------------------|---------------------|
| 4 | 88 | 20180729174415 |
|----------------------------|---------------------|
| 5 | 88 | 20180729174405 |
|----------------------------|---------------------|
| 6 | 04 | 20180729174358 |
|----------------------------|---------------------|
| 7 | 1 | 20170924183847 |
|----------------------------|---------------------|
| 8 | 1 | 20170921231422 |
|----------------------------|---------------------|
| 9 | 1 | 20170920194624 |
|----------------------------|---------------------|
| 10 | 32 | 20170820114728 |
|----------------------------|---------------------|
| 11 | 32 | 20170820114725 |
|----------------------------|---------------------|
| 12 | 32 | 20170820114421 |
|----------------------------|---------------------|
| 13 | 32 | 20170820114416 |
|----------------------------|---------------------|
| 14 | 1 | 20170225151023 |
|----------------------------|---------------------|
| 15 | 1 | 20170225151000 |
|----------------------------|---------------------|
| 16 | 1 | 20170225150957 |
|----------------------------|---------------------|
From the sorted table above, this is what I want to achieve:
Display groups of UserIDs and PhotoLabels where UserIDs appear 3 or more times in one group (eg: rows 4 and 5 where UserID=88 and row 6 where UserID=04 should be eliminated since the UserID=88 appears just twice in the group and UserID=04 appears only once in the group).
Display only the top most group of UserIDs and exclude any repeating UserIDs (eg: rows 7,8 and 9 displays the UserID=1 group. Don't display any other UserID=1 group such as rows 14,15 and 16. )
The expected result from query should be:
|------|---------------------|---------------------|
| Row | UserID | PhotoLabel |
|----------------------------|---------------------|
| 1 | 92 | 20180729181046 |
|----------------------------|---------------------|
| 2 | 92 | 20180729181041 |
|----------------------------|---------------------|
| 3 | 92 | 20180729181037 |
|----------------------------|---------------------|
| 7 | 1 | 20170924183847 |
|----------------------------|---------------------|
| 8 | 1 | 20170921231422 |
|----------------------------|---------------------|
| 9 | 1 | 20170920194624 |
|----------------------------|---------------------|
| 10 | 32 | 20170820114728 |
|----------------------------|---------------------|
| 11 | 32 | 20170820114725 |
|----------------------------|---------------------|
| 12 | 32 | 20170820114421 |
|----------------------------|---------------------|
| 13 | 32 | 20170820114416 |
|----------------------------|---------------------|
Thank you so much in in advance! :-)
If I am not misunderstood the requirement, below function properly works (but it shouldn't the most efficient solution)
protected List<AnObject> aFunction(List<AnObject> sortedList)
{
//Display groups of UserIDs and PhotoLabels where UserIDs appear 3 or more times in one group (eg: rows 4 and 5 where UserID = 88 and row 6 where UserID = 04 should be eliminated since the UserID = 88 appears just twice in the group and UserID = 04 appears only once in the group).
//Display only the top most group of UserIDs and exclude any repeating UserIDs(eg: rows 7, 8 and 9 displays the UserID = 1 group.Don't display any other UserID=1 group such as rows 14,15 and 16.
int pivot = -1;
int cnt = 0;
List<AnObject> masterList = new List<AnObject>();
List<AnObject> subList = new List<AnObject>();
//List<int> Excluded = new List<int>();
foreach (AnObject r in sortedList)
{
if (pivot != r.UserID)
{
if (cnt > 2)
{
masterList.AddRange(subList);
//Excluded.Add(pivot);
}
subList.Clear();
pivot = -1;
cnt = 0;
//if (!Excluded.Contains(r.UserID))
if (!masterList.Any(x => x.UserID == r.UserID))
{
pivot = r.UserID;
}
}
subList.Add(r);
cnt++;
}
return masterList;
}
To call it for testing
protected class AnObject
{
public AnObject(int uid, string photolabel)
{
this.UserID = uid;
this.PhotoLabel = photolabel;
}
public int UserID { get; set; }
public string PhotoLabel { get; set; }
}
protected void Execute()
{
List<AnObject> sortedList = new List<AnObject>();
sortedList.Add(new AnObject(92, "anystring"));
sortedList.Add(new AnObject(92, "anystring"));
sortedList.Add(new AnObject(92, "anystring"));
sortedList.Add(new AnObject(88, "anystring"));
sortedList.Add(new AnObject(88, "anystring"));
sortedList.Add(new AnObject(4, "anystring"));
sortedList.Add(new AnObject(1, "anystringfirst"));
sortedList.Add(new AnObject(1, "anystringfirst"));
sortedList.Add(new AnObject(1, "anystringfirst"));
sortedList.Add(new AnObject(32, "anystring"));
sortedList.Add(new AnObject(32, "anystring"));
sortedList.Add(new AnObject(32, "anystring"));
sortedList.Add(new AnObject(32, "anystring"));
sortedList.Add(new AnObject(1, "anystringafter"));
sortedList.Add(new AnObject(1, "anystringafter"));
sortedList.Add(new AnObject(1, "anystringafter"));
List<AnObject> bb = aFunction(sortedList);
}
Please wait! This is not the final answer! a little bit further modification is needed! modification is underway.
List<Photos> photolist = new List<Photos>()
{
new Photos() {UserID = 92, PhotoLabel = 20180729181046},
new Photos() {UserID = 92, PhotoLabel = 20180729181041},
new Photos() {UserID = 92, PhotoLabel = 20180729181037},
new Photos() {UserID = 88, PhotoLabel = 20180729174415},
new Photos() {UserID = 88, PhotoLabel = 20180729174405},
new Photos() {UserID = 04, PhotoLabel = 20180729174358},
new Photos() {UserID = 1, PhotoLabel = 20170924183847},
new Photos() {UserID = 1, PhotoLabel = 20170921231422},
new Photos() {UserID = 1, PhotoLabel = 20170920194624},
new Photos() {UserID = 32, PhotoLabel = 20170820114728},
new Photos() {UserID = 32, PhotoLabel = 20170820114725},
new Photos() {UserID = 32, PhotoLabel = 20170820114421},
new Photos() {UserID = 32, PhotoLabel = 20170820114416},
new Photos() {UserID = 1, PhotoLabel = 20170225151023},
new Photos() {UserID = 1, PhotoLabel = 20170225151000},
};
var photolist2 = photolist.GroupBy(g => g.UserID)
.Select(p => new
{
UserId = p.Key,
Count = p.Count()
})
.ToList();
var filteredPhotoList = photolist
.Join(photolist2,
photo => photo.UserID,
photo2 => photo2.UserId,
(photo, photo2) => new {UserId = photo.UserID, PhotoLabel =
photo.PhotoLabel, Count = photo2.Count})
.Where(p => p.Count > 2).Select(p => new
{
p.UserId, p.PhotoLabel
}).OrderByDescending(p => p.PhotoLabel).ThenBy(p => p.UserId).ToList();
Thank you all for your help. SKLTFZ's and TanvirArjel's answers were close but didn't achieve the expected results. I realized that you cannot achieve everything described above in Linq, so this is what I came up with and it achieves everything listed above:
PS: I renamed var result1 to ordered_photolist
List<Photos> ordered_photolist = photolist.OrderByDescending(p => p.PhotoLabel).ThenBy(r => r.UserID).ToList();
List<Photos> temp_photolist = new List<Photos>();
List<Photos> final_photolist = new List<Photos>();
int UserID = -1;
int UserIDCount = 0;
foreach (Photos p in ordered_photolist)
{
if (UserID == -1)
{
UserID = p.UserID;
temp_photolist.Add(p);
UserIDCount++;
}
else
{
if ( UserID == p.UserID )
{
temp_photolist.Add(p);
UserIDCount++;
}
else
{
if ( UserIDCount >= 3 )
{
// add temp_photolist to final list
int index = final_photolist.FindIndex(item => item.UserID == UserID);
if (index == -1)
{
// element does not exists, do what you need
final_photolist.AddRange(temp_photolist);
}
temp_photolist.Clear();
temp_photolist.Add(p);
UserIDCount = 1;
UserID = p.UserID;
}
else
{
temp_photolist.Clear();
UserIDCount = 0;
UserID = -1;
}
}
}
}

Creating a Nested List from a Flat List in C#

I currently have the following classes:
public class NavigationItem
{
public int ID { get; set; }
public string Title { get; set; }
public int ParentID { get; set; }
public List<NavigationItem> Children { get; set; }
}
public class FlatItem
{
public int ID { get; set; }
public string Title { get; set; }
public int ParentID { get; set; }
}
I have a sample data as follows:
+====+============+==========+
| ID | Title | ParentID |
+====+============+==========+
| 1 | Google | |
+----+------------+----------+
| 2 | Microsoft | |
+----+------------+----------+
| 3 | Oracle | |
+----+------------+----------+
| 4 | Gmail | 1 |
+----+------------+----------+
| 5 | Sheets | 1 |
+----+------------+----------+
| 6 | Adsense | 1 |
+----+------------+----------+
| 7 | Azure | 2 |
+----+------------+----------+
| 8 | SharePoint | 2 |
+----+------------+----------+
| 9 | Office | 2 |
+----+------------+----------+
| 10 | Java | 3 |
+----+------------+----------+
| 11 | Word | 9 |
+----+------------+----------+
| 12 | Excel | 9 |
+----+------------+----------+
| 13 | PowerPoint | 9 |
+----+------------+----------+
I already have the code to pull all the information from the sample data above and turn it into a List<FlatItem> object.
What's the best approach so that I can have a List<NavigationItem> object which will look like something below:
Google
Gmail
Sheets
AdSense
Microsoft
Azure
SharePoint
Office
Word
Excel
PowerPoint
Oracle
Java
I'm thinking of creating a recursive method to loop through my List<FlatItem> then structure it in a way to be a nested list of NavigationItem.
No need for recursion. You could use LINQ to build the structure easily:
List<FlatItem> flatItems = ...;
var navigationItems = flatItems.Select(
i => new NavigationItem { ID = i.ID, Title = i.Title, ParentID = i.ParentID }
).ToList();
foreach (var i in navigationItems)
i.Children = navigationItems.Where(n => n.ParentID == i.ID).ToList();
// get Google, Microsoft, Oracle items
var rootNavigationItems = navigationItems.Where(n => n.ParentID == 0);
Try this:
List<FlatItem> source = new List<UserQuery.FlatItem>()
{
new FlatItem() { ID = 1, Title = "Google", ParentID = null },
new FlatItem() { ID = 2, Title = "Microsoft", ParentID = null },
new FlatItem() { ID = 3, Title = "Oracle", ParentID = null },
new FlatItem() { ID = 4, Title = "Gmail", ParentID = 1 },
new FlatItem() { ID = 5, Title = "Sheets", ParentID = 1 },
new FlatItem() { ID = 6, Title = "Adsense", ParentID = 1 },
new FlatItem() { ID = 7, Title = "Azure", ParentID = 2 },
new FlatItem() { ID = 8, Title = "SharePoint", ParentID = 2 },
new FlatItem() { ID = 9, Title = "Office", ParentID = 2 },
new FlatItem() { ID = 10, Title = "Java", ParentID = 3 },
new FlatItem() { ID = 11, Title = "Word", ParentID = 9 },
new FlatItem() { ID = 12, Title = "Excel", ParentID = 9 },
new FlatItem() { ID = 13, Title = "PowerPoint", ParentID = 9 },
};
var lookup = source.ToLookup(x => x.ParentID);
Func<int?, List<NavigationItem>> build = null;
build = pid =>
lookup[pid]
.Select(x => new NavigationItem()
{
ID = x.ID,
Title = x.Title,
ParentID = x.ParentID,
Children = build(x.ID)
})
.ToList();
To start the process call build(null). That gives me this:
This does assume that the ParentId property is a int? - which your data table does suggest.
If you are ok with using recursion, you can create a function like this:
public List<NavigationItem> ChildrenOf(List<FlatItem> flatItems, int parentId)
{
var childrenFlatItems = flatItems.Where(i => i.ParentID == parentId);
return childrenFlatItems.Select(i => new NavigationItem {
ID = i.ID,
Title = i.Title,
ParentID = i.ParentID,
Children = ChildrenOf(flatItems, i.ID)})
.ToList();
}
Then, assuming that your root items have a parent id of 0 (since you aren't using nullable types), you generate the full list with:
ChildrenOf(flatsItems, 0)
Untested, however you could try this, should be fairly fast as well
var list = new List<FlatItem>();
var result = new List<NavigationItem>();
// just a helper to remember ids
var dict = new Dictionary<int, NavigationItem>();
foreach (var item in list)
{
var nav = new NavigationItem()
{
ID = item.ID,
ParentID = item.ParentID,
Title = item.Title,
Children = new List<NavigationItem>()
};
if (!dict.ContainsKey(nav.ParentID))
result.Add(nav);
else
dict[nav.ParentID].Children.Add(nav);
dict.Add(item.ID, nav);
}
no recursive, just GroupBy.
List<NavigationItem> list = ... // map from List<FlatItem>
// and init Children = new List<NavigationItem>();
var groups = list.GroupBy(x => x.ParentID).ToList();
foreach (var g in groups)
{
var items = list.Find(x => x.ID == g.Key);
if (items != null)
items.Children = g.ToList();
}
// tops is [Google, Microsoft, Oracle]
var tops = list.Where(x => x.ParentID == null).ToList();

Categories