i have a method GetChild(id) that return the children with respect to id of parent passed as a parameter
those children can also have their own children to any level
if i want to create a JSON which represent the entire hierarchy of child and parent then how should i proceed?
public ActionResult GetChild(long id)
{
Dal objDal = new Dal();
var res = objDal.db.ChildGet(id).ToList();
return Json(res, JsonRequestBehavior.AllowGet);
}
this is justfor first level
how can i use this GetChild(id) method recursively?
any kind of help will be appreciated
public class Comment
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Text { get; set; }
public List<Comment> Children { get; set; }
}
public JsonResult Test()
{
List<Comment> categories = new List<Comment>()
{
new Comment () { Id = 1, Text = "Yabancı Dil", ParentId = 0},
new Comment() { Id = 2, Text = "İngilizce", ParentId = 1 },
new Comment() { Id = 3, Text = "YDS", ParentId = 2 },
new Comment() { Id = 4, Text = "TOEFL", ParentId = 2 },
new Comment() { Id = 5, Text = "YDS Seviye1", ParentId = 3 },
new Comment() { Id = 6, Text = "TOEFL Seviye1", ParentId = 4 }
};
List<Comment> hierarchy = new List<Comment>();
hierarchy = categories
.Where(c => c.Id == 2)
.Select(c => new Comment()
{
Id = c.Id,
Text = c.Text,
ParentId = c.ParentId,
Children = GetChildren(categories, c.Id)
})
.ToList();
List<Comment> list = new List<Comment>();
List<string> list2 = new List<string>();
if (hierarchy != null)
{
liste.AddRange(hierarchy);
}
return Json(liste, JsonRequestBehavior.AllowGet);
}
public static List<Comment> GetChildren(List<Comment> comments, int parentId)
{
hAbDbContext db = new hAbDbContext();
return comments
.Where(c => c.ParentId == parentId)
.Select(c => new Comment()
{
Id = c.Id,
Text = c.Text,
ParentId = c.ParentId,
Children = GetChildren(comments, c.Id)
})
.ToList();
}
Related
I am programming a multilevel menu / submenu in ASP.NET MVC.
I have this table:
Id Name ParentId
----------------------------------
1 Menu1 null
2 Menu2 null
3 Submenu1-menu1 1
4 Submenu2-menu1 1
5 1-Level3-submenu1 3
6 2-Level3-submenu1 3
7 3-Level3-submenu1 3
To fetch the data using Entity Framework, I wrote this code:
var category = _context.Categories
.Include(p => p.SubCategories)
.Where(p => p.ParentId == null)
.ToList()
.Select(p => new MenuItemDto
{
CatId = p.Id,
Name = p.Name,
Child = p.SubCategories.ToList().Select(child => new MenuItemDto
{
CatId = child.Id,
Name = child.Name,
Child = child.SubCategories?.ToList().Select(grandchild => new MenuItemDto
{
CatId = grandchild.Id,
Name = grandchild.Name,
}).ToList()
}).ToList(),
}).ToList();
public class MenuItemDto
{
public long CatId { get; set; }
public string Name { get; set; }
public List<MenuItemDto> Child { get; set; }
}
but the result is that just Menu1 and Menu2 and the children of Menu1 (Submenumen1-menu1 and Submenu2-menu1), I could not fetch 1-Level3-submenu1 and 2-Level3-submenu1 and 3-Level3-submenu1
I would recommend simplifying the database call and building the tree in code as this would be more efficient.
To do this create a recursive method for building the children of an parent.
In the example below I am manually generating the Database list and taking the assumption of the ParentId being a string. I am also manually looping through the output and only going to 3 levels for this example. You should be aware that you can have infinite levels if you self reference.
You can see a working example of the code below at https://dotnetfiddle.net/BCyO6I
public class MenuItemDb
{
public long Id { get; set; }
public string Name { get; set; }
public string ParentId { get; set; }
}
public class MenuItemDto
{
public long CatId { get; set; }
public string Name { get; set; }
public List<MenuItemDto> Children { get; set; }
}
public class Program{
public static void Main(string[] args){
//get all data from Context
//var categories = _context.Categories.ToList();
//for the purpose of this test manually generate categories
List<MenuItemDb> categories = new List<MenuItemDb>();
categories.Add(new MenuItemDb() {
Id = 1, Name = "Menu1", ParentId = null
});
categories.Add(new MenuItemDb() {
Id = 2, Name = "Menu2", ParentId = null
});
categories.Add(new MenuItemDb() {
Id = 3, Name = "Submenu1-menu1", ParentId = "1"
});
categories.Add(new MenuItemDb() {
Id = 4, Name = "Submenu1-menu2", ParentId = "1"
});
categories.Add(new MenuItemDb() {
Id = 5, Name = "1-Level3-submenu1", ParentId = "3"
});
categories.Add(new MenuItemDb() {
Id = 6, Name = "2-Level3-submenu1", ParentId = "3"
});
categories.Add(new MenuItemDb() {
Id = 7, Name = "3-Level3-submenu1", ParentId = "3"
});
List<MenuItemDto> menu = new List<MenuItemDto>();
//build top level
foreach(var child in categories.Where(w => w.ParentId == null))
{
MenuItemDto childDto = new MenuItemDto() {
CatId = child.Id,
Name = child.Name
};
AddChildren(childDto, categories);
menu.Add(childDto);
}
foreach(var item in menu){
Console.WriteLine(item.Name);
foreach(var childLevel1 in item.Children){
Console.WriteLine(" -- " + childLevel1.Name);
foreach(var childLevel2 in item.Children){
Console.WriteLine(" -- " + childLevel2.Name);
}
}
}
}
public static void AddChildren(MenuItemDto parent, List<MenuItemDb> categories){
parent.Children = new List<MenuItemDto>();
foreach(var child in categories.Where(w => w.ParentId == parent.CatId.ToString()))
{
var childDto = new MenuItemDto() {
CatId = child.Id,
Name = child.Name
};
AddChildren(childDto, categories);
parent.Children.Add(childDto);
}
}
}
I think you want to create tree of categories so you can do like that:
first Get all of your category and put it in to the memory for better performance of sorting.
with this you can do it:
var unsortedCategories = _context.Categories.ToList()
and then sort your category with parentId
private async Task<List<Category>> SortCategoriesForTreeAsync(
List<Category> source,
int parentId = 0,
CancellationToken cancellationToken = default)
{
if (source is null)
throw new ArgumentNullException(nameof(source));
var result = new List<Category>();
foreach (var cat in source.Where(c => c.ParentId == parentId).ToList())
{
result.Add(cat);
result.AddRange(await SortCategoriesForTreeAsync(source, cat.Id, true, cancellationToken));
}
if (ignoreCategoriesWithoutExistingParent || result.Count == source.Count)
return result;
//find categories without parent in provided category source and insert them into result
foreach (var cat in source)
if (result.FirstOrDefault(x => x.Id == cat.Id) is null)
result.Add(cat);
return result;
}
use method like this:
var sortedAllCategory = await SortCategoriesForTreeAsync(unsortedCategories)
then when you have sorted categories you can create the tree:
private async Task<List<MenuItemDto>> CreateCategories(List<Category> allSortedCategories, int categoryId, CancellationToken cancellationToken)
{
var categories = allSortedCategories
.Where(c => c.ParentId == categoryId);
var listModels = new List<MenuItemDto>();
foreach (var category in categories)
{
var categoryModel = new MenuItemDto
{
CatId = category.Id,
Name = category.Name,
};
var subCategories = await PrepareCategories(allSortedCategories, category.Id, cancellationToken);
categoryModel.Child.AddRange(subCategories);
categoryModel.HaveSubCategories = categoryModel.SubCategories.Any();
listModels.Add(categoryModel);
}
return listModels;
}
so your can use this method to create tree like this:
var result = await PrepareCategories(sortedAllCategory, 0, cancellationToken);
I have an object:
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
}
I return a list that may look like the following:
List<Customer> CustomerList = new List<Customer>();
CustomerList.Add( new Customer { ID = 1, Name = "One", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 2, Name = "Two", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 3, Name = "Three", GroupID = 2 } );
CustomerList.Add( new Customer { ID = 4, Name = "Four", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 5, Name = "Five", GroupID = 3 } );
CustomerList.Add( new Customer { ID = 6, Name = "Six", GroupID = 3 } );
I want to return a linq query which will look like
CustomerList
GroupID =1
UserID = 1, UserName = "UserOne", GroupID = 1
UserID = 2, UserName = "UserTwo", GroupID = 1
UserID = 4, UserName = "UserFour", GroupID = 1
GroupID =2
UserID = 3, UserName = "UserThree", GroupID = 2
GroupID =3
UserID = 5, UserName = "UserFive", GroupID = 3
UserID = 6, UserName = "UserSix",
I tried from
Using Linq to group a list of objects into a new grouped list of list of objects
code
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID)
.Select(grp => grp.ToList())
.ToList();
works but does not give the desired output.
var groupedCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(grp =>new { GroupID =grp.Key, CustomerList = grp.ToList()})
.ToList();
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>{
u.Name = "User" + u.Name;
return u;
}, (key,g)=>g.ToList())
.ToList();
If you don't want to change the original data, you should add some method (kind of clone and modify) to your class like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public Customer CloneWithNamePrepend(string prepend){
return new Customer(){
ID = this.ID,
Name = prepend + this.Name,
GroupID = this.GroupID
};
}
}
//Then
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>u.CloneWithNamePrepend("User"), (key,g)=>g.ToList())
.ToList();
I think you may want to display the Customer differently without modifying the original data. If so you should design your class Customer differently, like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public string Prefix {get;set;}
public string FullName {
get { return Prefix + Name;}
}
}
//then to display the fullname, just get the customer.FullName;
//You can also try adding some override of ToString() to your class
var groupedCustomerList = CustomerList
.GroupBy(u => {u.Prefix="User", return u.GroupID;} , (key,g)=>g.ToList())
.ToList();
is this what you want?
var grouped = CustomerList.GroupBy(m => m.GroupID).Select((n) => new { GroupId = n.Key, Items = n.ToList() });
var result = from cx in CustomerList
group cx by cx.GroupID into cxGroup
orderby cxGroup.Key
select cxGroup;
foreach (var cxGroup in result) {
Console.WriteLine(String.Format("GroupID = {0}", cxGroup.Key));
foreach (var cx in cxGroup) {
Console.WriteLine(String.Format("\tUserID = {0}, UserName = {1}, GroupID = {2}",
new object[] { cx.ID, cx.Name, cx.GroupID }));
}
}
The desired result can be obtained using IGrouping, which represents a collection of objects that have a common key in this case a GroupID
var newCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(group => new { GroupID = group.Key, Customers = group.ToList() })
.ToList();
I have a table of WorldEvents. Each WorldEvent has a list of Presentations, that happened in some country, regarding that WorldEvent
public class WorldEvent
{
public int ID { get; set; }
public string Name { get; set; }
public List<Presentation> PresentationList { get; set; }
}
public class Presentation
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
}
public class WorldEventService
{
public List<WorldEvent> GetWorldEvents()
{
List<WorldEvent> worldEventList = new List<WorldEvent>();
List<Presentation> presentationList = new List<Presentation>();
// Create list of Presentations for WorldEvent_1
presentationList = new List<Presentation>()
{
new Presentation() { ID = 1, Name = "Presentation_1", Country = "Germany",},
new Presentation() { ID = 2, Name = "Presentation_2", Country = "UK",},
new Presentation() { ID = 3, Name = "Presentation_3", Country = "UK",},
};
// Add WorldEvent_1 to the list of WorldEvents
worldEventList.Add(new WorldEvent()
{
ID = 1,
Name = "WorldEvent_1",
PresentationList = presentationList,
});
// Create list of Presentations for WorldEvent_2
presentationList = new List<Presentation>()
{
new Presentation() { ID = 4, Name = "Presentation_4", Country = "USA",},
new Presentation() { ID = 5, Name = "Presentation_5", Country = "UK",},
new Presentation() { ID = 6, Name = "Presentation_6", Country = "Japan",},
};
// Add WorldEvent_2 to the list of WorldEvents
worldEventList.Add(new WorldEvent()
{
ID = 2,
Name = "WorldEvent_2",
PresentationList = presentationList,
});
// Create list of Presentations for WorldEvent_3
presentationList = new List<Presentation>()
{
new Presentation() { ID = 7, Name = "Presentation_7", Country = "France",},
new Presentation() { ID = 8, Name = "Presentation_8", Country = "Germany",},
new Presentation() { ID = 9, Name = "Presentation_9", Country = "Japan",},
};
// Add WorldEvent_3 to the list of WorldEvents
worldEventList.Add(new WorldEvent()
{
ID = 3,
Name = "WorldEvent_3",
PresentationList = presentationList,
});
return worldEventList;
}
}
Now - how can I get a list of WorldEvents, whose Presentations took place in the UK.
And - in the list of my interest, WorldEvents should contain info about those UK Presentations only.
In other word, I need this as result:
WorldEvent_1(Presentation_2, Presentation_3)
WorldEvent_2(Presentation_5)
If I've understood what you want. There are many ways to do this, however you can filter first, then recreate your WorldEvents with the filtered list of Presentation
var country = "UK";
var result = worldEventList.Where(x => x.PresentationList.Any(y => y.Country == country))
.Select(x => new WorldEvent()
{
ID = x.ID,
Name = x.Name,
PresentationList = x.PresentationList
.Where(y => y.Country == country)
.ToList()
}).ToList();
or as noted by Gert Arnold in the comments you could filter after the fact
var result = worldEventList.Select(x => new WorldEvent()
{
ID = x.ID,
Name = x.Name,
PresentationList = x.PresentationList
.Where(y => y.Country == country).ToList()
}).Where(x => x.PresentationList.Any())
.ToList();
Note : Because this is not projecting (selecting) each Presentation, any change you make to a Presentation in the result will be reflected in the original data. If you don't want this, you will need to recreate each Presentation
var worldEvent = new WorldEventService.GetWorldEvents();
var filter = "";//userInput
var filteredResult = worldEvent.Select(r => new WorldEvent
{
PresentationList = r.PresentationList.Where(c => c.Country == filter).ToList(),
ID = r.Id,
Name = r.Name
}).ToList();
public static List<WorldEvent> Filter(string Country, List<WorldEvent> events) {
var evs = from ev in events.Where(x => x.PresentationList.Any(y => y.Country == Country))
let targetPres = from pres in ev.PresentationList
where pres.Country == Country
select pres
select new WorldEvent {
ID = ev.ID,
Name = ev.Name,
PresentationList = targetPres.ToList()
};
return evs.ToList();
}
Not sure if my understanding is correct, I guess there's a one to many relationship between your WorldEvent and Presentation table. So if you'd like to get all the WorldEvents and its related Presentations which take place in UK, by using EntityFramework, you can try this:
worldEventContext
.Include(PresentationContext)
.Select(
w => new
{
w.ID,
w.Name,
PresentationList = w.PresentationContext.Where(p => p.Country == "UK")
})
I'm getting the results of a sql outer join as flat results in an IEnumerable, and would like to convert them to nested typed objects in linq. From something like this:
[{id: 1, industryId: 1}, {id:1, industryId: 2}, {id:2, industryId: 1} etc..]
to something like this:
list of Company [{id: 1, list of Industry{industryId: 1, 2}, {id: 2, list of Industry{industryId: 1}}]
I'm currently trying a solution with GroupBy:
Companies = flatDbRows
.GroupBy(
row => row.CompanyId,
(key, value) => new CompanyModel
{
CompanyId = value.First().CompanyId,
CompanyName = value.First().CompanyName,
Industries = value
.GroupBy(
row => new { row.IndustryId, row.Industry },
(k, v) => new IndustryModel() { IndustryId = k.IndustryId, Name = k.Industry }
)
.Where(x => x.IndustryId != 0)
.ToList(),
}).ToList();
}
but it doesn't feel great, especially with all the value.First() I'm using to get the values that only belong to each grouped company. Is there something more appropriate? Group join sounded more like what I wanted, but I'm having trouble understanding how to apply it to a single list. I'm open to using query syntax instead of the lambdas if that's easier.
I'm trying to go from this model (where company-related info will be duplicated for each outer joined industry result):
public class CompanyFlatDbRowsModel
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public int IndustryId{ get; set; }
public string Industry { get; set; }
}
to this:
public class CompanyModel
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public IEnumerable<IndustryModel> Industries { get; set; }
}
// FULL edit after providing your models
public class TestClass
{
public class CompanyModel
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public List<IndustryModel> Industires { get; set; }
}
public class IndustryModel
{
public int IndustryId { get; set; }
public string IndustryName { get; set; }
}
public class CompanyFlatDbRowsModel
{
public CompanyFlatDbRowsModel()
{
}
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public int IndustryId { get; set; }
public string Industry { get; set; }
}
[Fact]
public void Test()
{
var data = new List<CompanyFlatDbRowsModel>
{
new CompanyFlatDbRowsModel
{
CompanyId = 1,
CompanyName = "Company 1",
IndustryId = 1,
Industry = "Industry 1"
},
new CompanyFlatDbRowsModel
{
CompanyId = 1,
CompanyName = "Company 1",
IndustryId = 2,
Industry = "Industry 2"
},
new CompanyFlatDbRowsModel
{
CompanyId = 2,
CompanyName = "Company 2",
IndustryId = 3,
Industry = "Industry 3"
},
new CompanyFlatDbRowsModel
{
CompanyId = 2,
CompanyName = "Company 2",
IndustryId = 4,
Industry = "Industry 4"
},
};
var result = data.GroupBy(x => x.CompanyId)
.Select(x => new CompanyModel()
{
CompanyId = x.Key,
CompanyName = x.First().CompanyName,
Industires = x.Select(y=> new IndustryModel
{
IndustryName = y.Industry,
IndustryId = y.IndustryId
}).ToList()
}).ToList();
foreach (var item in result)
{
var text = $"Company id : {item.CompanyId}, industries : {string.Join(',',item.Industires.Select(x=>$"(name: {x.IndustryName}, id: {x.IndustryId})"))}";
Debug.WriteLine(text);
}
}
}
output:
Company id : 1, industries : (name: Industry 1, id: 1),(name: Industry 2, id: 2)
Company id : 2, industries : (name: Industry 3, id: 3),(name: Industry 4, id: 4)
edit:
alternatively you can do as below, however the "first" thing still occurs somewhere, I have tried also the GroupJoin but it doesn't really help in that case.
var otherResult = data.Select(x =>
new CompanyModel
{
CompanyId = x.CompanyId,
CompanyName = x.CompanyName,
Industires = data
.Where(y => y.CompanyId == x.CompanyId)
.Select(y => new IndustryModel
{
IndustryId = y.IndustryId,
IndustryName = y.Industry
}).ToList()
})
.GroupBy(y => y.CompanyId)
.Select(x => x.First())
.ToList();
edit:
one more approach without using "first"
var anotherResult = data.GroupBy(x => x.CompanyId)
.Select(x =>
{
var companyModel = new CompanyModel()
{
CompanyId = x.Key
};
companyModel.Industires = x.Select(y =>
{
companyModel.CompanyName = y.CompanyName; // assignign here occurs multiple times however with the same value
return new IndustryModel
{
IndustryId = y.IndustryId,
IndustryName = y.Industry
};
}).ToList();
return companyModel;
}).ToList();
I have an object:
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
}
I return a list that may look like the following:
List<Customer> CustomerList = new List<Customer>();
CustomerList.Add( new Customer { ID = 1, Name = "One", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 2, Name = "Two", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 3, Name = "Three", GroupID = 2 } );
CustomerList.Add( new Customer { ID = 4, Name = "Four", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 5, Name = "Five", GroupID = 3 } );
CustomerList.Add( new Customer { ID = 6, Name = "Six", GroupID = 3 } );
I want to return a linq query which will look like
CustomerList
GroupID =1
UserID = 1, UserName = "UserOne", GroupID = 1
UserID = 2, UserName = "UserTwo", GroupID = 1
UserID = 4, UserName = "UserFour", GroupID = 1
GroupID =2
UserID = 3, UserName = "UserThree", GroupID = 2
GroupID =3
UserID = 5, UserName = "UserFive", GroupID = 3
UserID = 6, UserName = "UserSix",
I tried from
Using Linq to group a list of objects into a new grouped list of list of objects
code
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID)
.Select(grp => grp.ToList())
.ToList();
works but does not give the desired output.
var groupedCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(grp =>new { GroupID =grp.Key, CustomerList = grp.ToList()})
.ToList();
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>{
u.Name = "User" + u.Name;
return u;
}, (key,g)=>g.ToList())
.ToList();
If you don't want to change the original data, you should add some method (kind of clone and modify) to your class like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public Customer CloneWithNamePrepend(string prepend){
return new Customer(){
ID = this.ID,
Name = prepend + this.Name,
GroupID = this.GroupID
};
}
}
//Then
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>u.CloneWithNamePrepend("User"), (key,g)=>g.ToList())
.ToList();
I think you may want to display the Customer differently without modifying the original data. If so you should design your class Customer differently, like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public string Prefix {get;set;}
public string FullName {
get { return Prefix + Name;}
}
}
//then to display the fullname, just get the customer.FullName;
//You can also try adding some override of ToString() to your class
var groupedCustomerList = CustomerList
.GroupBy(u => {u.Prefix="User", return u.GroupID;} , (key,g)=>g.ToList())
.ToList();
is this what you want?
var grouped = CustomerList.GroupBy(m => m.GroupID).Select((n) => new { GroupId = n.Key, Items = n.ToList() });
var result = from cx in CustomerList
group cx by cx.GroupID into cxGroup
orderby cxGroup.Key
select cxGroup;
foreach (var cxGroup in result) {
Console.WriteLine(String.Format("GroupID = {0}", cxGroup.Key));
foreach (var cx in cxGroup) {
Console.WriteLine(String.Format("\tUserID = {0}, UserName = {1}, GroupID = {2}",
new object[] { cx.ID, cx.Name, cx.GroupID }));
}
}
The desired result can be obtained using IGrouping, which represents a collection of objects that have a common key in this case a GroupID
var newCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(group => new { GroupID = group.Key, Customers = group.ToList() })
.ToList();