Say I have a list static list of Ids in a particular order:
List<int> ordered = new List<int> {7,2,3,4,5};
And I would like to select the items out of the database maintaining that order.
The trivial:
var posts = ( from p in Current.DB.Posts
where ordered.Contains(p.Id)
select p).ToList();
Comes back fast, but out of order.
How do I select these posts out of the db and maintain the order in an elegant and efficient way?
If you don't explicitly include an order-by clause, you only really have a set - any ordering is purely convenience and will usually happen to be on the clustered index - but IIRC this is not guaranteed (and I imagine things like the server choosing to use parallelism would throw this out)
Include an order-by; either at the DB, or at the client. Alternatively, throw the results into a dictionary:
var dict = Current.DB.Posts.Where(p => ordered.Contains(p.Id))
.ToDictionary(p => p.Id);
then you can pull out the one you need at will, ignoring order.
Here's a combination of Marc's answer and your answer:
var dict = Current.DB.Posts.Where(p => ordered.Contains(p.Id))
.ToDictionary(p => p.Id);
return ordered.Select(id => dict[id]).ToList();
Since it omits the OrderBy step, I suspect that it will be a bit more efficient. It's certainly a bit prettier.
We ended up going with:
var reverseIndex = ordered.Select((id, index) => new { Id = id, Index = index }).ToDictionary(pair => pair.Id, s => s.Index);
model.Posts = Current.DB.Posts
.Where(p => postIds.Contains(p.Id))
.AsEnumerable()
.OrderBy(p => reverseIndex[p.Id] )
.ToList();
Ugly, yet reasonably efficient for large lists.
You could project the List<int> onto your list of posts.
var posts = ( from p in Current.DB.Posts
where ordered.Contains(p.Id)
select p).ToList();
return ordered.Select(o => posts.Single(post => post.Id == o)).ToList();
You could also do this at the database retrieval level but you'd be doing multiple select statements
ordered.Select(o => Current.DB.Posts.Single(post => post.Id == o)).ToList();
Related
I have a table of users, grouped into sessions. I would like to select an array for each user that consists of the number of tasks they have in each session:
var taskCounts =
from session in gzClasses.e_userLongSessions
orderby session.workerId ascending, session.startTime ascending
group session by session.workerId
into record
select record.Select(s => s.totalTasks).ToArray();
int[][] result = taskCounts.ToArray();
The above theoretically works, but it results in a separate SQL query for each user, as shown in the image below. Since the database is not local, this takes quite a long time. Is there a way to grab all the data in one query and reduce the overhead of running a bunch of individual queries?
At the same time, I'd like ensure that it's efficient by only transmitting the totalTasks integer values over the wire, instead of sending the entire database record.
Put another way, I'd like to grab a set of grouped integers from a remote database all in one query, and have them arranged into arrays in a C# program. It sounds pretty simple in principle, but I'm having a hard time getting LINQ to do this.
Depending on how many records you get back, you could return a minimal amount of data and do the grouping part in memory (pretty quickly since it'll already be sorted):
Using method syntax:
gzClasses.e_userLongSessions
.OrderBy(s => s.workerId)
.ThenBy(s => s.startTime)
.Select(s => new { s.workerId, s.totalTasks })
.ToList()
.GroupBy(x => x.workerId)
.Select(g => g.Select(x => x.totalTasks).ToArray())
.ToArray();
Using query syntax:
var query = from session in gzClasses.e_userLongSessions
orderby session.workerId ascending, session.startTime ascending
select new { Id = s.workerId, s.totalTasks };
var taskCounts = from worker in query.ToList()
group worker by worker.Id into g
select g.Select(x => x.totalTasks).ToArray();
var result = taskCounts.ToArray();
I see the same behavior in Linqpad (Linq-to-SQL default), but I feel as though I've seen Linq-to-Entities handle a GroupBy followed by a group.Select(x => x.Something) without resulting in an n+1 query...Could be imagining things though (not sure what the SQL would look like to achieve that).
Wouldn't a Dictionary be more useful than an array of arrays?
gzClasses.e_userLongSessions.OrderBy(s => s.workerId)
.ThenBy(s => s.startTime)
.GroupBy(s => s.workerId, t => t.TotalTasks).ToArray()
.ToDictionary(g => g.Key, h => h.ToArray());
That should return a Dictionary, with the workerId as the key, and an array of the number of tasks as the value.
Maybe if you replace "from session in gzClasses.e_userLongSessions" by "from session in gzClasses.e_userLongSessions.AsEnumerable()"?
The position of your ToArray() causes LINQ to be more eager than it should be, resulting in your many queries. I think this will result in just one query:
var taskCounts =
from session in gzClasses.e_userLongSessions
orderby session.workerId ascending, session.startTime ascending
group session by session.workerId
into record
select record.Select(s => s.totalTasks);
int[][] result = taskCounts.Select(x => x.ToArray()).ToArray();
SFC.OrderFormModifiedMonitoringRecords
.SelectMany(q => q.TimeModify, w => w.DateModify)
.Distinct()
.OrderBy(t => t)
.SelectMany(t => new { RowID = t.rowID, OFnum = t.OFNo });
It's Error did i missed something or is it Completely Coded The Wrong Way? After this i'm gonna use this on a Foreach method to gather up multiple data without the duplicates.
The delegate you pass to SelectMany must return an IEnumerable and is for collapsing multiple collections into one. So yes, something's definitely wrong here. I think you've confused it with Select which simply maps one collection to another.
Without knowing what your goal is, it's hard to know exactly how to fix it, but I'm guessing you want something like this:
SFC.OrderFormModifiedMonitoringRecords
.OrderBy(t => t.DateModify)
.ThenBy(t => t.TimeModify)
.Select(t => new { RowID = t.rowID, OFnum = t.OFNo })
.Distinct();
Or in query syntax:
(from t in SFC.OrderFormModifiedMonitoringRecords
orderby t.DateModify, t.TimeModify
select new { RowID = t.rowID, OFnum = t.OFNo })
.Distinct();
This will order the records by DateModify then by TimeModify, select two properties, rowID and OFNo and return only distinct pairs of values.
The end result is I want to use .Where(t => someIntList.Contains(t.ID)).ToList(). I'm struggling to create someIntList.
What I have so far: List<Person> people = people.Where(p => p.isActive).ToList(). How do I return just a List<int> of the p.ID property?
Or is there another way to do Contains (without writing a Comparer class as I already have one used for another purpose.
Well, producing the List<int> is easy, using Select to perform the projection:
List<int> activeIds = people.Where(p => p.IsActive)
.Select(p => p.ID)
.ToList();
However, rather than do that and then use Contains, I would perform a join:
var activePeople = people.Where(p => p.IsActive);
var query = from person in otherList
join activePeople on person.ID equals activePeople.ID
select person;
Or create a HashSet<int> instead of a List<int>, so that the Contains check is more efficient:
var activeIds = new HashSet<int>(people.Where(p => p.IsActive)
.Select(p => p.ID));
var query = otherList.Where(t => activeIds.Contains(t.ID))
.ToList();
Both of these will give O(M + N) complexity for finding all matches, rather than O(M * N) that constructing a list and then using that for the Contains check would do.
Of course, that's assuming the Contains check is going to be done in-process. If this is actually going to be used in a LINQ to SQL query, then it could be that passing in a List<int> is fine - or it could be that the join allows you to do it all in the database. We really need more context to give you good advice - but don't just stop at "this is how I can build a List<T>, therefore I'm done."
Given following structure: a person has functions. Each function has roles. Each roles has features. Now I would like to figure out with linq if a given person has a certain feature, but I am doing something wrong with this query. As a result I always get the count of the functions (but I'd like to get the count of the features):
var count = person.Functions
.Select(fu => fu.Roles
.Select(r => r.Features
.Where(f => f.FeatureId == 99999)))
.Count();
What am I doing wrong here? According to this query I expect either 0 (hasn't got the feature) or 1.
var query = from function in person.Functions
from role in function.Roles
from feature in role.Features
where feature.FeatureId == 99999
select feature;
var count = query.Count();
or
var count = person.Functions
.SelectMany(function => function.Roles)
.SelectMany(role => role.Features)
.Count(feature => feature.FeatureId == 99999);
If you don't need the exact count but just want to know if the person has the feature or not, use Any instead of Count.
var count = person.Functions
.SelectMany(p => p.Roles)
.SelectMany(r => r.Features)
.Where(f => f.FeatureId == 99999)
.Count();
I'm not really sure, but I think you want the total number of Features with teh given Id. You would want to use SelectMany.
How to sort a concurrent collection in .NET 4.0
For example I have constructed my ConcurrentBag collection. How can I sort the elements in it?
ConcurrentBag<string> stringCollection;
ConcurrentBag<CustomType> customCollection;
To expand on DSW's answer, you can use the OrderBy on an enumerable.
customCollection.OrderBy(cc => cc.FieldToOrderBy);
You can also do it in descending order:
customCollection.OrderByDescending(cc => cc.FieldToOrderBy);
you can use OrderBy method for sorting
and also try this too..
var result = stringCollection.AsParallel().AsOrdered();
for more information check below link
http://msdn.microsoft.com/en-us/library/dd460719.aspx, you can lean how to do complex sorting using PLINQ, e.g:
var q2 = orders.AsParallel()
.Where(o => o.OrderDate < DateTime.Parse("07/04/1997"))
.Select(o => o)
.OrderBy(o => o.CustomerID) // Preserve original ordering for Take operation.
.Take(20)
.AsUnordered() // Remove ordering constraint to make join faster.
.Join(
orderDetails.AsParallel(),
ord => ord.OrderID,
od => od.OrderID,
(ord, od) =>
new
{
ID = ord.OrderID,
Customer = ord.CustomerID,
Product = od.ProductID
}
)
.OrderBy(i => i.Product); // Apply new ordering to final result sequence.
you can either use PLINQ or you can write implement your own parallel sort function like the one in this article http://www.emadomara.com/2011/08/parallel-merge-sort-using-barrier.html
Get a list from the collection, sort the list, like:
ConcurrentBag<string> bag = new ConcurrentBag<string>();
var temp = bag.ToList();
temp.Sort();//you can apply a custom sort delegate here
bag = new ConcurrentBag<string>(temp);