Author |
Share Topic Topic Search Topic Options
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Topic: Sorting Includes Posted: 13-Aug-2009 at 11:50am |
So I have a query:
var query = _context.Orders.Include("OrderDetails"); |
I would like to sort the Order Details within each order by the OrderDetailNumber field, which is an integer from 1 to the number of lines. Its purpose is to keep the lines in a predictable order on screen and on paper.
How do I sort the Order Details properly?
Thanks,
Simon
|
|
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
Posted: 14-Aug-2009 at 2:26pm |
Here's an example that sorts OrderDetails by descending UnitPrice.
var query = _context.Orders.Include("OrderDetails").Select(o => new { o, SortedOrderDetails = o.OrderDetails.OrderByDescending(od => od.UnitPrice) });
|
Edited by eileenv - 14-Aug-2009 at 2:27pm
|
|
midnit
Senior Member
Joined: 22-Jun-2009
Location: Charlotte
Posts: 112
|
Post Options
Quote Reply
Posted: 15-Aug-2009 at 12:49am |
That returns you a list of {o, SortedOrderDetails} correct?
o.OrderDetails is still unsorted and I see no straight-forward way to make it so. How do you make the RelatedEntityList load sorted?
|
|
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
Posted: 15-Aug-2009 at 1:37am |
OrderDetails is sorted. For each Order, the OrderDetails associated with that Order is sorted by UnitPrice.
|
|
midnit
Senior Member
Joined: 22-Jun-2009
Location: Charlotte
Posts: 112
|
Post Options
Quote Reply
Posted: 19-Aug-2009 at 6:42am |
I am unable to get this to work but then again LINQ and Entity are new to me. Please correct this query.
var q = from item in Manager.Orders
.Include("OrderDetails").Select(o => new { o, SortedOrders = o.OrderDetails.OrderBy(od => od.UnitPrice) })
.Include("CustomerDetails")
orderby item.LastUpdateTms descending
select item;
I would like the result of the query to be a list of orders sorted by lastupdatetms, and each order to have its order details sorted on unitprice. Please show me the error of my ways!
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 19-Aug-2009 at 6:59am |
So I wrote this test, and it won't compile. The error is:
Error 1 Cannot implicitly convert type 'IdeaBlade.EntityModel.IEntityQuery<AnonymousType#1>' to 'IdeaBlade.EntityModel.IEntityQuery<GcsAg.DomainModel.Deal>'. An explicit conversion exists (are you missing a cast?)
What am I doing wrong? The syntax seems to be the same as eileenv's recommendation.
Here's the test:
[TestMethod]
public void TestSortingDealPricing()
{
//in the database, this deal has 2 pricings
//8052 is for 2001-4000 volume
//8053 is for 1-2000 volume
//these need to be sorted by volume in the deal object
Deal deal = Deal.Fetch(10094);
//first, lets assert that they aren't sorted properly
Assert.AreEqual(2, deal.DealPricings.Count);
Assert.AreEqual(8052, deal.DealPricings[0].DealPricingId);
Assert.AreEqual(8053, deal.DealPricings[1].DealPricingId);
//ok. that worked. now we need to figure out how to get those items
//loaded and sorted properly
//let's try a straight load
var em = LocalEntityManager.DefaultManager;
em.Clear();
var qry = em.Deals
.Include(Deal.EntityPropertyNames.DealPricings)
.Where(item => item.DealId == 10094)
.With(QueryStrategy.DataSourceOnly);
Deal deal2 = qry.Execute().First();
Assert.AreEqual(2, deal2.DealPricings.Count);
Assert.AreEqual(8052, deal2.DealPricings[0].DealPricingId);
Assert.AreEqual(8053, deal2.DealPricings[1].DealPricingId);
//so that didn't sort them (no surprise there)
//let's try the recommendation from eileenv
//http://www.ideablade.com/forum/forum_posts.asp?TID=1423
em.Clear();
qry = em.Deals
.Include(Deal.EntityPropertyNames.DealPricings)
.Where(item => item.DealId == 10094)
.Select(item => new {item, SortedDealPricings
= item.DealPricings.OrderByDescending(dp => dp.VolumeMin)})
.With(QueryStrategy.DataSourceOnly);
Deal deal3 = qry.Execute().First();
Assert.AreEqual(2, deal3.DealPricings.Count);
//note: the details are reversed here
Assert.AreEqual(8053, deal3.DealPricings[0].DealPricingId);
Assert.AreEqual(8052, deal3.DealPricings[1].DealPricingId);
} |
Edited by skingaby - 19-Aug-2009 at 7:02am
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 19-Aug-2009 at 7:11am |
Interesting. Your example returns a totally different type. So I fixed my code to handle that. Now it still won't compile because the Execute is no longer returning a Deal entity. What do I need to do to get this test to pass?
em.Clear();
var qry2 = em.Deals
.Include(Deal.EntityPropertyNames.DealPricings)
.Where(item => item.DealId == 10094)
.Select(item => new
{
item,
SortedDealPricings
= item.DealPricings.OrderByDescending(dp => dp.VolumeMin)
})
.With(QueryStrategy.DataSourceOnly);
Deal deal3 = qry2.Execute().First(); |
Edited by skingaby - 19-Aug-2009 at 7:13am
|
|
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
Posted: 19-Aug-2009 at 11:44am |
Sorry I wasn't clear on how to retrieve the SortedOrderDetails.
var query = _context.Orders
.Include("OrderDetails")
.Select(o => new { o, SortedOrderDetails = o.OrderDetails.OrderByDescending(od => od.UnitPrice) });
var orderDetails = query.ToList().Select(o => o.SortedOrderDetails);
|
Or, here is an even better way...
Define a property in the Order class:
Order.cs:
public IEnumerable<OrderDetail> SortedOrderDetails {
get { return this.OrderDetails.OrderByDescending(od => od.UnitPrice); }
}
|
This way you can query for Orders the regular way and return either sorted or unsorted results:
var query = _context.Orders.Include("OrderDetails");
var order = query.ToList().First();
// get sorted
var sortedOrderDetails = order.SortedOrderDetails;
// get unsorted
var unsortedOrderDetails = order.OrderDetails;
|
Edited by eileenv - 19-Aug-2009 at 5:56pm
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 20-Aug-2009 at 10:09am |
Neither of these seems very satisfactory.
I will have to change a lot of code that already works fine if I create an Order.OrderDetailsSorted property. I.e. All the code (and UI binding - urgh) that currently refers to OrderDetails will now have to refer OrderDetailsSorted. (Yeuch)
The second approach with
.Select(o => new { o, SortedOrderDetails = o.OrderDetails.OrderByDescending(od => od.UnitPrice) });
is only useful if I want to get the order details as a separate collection, not as part of an order, as in:
var orderDetails = query.ToList().Select(o => o.SortedOrderDetails);
The entity navigation properties are read only, right? So I can't say:
order.OrderDetails = order.OrderDetails.OrderByDesc(...)
Is there a method that will re-order the members of the collection within that collection? So instead I can say, call a sort method like this:
order.OrderDetails.Sort(...);
|
|
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
Posted: 20-Aug-2009 at 2:20pm |
The .Select statement projects the query results into a new collection consisting of Orders and their respective OrderDetails.
Lets create a custom type so can view the result set better.
public class MyType {
public Order MyOrder { get; set; }
public IEnumerable<OrderDetails> SortedOrderDetails { get; set; }
}
The .Select statement then becomes:
.Select(o => new MyType { MyOrder = o, SortedOrderDetails = o.OrderDetails.OrderByDescending(od => od.UnitPrice) });
If you were to retrieve the first item in the collection you would see both MyOrder and its SortedOrderDetails:
MyType firstResult = query.ToList().First();
var order = firstResult.MyOrder;
var orderdetail = firstResult.SortedOrderDetails;
|
|
midnit
Senior Member
Joined: 22-Jun-2009
Location: Charlotte
Posts: 112
|
Post Options
Quote Reply
Posted: 20-Aug-2009 at 2:44pm |
So basically you get a compound collection? Whereby the SortedOrderDetails is completed unrelated to the Order except that they are at the same index in this collection.
IMHO this seems kludgy. Why is there an imposed limitation to sorting a RelatedEntityList? To truely do what we want, which is to receive an Order where the .OrderDetails is sorted, do we need to do a POCO query? Or is there no way to accomplish what we really want? If having a custom object or additional property is the solution wouldn't it be just as easy (actually easier) to use a CollectionViewSource and map it to the already obtained Order.OrderDetails?
|
|
eileenv
IdeaBlade
Joined: 15-Jun-2007
Location: United States
Posts: 68
|
Post Options
Quote Reply
Posted: 20-Aug-2009 at 2:50pm |
The query I proposed does the retrieving of the Orders and the sorting of the related OrderDetails all in a single query.
Alternatively you can just retrieve the Orders with an include of OrderDetails. Then iterate through the result set and call order.OrderDetails.OrderByDesc(...).
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 20-Aug-2009 at 4:10pm |
Does order.OrderDetails.OrderByDesc(...) do the sort without having to assign it to another variable?
This fails:
Deal deal3 = qry.Execute().First();
deal3.DealPricings.OrderByDescending((x) => x.VolumeMin);
Assert.AreEqual(2, deal3.DealPricings.Count);
//note: the details are reversed here
Assert.AreEqual(8053, deal3.DealPricings[0].DealPricingId);
Assert.AreEqual(8052, deal3.DealPricings[1].DealPricingId); |
Edited by skingaby - 20-Aug-2009 at 4:33pm
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 20-Aug-2009 at 4:34pm |
Oh crap. I'm an idiot. I'm sorting OrderByDescending. I just need OrderBy. OOOPS!
However, this still fails:
Deal deal3 = qry.Execute().First();
deal3.DealPricings.OrderBy((x) => x.VolumeMin);
Assert.AreEqual(2, deal3.DealPricings.Count);
//note: the details are reversed here
Assert.AreEqual(8053, deal3.DealPricings[0].DealPricingId);
Assert.AreEqual(8052, deal3.DealPricings[1].DealPricingId);
|
While this succeeds:
var sortedPrices = deal3.DealPricings.OrderBy((x) => x.VolumeMin);
Assert.AreEqual(2, sortedPrices.Count());
//note: the details are reversed here
Assert.AreEqual(8053, sortedPrices.ElementAt(0).DealPricingId);
Assert.AreEqual(8052, sortedPrices.ElementAt(1).DealPricingId); |
Edited by skingaby - 20-Aug-2009 at 5:14pm
|
|
midnit
Senior Member
Joined: 22-Jun-2009
Location: Charlotte
Posts: 112
|
Post Options
Quote Reply
Posted: 21-Aug-2009 at 7:18am |
Originally posted by midnit
To truely do what we want, which is to receive an Order where the .OrderDetails is sorted, do we need to do a POCO query? Or is there no way to accomplish what we really want? |
Is the answer here that it cannot be done? I agree there are several ways to "work around" the problem but is there a way to solve the problem with the DESIRED results. Those results being that the actual items inside Order.OrderDetails are sorted? None of the things discussed in this thread actually answer the original question but it has not yet been said "you cant do that".
Can you populate Order.OrderDetails with sorted results? If so, how. If not, thank you for the info and we will work around this limitation.
|
|
skingaby
DevForce MVP
Joined: 23-Apr-2008
Location: United States
Posts: 146
|
Post Options
Quote Reply
Posted: 26-Aug-2009 at 6:35am |
Can you please help with this? Is there any way that we can use the Collection.Sort() method, or some other method, that actually sorts the items inside the collection, without having to return the sorted items into a different collection?
|
|