Linq Where Contains ... Keep default order - c#

I have a collection of ID numbers that I wish to return some object for, I'm doing it by using a linq statement with a where that's using a contains statement:
var recentCats = (from i in EntityCache.Default.GetAll<Categories>()
where WebProfile.Current.RecentlyCreatedCategories.Contains(i.Id)
&& BoundCategory.ParentItemClass.Id.Equals(i.ParentItemClass.Id)
select new CategoryInfo()
{
Category = i,
ClassId = i.ParentItemClass.Id,
ClassImage = NamedResourceManager.GetResourceBinary(i.ParentItemClass.NameResourceId)
});
This works perfectly fine, except that I want to keep the order of items in the returned collection the same as they were in the list that goes in. So for example if I had a list of IDs: 14, 603, 388, I want the objects that come back to be in the same order, not the order that they're returned by the cache. Is there any way in entity framework to do this, or any way to do it that doesn't involve me writing a foreach loop?
Thanks

The Where Linq extension as with most extensions maintains the original order of the list.
Do LINQ's Enumerable Methods Maintain Relative Order of Elements?
As long as you do not explicitly reorder or use an operator that naturally would reorder the original list's order should be maintained. Obviously reordering the list for a where statement would be unneeded overhead.
The reason the information above does not apply to this question is in the comments bellow.
I would suggest changing the output of the select to be a key/value pair, where the key is the index of the Id in your list, and the value is your new object, then orderBy the key on the resulting set, and select the value.

For anyone interested, I was able to get them to come out in the same order as the list by joining them, as mentioned by Ray in the comments above.
var recentCats = (from i in WebProfile.Current.RecentlyCreatedCategories
join b in allCats
on i equals b.Id
where BoundCategory.ParentItemClass.Id.Equals(b.ParentItemClass.Id)
select ...
Thanks again for all your help & answers.

Related

LINQ Grouping a List of Objects into Anonymous Type

I am having difficulty trying to use LINQ to query a sql database in such a way to group all objects (b) in one table associated with an object (a) in another table into an anonymous type with both (a) and a list of (b)s. Essentially, I have a database with a table of offers, and another table with histories of actions taken related to those offers. What I'd like to be able to do is group them in such a way that I have a list of an anonymous type that contains every offer, and a list of every action taken on that offer, so the signature would be:
List<'a>
where 'a is new { Offer offer, List<OfferHistories> offerHistories}
Here is what I tried initially, which obviously will not work
var query = (from offer in context.Offers
join offerHistory in context.OffersHistories on offer.TransactionId equals offerHistory.TransactionId
group offerHistory by offerHistory.TransactionId into offerHistories
select { offer, offerHistories.ToList() }).ToList();
Normally I wouldn't come to SE with this little information but I have tried many different ways and am at a loss for how to proceed.
Please try to avoid .ToList() calls, only do if really necessary. I have an important question: Do you really need all columns of OffersHistories? Because it is very expensive grouping a full object, try only grouping the necessary columns instead. If you really need all offerHistories for one offer then I'm suggesting to write a sub select (this is also cost more performance):
var query = (from offer in context.Offers
select new { offer, offerHistories = (from offerHistory in context.OffersHistories
where offerHistory.TransactionId == offer.TransactionId
select offerHistory) });
P.s.: it's a good idea to create indexes for foreign key columns, columns that are used in where and group by statements, those are going to make the query faster,

How to create a list from filtering 2 lists with linq to object

I wanted to know if there is a way using Linq to object to get a list from filtering 2 other lists.
I have two lists of objects A and B, they are related two each other by an atribute(Code:String). B has another atribute, Name:String.
I want to get a list of A objects that meet 2 conditions.
-All of A objects must match their A.Code atribute to any of B.Code atribute in the B List.
-B.Name must be = "yoda";
I tried with this code(and another exampeles)
but it doesent seem to work and i dont know why.
I am just starting with linQ.
List<A> FilteredAList = (from OneA in ListOfA
join OneB in ListOfB
on OneA.Code equals OneB.Code
where OneB.Name == "yoda"
select OneA).ToList<A>();
Thanks in Advance!.
With your requirement, I think we should use Any method, therefore we should write method query not expression query. Of course I don't know the equivalent of Any in expression query (At least it should be short as in the method query, otherwise, it's not good). If any one knows, please leave some comment. Thanks for that.
var FilteredAList = ListOfA.Where(x=>ListOfB.Any(y=>x.Code==y.Code && y.Name=="yoda"))
.ToList<A>();

