I have the following basic classes (cut down for this question):
public class Parent
{
public string Name { get; set; }
public IList<Child> Children { get; set; }
}
public class Child
{
public string Name { get; set; }
}
If I have a Parent collection, what I'd like to do is get an IList that is sorted by Parent.Name and also the Children for each parent need to be sorted by their Name.
I've tried this (which only sorts the Parents, not the Children):
IList<Parent> parents = ... //Populated
parents.OrderBy(p => p.Name).ThenBy(p => p.Children.OrderBy(c => c.Name)).ToList()
I've searched but can't find anything (probably me being dumb).
Any suggestions for a Linq newbie?
Thanks in advance
Andy
First of all, calling OrderBy on the list, the way you do, won't sort it in-place. It will return a new sorted IEnumerable; you can use .ToList() on that to turn it into a list, but it will still be a copy. Now on to the sorting itself. You really need to not just order the items in the collection, but make a copy of each item which would have its Children sorted as well. So:
IList<Parent> parents = ... //Populated
parents = (from p in parents
orderby p.Name
select new Parent
{
Name = p.Name,
Children = p.Children.OrderBy(c => c.Name).ToList()
}
).ToList();
Same solution, using LINQ method syntax:
IList<MyType> myTypeList = ... //Populated
var sortedList = myTypeList.Select(t =>
{
t.Children = t.Children.OrderBy(c => c.Name).ToList();
return t;
}).ToList();
Related
I have a list of objects within a list of objects (List-ParentClass) that has as one of its objects a nested list (List-ChildClass). To populate List-ChildClass I have used a foreach loop as shown below. I have also nested a linq query as show below.
At this point I am having some performance issues and I feel like there is a better way to do this that I am just not finding.
Question: How could I do this better/faster?
Note - This is a Web based .net MVC application written in C#. I use EF back to a SQL database.
public class ParentClass
{
public int pcid { get; set; }
public List<ChildClass> ChildClassList { get; set; }
}
public class ChildClass
{
public int pcid { get; set; }
public int ccid { get; set; }
}
public class DoWork
{
public void ExampleMethodForEach()
{
List<ParentClass> ParentClassList = new List<ParentClass>();
foreach(ParentClass a in ParentClassList)
{
a.ChildClassList = EFDatabase2.where(b => b.pcid == a.pcid).select(b => b.ccid).ToList();
}
}
public void ExampleMethodLinq()
{
var ParentClassList = (from a in EFDatabase
select new ParentClass
{
ccid = a.ccid,
pcid = (from b in EFDatabase2
where b.pcid == a.pcid
select b.ccid).ToList()
//something like this were I nest a query
}).ToList();
}
}
The best way when working with relational databases and LINQ is to use joins to correlate data. In your case, the most appropriate is group join:
var ParentClassList =
(from p in EFDatabase
join c in EFDatabase2 on p.pcid equals c.pcid into children
select new ParentClass
{
pcid = p.pcid,
ChildClassList =
(from c in children
select new ChildClass
{
pcid = c.pcid,
ccid = c.ccid
}).ToList()
}).ToList();
which should give you a nice fast single database query.
P.S. Hope your EFDatabase and EFDatabase2 variables refer to two tables inside one and the same database.
You are hitting your database multiple times. You have a N+1 issue.
What I suggest is to query all parents first, but excluding the children data. Then get the ID of all parents that you retrieved and put it inside an array. We will use that array to create a IN clause in SQL.
After loading all the children using the array of parent IDs, map them to a Lookup using ToLookup using the parent ID as the key and use a foreach to assign the list of children to the parent.
var parents = EFDatabase2.Parents.Where(...).Select(p => new ParentClass { pcid = p.pcid }).ToList();
var ids = parents.Select(p => p.pcid).ToArray();
var children = EFDatabase2.Children.Where(c => ids.Contains(c.ccid)).Select(c => new ChildClass { pcid = c.pcid, ccid = c.ccid }).ToLookup(c => c.pcid);
foreach (var parent in parents)
{
parent.Children = children[parent.pcid];
}
In this case, you will only do two queries to your database.
In my data structures I have the following classes:
public partial class Item
{
// stuff
public int QuoteId { get; set; }
public virtual ItemType ItemType { get; set; }
}
public partial class ItemType
{
//stuff
public virtual ICollection<Item> Items { get; set; }
}
What I want to do is get a list of all the ItemTypes, each of which has its Items collection populated according to a QuoteId.
So, for example if there are three item types, only two of which have items with a quote Id of 50:
ItemType1
Item.QuoteId == 50
ItemType2
ItemType3
Item.QuoteId == 50
I've managed to get something close with this query:
r.ItemTypes.Select(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId));
But what this gives you (as you might expect, since I'm Selecting on Item) is an IEnumerable<IEnumerable<Item>>. This has the structure that I'm after but doesn't have the ItemType data.
I realise this is a dumb question, but I'm frustrated by my inability to get the answer.
r.ItemTypes.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
If you need to get all ItemTypes and only specific Items for every, you can do this:
r.ItemTypes.Select(x => new
{
x,
FilteredItems = x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
});
After that you need to assign x.Items to FilteredItems for every ItemType
You have to select the Item.ItemType property if you want the all ItemTypes of a given QuoteId. You also have to use SelectMany to flatten the "nested" collections:
IEnumerable<ItemType> types = r.ItemTypes
.SelectMany(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
.Select(i => i.ItemType));
If you are not interested in the nested ItemType(don't know the logic) you can use Backs' approach:
IEnumerable<ItemType> types = r.ItemTypes
.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
var result = from itemTypes in r.ItemTypes
where itemTypes.QuoteId equals CurrentQuote.QuoteId
select new {itemType = itemTypes}
I have a parent child relationship, something like:
public class MyType {
public IList<MyType> Children {get;set;}
public int Order {get;set;}
}
I would like to select the list so that each level is in order.
I can do this easily for the top level:
mylist.Children.OrderBy(x => x.Order)
But how do I do it for every set of Children?
The end result would be the list or type with all its children, and there children (and so on) all sorted correctly by Order.
Thanks.
You can do Recursive Order by adding one method to MyType like this:
public class MyType
{
public IList<MyType> Childrens { get; set; }
public int Order { get; set; }
public void RecursiveOrder()
{
Childrens = Childrens.OrderBy(x => x.Order)
.ToList();
Childrens.ToList().ForEach(c => c.RecursiveOrder());
}
}
You can keep the children sorted if you use a SortedList as your underlying children collection. Then you can expose the Values property to get the values. Just key the items by their order when you add to the list.
e.g.,
public class MyType
{
public MyType(int order)
{
this.order = order;
}
private int order;
private SortedList<int, MyType> children = new SortedList<int, MyType>();
public int Order { get { return order; } }
public IList<MyType> Children { get { return children.Values; } }
public void AddChild(MyType child)
{
children.Add(child.order, child);
}
}
Otherwise, you'd probably want to sort the lists recursively. Using LINQ wouldn't be appropriate here. At best, LINQ would allow you to iterate over the children in a sorted order but it doesn't actually sort the underlying list unless you replace the list instance with the sorted version. If the underlying list has a Sort() method (which the generic List<T> has), then use that.
private List<MyType> children;
public void EnsureSorted()
{
children.Sort();
foreach (var child in children)
child.EnsureSorted();
}
Starting off with a sorted list would be a lot easier however.
I agree with Jeff that the easiest answer is to store the data sorted if that is going to be your main access pattern. But lets say you really want to do this with Linq:
First off, if you knew you only wanted two levels of ordering, you could do something like this:
myList.Children.OrderBy(x => x.Order)
.Select(c => c.Children.OrderBy(x => x.Order))
But what if what you really want is totally recursive ordering, all the way down?
delegate IEnumerable<MyType> RecursiveFunc(MyType data, RecursiveFunc self);
RecursiveFunc op = (data, func) => data.Children.OrderBy(x => x.Order)
.Select(x => func(x, func));
IEnumerable<MyType> result = op(myList, op);
Just writing that makes my brain hurt, and I haven't tried running it, so best of luck! What it comes down to is passing a linq expression(lambda) to itself, to recursively apply itself down the tree.
thry this
mylist.Children.OrderBy(x => x.Order).ThenBy( x => x.order).ToList();
If the end result simply has to be a list of children (instead of a list of parents with nested children), you can use a SelectMany
IEnumerable<Child> result = parents
.SelectMany(p => p.Children)
.OrderBy(child => child.Order);
Consider this,
class Item
{
public string ID { get; set;}
public string Description { get; set; }
}
class SaleItem
{
public string ID { get; set;}
public string Discount { get; set; }
}
var itemsToRemoved = (List<Item>)ViewState["ItemsToRemove"];
// get only rows of ID
var query = from i in itemsToRemoved select i.ID;
var saleItems= (List<SaleItem>)ViewState["SaleItems"];
foreach (string s in query.ToArray())
{
saleItems.RemoveItem(s);
}
How can I write this LINQ phrase using IEnumerable/List Extension methods
// get only rows of ID
var query = from i in items select i.ID;
thanks in advance.
That one's easy:
var query = items.Select(i => i.ID);
A select clause always corresponds to a call to Select. Some of the other operators end up with a rather more complex expansion :) If you work hard, you can get the compiler to do some very odd stuff...
You can find all the details of this and other query expression translations in section 7.16 of the C# specification (v3 or v4).
<plug>
You could also buy C# in Depth, 2nd edition and read chapter 11 if you really wanted to :)</plug>
You can use this:
var query = items.Select(i => i.ID);
A couple of other points:
Here you don't need the call to ToArray:
foreach (string s in query.ToArray())
Also if your list is large and you are removing a lot of items you may want to use List.RemoveAll instead of iterating. Every time you remove an item from a list all the other items after it have to be moved to fill the gap. If you use RemoveAll this only has to be done once at the end, instead of once for every removed item.
List<Item> itemsToRemove = (List<Item>)ViewState["ItemsToRemove"];
HashSet<string> itemIds = new HashSet<string>(itemsToRemove.Select(s => s.ID));
saleItems.RemoveAll(c => itemIds.Contains(c.ID));
public static class ItemCollectionExtensions
{
public static IEnumerable<int> GetItemIds(this List<Item> list)
{
return list.Select(i => i.ID);
}
}
Imagine you've got some Entity Framework entities that look like this (obviously not these specific classes, but the autogenerated ones with all the Entity Framework plumbing; these are just for illustration):
public class Parent
{
public int ID { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public Parent Parent { get; set; }
public int Number { get; set; }
}
I have a LINQ query that looks like this:
from parent in context.Parents.Include("Child")
select parent
However, this returns a list of Parents where the children are in ID order. I want the children to be sorted by their Number property within their Parent.
How can this be done?
Edit: A clarification: the idea is to have the query hidden behind a method call (in the layer facade) that simply returns an IList<Parent>. This makes using solutions like anonymous class queries and manual sorting painful (compared to some panacea solution where you can just do it in the query or something).
Alex James discusses this issue in this tip.
Essentially, relationships are considered as unordered, per standard relational modeling. So you can't get them sorted. But you can project onto other collections, which can be sorted.
Take a look at this post. You could try something like this:
var query = ((from parent in context.Parents
from child in parent.Child
orderby child.Number ascending
select parent) as ObjectQuery<Parent>
).Include("Child");
One option is executing the query and sorting in memory (e.g. on output).
var parents = context.Parents.Include("Child").ToList(); //note that ToList is here just to execute the query and get the objects in memory
foreach (var p in parents)
{
//do something with parent item
foreach (var c in p.Child.OrderBy(c => c.Number))
{
/do something with the child item
}
}
There are two other options that also seem to work with their own pros and cons:
LINQ ".Include" orderby in subquery
LINQ OrderBy Name ThenBy ChildrenCollection.Name
here is something that I have done:
var query = from parent in context.Parents
select new
{
parent,
childs = from child in context.Child
orderby child.ID ascending
select new
{
child
}
}
I implememented something like this and it worked very well for me