Convert IQueryable to IOrderedQueryable - c#

I have 2 tables : Items and ItemMetrics. Items contains my items and ItemMetrics contains statistics about those items.
I'm trying to sort a list of Items by the corresponding statistic Weight.
I got it to work using a subquery, but I thought it was a little inefficent and could be optimized...
items.OrderBy(item => (from metric in Context.ItemMetrics where item.ItemId == metric.ItemId select metric.Weight).FirstOrDefault());
...So I am trying to accomplish the same thing using a join...
from item in items join metric in ItemMetrics on item.ItemId equals metric.ItemId orderby metric.Weight select item;
This is working fine in LINQPad. I need to return an IOrderedQueryable for a supplmentary method to do some paging using Skip and Take, but the compiler is telling me the result of this query is an IQueryable.
The only way I can think of explictly implying IOrderedQueryable is wrapping the query in
OrderBy(x => 0)
Is there a better way?

The problem is that an IOrderedQueryable needs to be able to apply a secondary ordering - and by the time you've projected the item/metric pair to just an item, you've lost the primary ordering.
One approach would be to defer the projection until later:
var ordered = items.Join(ItemMetrics,
item => item.ItemId,
metric => metric.ItemId,
(item, metric) => new { item, metric })
.OrderBy(pair => pair.Metric.Weight);
var paged = ApplyPaging(ordered, pageConfiguration); // Whatever
var query = paged.Select(pair => pair.Item);
That's assuming ApplyPaging (or whatever you're using) returns an IQueryable or an IOrderedQueryable.

Related

Linq to entities query that returns a count and a list of objects

Is it possible to create a linq to entities query that will give me the total count of things in a query, along with a list of some of those items?
For example if the query unconstrained would return 40,000 items but I just want the 5th page of 500 items the object would say Count = 40,000 and List<items> would contain just 500 items.
Here is code that returns the specific customer's # of orders, and then a 'page' of those orders. (This will retrieve orders 501 through 600.)
Customer[] CustomerList;
int CustomerID;
var x = CustomerList
.Where(r => r.CustomerID == CustomerID)
.Select(r => new {
OrderCount = r.Orders.Count(),
OrderPageList = r.Orders.Skip(500).Take(100).ToArray() });
int totalCount = x.OrderCount;
Order[] orderList = x.OrderPageList;
Yes it's possible (another example here: Better way to query a page of data and get total count in entity framework 4.1?), but not recommended. It usually takes contrived query constructs to get the count and the data in one LINQ statement (David's answer evades that by only selecting child items).
You better create an IQueryable and get its count and its paged subset separately.
Even better, add PagedList to your project and use code like:
var prd = Products.Where(p => p.Categories.Any(c => c.CategoryName == "Sports" ));
var page = prd.OrderBy(p => p.Name).ToPagedList(5,500);
This simply executes the count and a Skip/Take query to get page 5 of all pages containing 500 items.
The result, an IPagedList<T>, is an IEnumerable<T> that has some useful properties like HasNextPage or PageCount.

LINQ to Entities does not recognize the method Generic.List(int) to Generic.IEnumerable(int) method