how to append IQueryable within a loop

I have a simple foreach loop that goes through the productID's I have stored in a user's basket and looks up the product's details from the database.
As you can see from my code, what I have at present will return the very last item on screen - as the variable is overwritten within the loop. I'd like to be able to concat this so that I can display the product details for the items only in the basket.
I know I could do something very easy like store only ProductIDs in the repeater I use and onitemdatabound call the database there but I'd like to make just one database call if possible.
Currently I have the following (removed complex joins from example, but if this matters let me know):
IQueryable productsInBasket = null;
foreach (var thisproduct in store.BasketItems)
{
productsInBasket = (from p in db.Products
where p.Active == true && p.ProductID == thisproduct.ProductID
select new
{
p.ProductID,
p.ProductName,
p.BriefDescription,
p.Details,
p.ProductCode,
p.Barcode,
p.Price
});
}
BasketItems.DataSource = productsInBasket;
BasketItems.DataBind();
Thanks for your help!
It sounds like you really want something like:
var productIds = store.BasketItems.Select(x => x.ProductID).ToList();
var query = from p in db.Products
where p.Active && productIds.Contains(p.ProductID)
select new
{
p.ProductID,
p.ProductName,
p.BriefDescription,
p.Details,
p.ProductCode,
p.Barcode,
p.Price
};
In Jon's answer, which works just fine, the IQueryable will however be converted to an IEnumerable, since you call ToList() on it. This will cause the query to be executed and the answer retrieved. For your situation, this may be OK, since you want to retrieve products for a basket, and where the number of products will probably be considerably small.
I am, however, facing a similar situation, where I want to retrieve friends for a member. Friendship depends on which group two members belongs to - if they share at least one group, they are friends. I thus have to retrieve all membership for all groups for a certain member, then retrieve all members from those groups.
The ToList-approach will not be applicable in my case, since that would execute the query each time I want to handle my friends in various ways, e.g. find stuff that we can share. Retrieving all members from the database, instead of just working on the query and execute it at the last possible time, will kill performance.
Still, my first attempt at this situation was to do just this - retrieve all groups I belonged to (IQueryable), init an List result (IEnumerable), then loop over all groups and append all members to the result if they were not already in the list. Finally, since my interface enforced that an IQueryable was to be returned, I returned the list with AsIQueryable.
This was a nasty piece of code, but at least it worked. It looked something like this:
var result = new List<Member>();
foreach (var group in GetGroupsForMember(member))
result.AddRange(group.GroupMembers.Where(x => x.MemberId != member.Id && !result.Contains(x.Member)).Select(groupMember => groupMember.Member));
return result.AsQueryable();
However, this is BAD, since I add ALL shared members to a list, then convert the list to an IQueryable just to satisfy my post condition. I will retrieve all members that are affected from the database, every time I want to do stuff with them.
Imagine a paginated list - I would then just want to pick out a certain range from this list. If this is done with an IQueryable, the query is just completed with a pagination statement. If this is done with an IEnumerable, the query has already been executed and all operations are applied to the in-memory result.
(As you may also notice, I also navigate down the entity's relations (GroupMember => Member), which increases coupling can cause all kinds of nasty situations further on. I wanted to remove this behavior as well).
So, tonight, I took another round and ended up with a much simpler approach, where I select data like this:
var groups = GetGroupsForMember(member);
var groupMembers = GetGroupMembersForGroups(groups);
var memberIds = groupMembers.Select(x => x.MemberId);
var members = memberService.GetMembers(memberIds);
The two Get methods honor the IQueryable and never convert it to a list or any other IEnumerable. The third line just performs a LINQ query ontop of the IEnumerable. The last line just takes the member IDs and retrieves all members from another service, which also works exclusively with IQueryables.
This is probably still horrible in terms of performance, but I can optimize it further later on, if needed. At least, I avoid loading unnecessary data.
Let me know if I am terribly wrong here.

Sorting a list in .Net by one field, then another

I have list of objects I need to sort based on some of their properties. This works fine to sort it by one field:
reportDataRows.Sort((x, y) => x["Comment1"].CompareTo(y["Comment1"]));
foreach (var row in reportDataRows) {
...
}
I see lots of examples on here that do this with only one field. But how do I sort by one field, then another? Or how about a list of many fields? It seems like using LINQ orderby thenby would be best, but I don't know enough about it to know how use it.
For the parameters, something like this that supports any number of fields to sort by would be nice:
var sortBy = new List<string>(){"Comment1","Time"};
I don't want to be writing code to do this in every one of my apps. I plan on moving this sort code to the class that holds the data so that it can do more advanced things like using a list of parameters and implicitly recognizing that the field is a date and sorting it as a date instead of a string. The reportDataRow object contains fields with this information, so I don't have to do any messy checks to find out if the field is supposed to be a date.
Yes, I think it makes more sense to use OrderBy and ThenBy:
foreach (var row in reportDataRows.OrderBy(x => x["Comment1"]).ThenBy(x => x["Comment2"])
{
...
}
This assumes the other thing you want to order by is "Comment2".
Try this:
reportDataRows.Sort((x, y) =>
{
var compare = x["Comment1"].CompareTo(y["Comment1"]);
if(compare != 0)
return compare;
return x["Comment2"].CompareTo(y["Comment2"]);
});
You may want to look at this previous answer where I posted an extension method which handles multiple order by's in LINQ. This allows this sort of syntax:
myList.OrderByMany(x => x.Field1, x => x.Field2);
Look at the example for ThenBy on msdn.
If you're comparing your own objects, then you can implement the IComparable interface.
Otherwise, you can use the IComparer interface.
Using LINQ method syntax:
var sortedRows = reportDataRows.OrderBy(r => r["Comment1"])
.ThenBy(r => r["AnotherField"];
foreach (var row in sortedRows) {
...
}
And even more readable using query comprehension syntax:
var sortedRows = from r in reportDataRows
orderby r["Comment1"], r["Comment2"]
select r;
foreach (var row in sortedRows) {
...
}
You got it. Enumerable.OrderBy().ThenBy() is your ticket. It works exactly like it looks; elements are sorted by each projection, with ties decided by comparing the next projection. You can chain as many ThenBys as you want, and there are also OrderByDesc and ThenByDesc methods that will sort that projection in descending order.
As Albin has pointed out, An OrderBy chain does not touch the original list unless you assign the result of the ordering back to the original variable, like this:
reportDataRows = reportDataRows.OrderBy(x=>x.Comment1).ThenBy(x=>x.Comment2).ToList();
As a rule, OrderBy will perform slightly slower than List.Sort(); the algorithm is designed to work on any IEnumerable series of elements, so in order to sort (which requires knowing every element of the series) it slurps its entire source enumerable into a new array. However, OrderBy has a distinct advantage over Sort in that it is a "stable" sort; elements that are exactly equal to each other will retain their "relative order" in the sorted enumerable (the first of the two that you;d encounter when iterating through the unsorted list will be the first of the two encountered when iterating through the sorted list).

LINQ - Get all items in a List within a List?

I'm currently working my way through the learning curve that is LINQ and I could really use some assistance. I don't know if what I want is possible, but if I had to wager, I bet it is.
I currently have a list of objects called _tables and each of these objects has within it another list of objects exposed through the property, "Indexes". Essentially, I'd like to end up with one List that contains all the Indexes from all of the _tables.
Here's what I have so far:
var indexes = from TableInfo tab
in _tables
where tab.Indexes.Count > 0
select tab.Indexes;
Unfortunately, this seems to be giving me another List of Lists, but only where the Indexes List contains more than one value... Is there some way to get all of these lists together without loops?
You want to use the SelectMany extension method.
_tables.SelectMany(t => t.Indexes)
In addition to tbischel's answer, the query expression version of what you're going for is below.
var indexes = from TableInfo tab in _tables
from index in tab.Indexes
select index;
You don't need the where clause and you also shouldn't need to tell it what tab is
And you will need to use SelectMany
var indexes = (from tab in _tables).SelectMany(t => t.Indexes)
Or you could do it like this
var indexes = from tab in _tables
from t in tab.Indexes
select t;
That should be a little more familiar syntaz
var rows = from item in table select item;

Categories

Resources