The database contains Orders.
Orders can be contained within a group of Orders.
For every group of Orders it could contain 1 to many Orders.
However, Orders could have a NULL value assigned GroupOrderId as previous Orders did not have the grouping concept. Only new Orders enforce the concept of being added to a group.
The class structure to be populated in order to perform actions on each Order is
public class OrdersGroup
{
public int? GroupOrderId { get; set; }
public List<int> OrderIds { get; set; }
}
The linq statement
var workPacketOrdersList = (from o in db.Orders
where
o.GroupOrderId >= groupOrderIdMin && o.GroupOrderId <= groupOrderIdMax &&
o.IsDeleted == false
orderby o.WorkPacketId ascending
group o by o.WorkPacketId
into grp
select new OrdersGroup
{
GroupOrderId = grp.Key,
OrderIds = grp.Select(g => g.OrderId).ToList()
}).ToList();
Full exception
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[System.Int32] ToList[Int32](System.Collections.Generic.IEnumerable`1[System.Int32])' method, and this method cannot be translated into a store expression.
I see that the returned type of the linq query is a List<OrdersGroup>.
If the final .ToList() is omitted from the query than the return type becomes an IQueryable<OrdersGroup>
No matter what action is performed next the result is an exception that this method cannot be translated into a store expression.
I have tried to remove the specific select new OrdersGroup into a more generic select new and then perform actions on this result only to find the same store expression exception.
Can someone give some insight into where this linq is incorrect?
this is the part that's failing - grp.Select(g => g.OrderId).ToList() - you can't have a .ToList() in the select clause. remove that and you should be fine.
The problem is that LINQ to Entities is attempting to convert your query into SQL. It doesn't know how translate ToList into SQL, so that's the problem. You need to remove the call to ToList from inside your query.
That is,
OrderIds = grp.Select(g => g.OrderId).ToList()
LINQ to Entities can not convert that to SQL. Remove the call
OrderIds = grp.Select(g => g.OrderId)
and if you need OrderIds to be a List<int>, do the call to ToList after you execute the query.
It's because you're trying to call ToList() in a part of the query that will become raw SQL and executed at the source (ie SQL Server, not the CLR). I don't know exactly what your data is so I can't necessarily make an accurate recommendation on how to fix it but I would try taking making the ToList() call after this query or just not making it all. It's likely IEnumberable will offer whatever functionality you need which is what the Select will return if you remove the ToList() call.
By the way since I wasn't explicit, I'm referring to the ToList() call inside the select -(second to last line) OrderIds = grp.Select(g => g.OrderId).ToList() the other one is fine. It's executed on the results of the SQL query which is perfectly fine, you just can't make calls to C# specific methods within a query that will be executed by the SQL provider.
Your problem is that you select a list in your select statement.
select new OrdersGroup
{
GroupOrderId = grp.Key,
OrderIds = grp.Select(g => g.OrderId).ToList()
/////////////////////////////////////^^^^^^^^^HERE
}
What you need to do is change OrderIds to an IEnumerable<int>, and then get rid of the ToList.

LINQ to SQL where collection contains collection

I have a problem :( I have a many-many table between two tables(1&2), via a mapping table(3):
(1)Trees / (2)Insects
TreeID <- (3)TreeInsects -> InsectID
And then a one to many relationship:
Trees.ID -> Leaves.TreeID
And I would like to perform a query which will give me all the Leaves for a collection of insects (via trees-insect mapping table).
E.g. I have List<Insects> and I want all Leaves that have an association with any of the Insects in the List via the Tree-Insects mapping table.
This seems like a simple task, but for some reason I'm having trouble doing this!!
The best I have: but the Single() makes it incorrect:
from l in Leaves
where (from i in Insects
select i.ID)
.Contains((from ti in l.Tree.TreeInsects
select ti.InsectID).Single())
select l;
(from i in insectsCollection
select from l in Leaves
let treeInsectIDs = l.Tree.TreeInsects.Select(ti => ti.InsectID)
where treeInsectIDs.Contains(i.ID)
select l)
.SelectMany(l => l)
.Distinct();
I'm bad with sql-like syntax so I'll write with extensions.
ctx.Leaves.Where(l => ctx.TreeInsects.Where( ti => list_with_insects.Select(lwi => lwi.InsectID).Contains( ti.InsectID ) ).Any( ti => ti.TreeID == l.TreeID ) );
Try investigating the SelectMany method - I think that may be the key you need.
I would get a list of Trees that are available to that Insect, then peg on a SelectMany to the end and pull out the collection of Leaves tied to that Tree.
List<int> insectIds = localInsects.Select(i => i.ID).ToList();
//note - each leaf is evaluated, so no duplicates.
IQueryable<Leaf> query =
from leaf in myDataContext.Leaves
where leaf.Tree.TreeInsects.Any(ti => insectIds.Contains(ti.InsectId))
select leaf;
//note, each matching insect is found, then turned into a collection of leaves.
// if two insects have the same leaf, that leaf will be duplicated.
IQueryable<Leaf> query2 =
from insect in myDataContext.Insects
where insectIds.Contains(insect.ID)
from ti in insect.TreeInsects
from leaf in ti.Tree.Leaves
select leaf;
Also note, Sql Server has a parameter limit of ~2100. LinqToSql will happily generate a query with more insect IDs, but you'll get a sql exception when you try to run it. To resolve this, run the query more than once, on smaller batches of IDs.
How do you get this list of insects? Is it a query too?
Anyway, if you don't mind performance (SelectMany can be slow if you have a big database), this should work:
List<Insect> insects = .... ; //(your query/method)
IEnumerable<Leave> leaves = db.TreeInsects
.Where(p=> insects.Contains(p.Insect))
.Select(p=>p.Tree)
.SelectMany(p=>p.Leaves);

How to Group and Order in a LINQ Query

I would like to group & order by in a query builder expression. The following query gets me close to what i want but the order by does not appear to be working.
what i have is an object that has unique ids but some will have a common versionId. I would like to get the last edited item of the same versionId. So only one item per version id and i want it to be the last edited one.
IQueryable<Item> result = DataContext.Items.Where(x => (x.ItemName.Contains(searchKeyword) ||
x.ItemDescription.Contains(searchKeyword))
.GroupBy(y => y.VersionId)
.Select(z => z.OrderByDescending(item => item.LastModifiedDateTime).FirstOrDefault());
Edit: I don't really care about the order of the result set, i really just care about what item within the grouping is returned. I want to make sure that the last edited Item within a versionId group is return.
Your z parameter contains the individual group objects.
By calling OrderBy inside of Select, you're ordering the items in each group, but not the groups themselves.
You need to also call OrderBy after Select, like this:
.Select(z.OrderByDescending(item => item.LastModifiedDateTime).FirstOrDefault())
.Where(item => item != null)
.OrderByDescending(item => item.LastModifiedTime)

LINQ: Doing an order by!

i have some Linq to Entity code like so:
var tablearows = Context.TableB.Include("TableA").Where(c => c.TableBID == 1).Select(c => c.TableA).ToList();
So i'm returning the results of TableA with TableB.TableBID = 1
That's all good
Now how can I sort TableA by one of its column? There is a many to many relation ship between the two tables
I tried various ways with no look, for example
var tablearows = Context.TableB.Include("TableA").Where(c => c.TableBID == 1).Select(c => c.TableA).OrderBy(p => p.ColumnToSort).ToList();
In the above case when i type "p." i don't have access to the columns from TableA, presumably because it's a collection of TableA objects, not a single row
How about using SelectMany instead of Select :
var tablearows = Context.TableB.Include("TableB")
.Where(c => c.TableBID == 1)
.SelectMany(c => c.TableA)
.OrderBy(p => p.ColumnToSort)
.ToList();
EDIT :
The expression below returns collection of TableAs -every element of the collection is an instance of TableA collection not TableA instance- (that's why you can't get the properties of the TableA) :
var tablearows = Context.TableB.Include("TableB")
.Where(c => c.TableBID == 1)
.Select(c => c.TableA);
If we turn the Select to SelectMany, we get the result as one concatenated collection that includes elements :
var tablearows = Context.TableB.Include("TableB")
.Where(c => c.TableBID == 1)
.SelectMany(c => c.TableA);
Okay, so now I've taken on board that there's a many to many relationship, I think Canavar is right - you want a SelectMany.
Again, that's easier to see in a query expression:
var tableARows = from rowB in Context.TableB.Include("TableA")
where rowB.TableBID == 1
from rowA in rowB.TableA
orderby rowA.ColumnToSort
select rowA;
The reason it didn't work is that you've got a different result type. Previously, you were getting a type like:
List<EntitySet<TableA>>
(I don't know the exact type as I'm not a LINQ to Entities guy, but it would be something like that.)
Now we've flattened all those TableA rows into a single list:
List<TableA>
Now you can't order a sequence of sets by a single column within a row - but you can order a sequence of rows by a column. So basically your intuition in the question was right when you said "presumably because it's a collection of TableA objects, not a single row" - but it wasn't quite clear what you mean by "it".
Now, is that flattening actually appropriate for you? It means you no longer know which B contributed any particular A. Is there only actually one B involved here, so it doesn't matter? If so, there's another option which may even perform better (I really don't know, but you might like to look at the SQL generated in each case and profile it):
var tableARows = Context.TableB.Include("TableA")
.Where(b => b.TableBID == 1)
.Single()
.TableA.OrderBy(a => a.ColumnToSort)
.ToList();
Note that this will fail (or at least would in LINQ to Objects; I don't know exactly what will happen in entities) if there isn't a row in table B with an ID of 1. Basically it selects the single row, then selects all As associated with that row, and orders them.

Categories

Resources