How to OrderBy using Linq with missing elements? - c#

Usually when using Linq I would usually filter out empty/null records using Where or similar. However, I need to order a List on multiple criteria and retain all items in the list but in order.
The following works only when .Dimension1.Count > 0 for all items in the list
var orderedList = mList.Elements
.OrderBy(x => x.Property1)
.ThenBy(x => x.Property2)
.ThenBy(x => x.Property3.Dimension1[0].Value2)
.ToList();
If any of the elements have Dimension1.Count == 0 then I get error:
'Index was out of range. Must be non-negative and less than the size of the collection.'
Which is expected as the array is not dimensioned.
Is there a way to make this work when the list contains items which have .Dimension1.Count = 0?
Note that Dimension1[0].Value2 is type double.

You can do something like this:
var orderedList = mList.Elements.OrderBy(x => x.Property1)
.ThenBy(x => x.Property2)
.ThenBy(x => x.Property3.Dimension1.Count == 0
? -1
: x.Property3.Dimension1[0].Value2)
.ToList();
I am assuming here that Value2 is an integer. If it is a string for example, you can use null instead of -1.
The idea is that you use a special value when Count is 0.

You don't mention whether you're using, for example, Entity Framework. It's doubtful this would convert to a valid SQL statement (it might..), but this may be worth a try in your case.
.ThenBy(x => x.Property3.Dimension1[0].Count > 0 ? x.Property3.Dimension1[0].Value2 : -1)
I'm assuming Value2 is always > 0, so that any records missing values should be assigned -1 and be pushed further down the list. If that's not the case, you may need to change -1 to something more appropriate to your situation.

You can just provide a default value for the second ThenBy.
.ThenBy(x => x.Property3.Dimension1.Any()
? x.Property3.Dimension1[0].Value2
: // Some default value for the type of Value2.
// Go high or low depending on the type to put
// them at the top or bottom of the list
);

if you want to be sure that will not be null exception for Dimensions1 array:
.Then(x => (x.Property3.Dimensions1 ?? new object[0]).Count > 0 ? x.Property3.Dimensions1[0].Value2 : -1)

There is a dedicated LINQ method for that, DefaultIfEmpty, which
Returns the elements of the specified sequence or the specified value
in a singleton collection if the sequence is empty.
var orderedList = mList.Elements
.OrderBy(x => x.Property1)
.ThenBy(x => x.Property2)
.ThenBy(x => x.Property3.Dimension1.DefaultIfEmpty(someDefaultValue).ElementAt(0).Value2)
.ToList();

Related

how to use Contains in where clause in linq

I have this:
var myResult = uow.GetRepository<SLItemsCustomersCard, long>()
.Query(x => x.CustomerId == customerId && x.SquareColor == squareColor)
.OrderBy(x => x.BranchMapRang)
.Select((r, i) => new { Row = r, Index = i })
.Where(x => x.Index == visitCounter - 1).ToList();
but I want to achive this in where clause:
.Where(x => x.Index.Cotains(visitCounter)).ToList();
How to do this?
You seem to be misunderstanding what the Contains method does. I'm basing this answer on your earlier usage of:
Where(x => x.Index == visitCounter - 1)
In other words, visitCounter is an integer (and so is Index). But then you want to use it like this:
Where(x => x.Index.Contains(visitCounter))
Which does not make syntactical sense. An integer (Index) does not have a Contains function. It's not fully clear to me what you are trying to achieve, but your comment clarifies it a bit more:
But I want to achieve something like IN clause in SQL server.
The IN clause in SQL requires a range of possibilities (a list of integers, in your case), and you're not working with a list of integers here. Furthermore, you have phrased it as Index.Contains(visitCounter) which would imply that you're expecting Index to be the list of integers?
That simply doesn't make sense. So I'll give you the answer that makes the most sense, on the assumption that you weren't accurate with your pseudocode:
List<int> visitorIds = new List<int>() { 1, 5, 99, 125 };
And then you can do the following:
.Where(x => visitorIds.Contains(x.Index))
To put it in words, this snippet basically tells the computer to "only give the items whose Index is mentioned in the visitorIds list".
You can use Contains like this:
int[] VisitorIds = new int[] {1,2,3}; //an array to check with
.Where(x => vivitorIds.Contains(x.Index)).ToList();

If number have no value it should placed last(LINQ)

I am using linq to sql and my code looks like this:
var agendaLists =
dataContext.view_Agendas.Where(m => m.MeetingID == meetingID)
.OrderBy(n => n.Number)
.ThenBy(n => n.CaseNumber)
.ThenByDescending(s => s.MainYN)
.ToList();
So basicly our "n.Number" is the order number. everything works fine its sorted 1,2,3,4 etc which is correct. but if a object have no value in n.Number it will be displayed in the top but I want it to be placed last.
Today it sorts like this, lets say we get 4 objects back:
Null, 1, 2, 3
I want it sorted like this:
1, 2, 3, Null
Any kind of help is appreciated.
try the following:
n => n.Number ?? int.MaxValue
I dont remember if you actually have to check for DBNull, but it would be the same.
You can use this approach, presuming Number is an int?:
var agendaLists = dataContext.view_Agendas
.Where(m => m.MeetingID == meetingID)
.OrderBy(m => n.Number.HasValue ? 0 : 1)
.ThenBy(n => n.Number)
.ThenBy(n => n.CaseNumber)
.ThenByDescending(s => s.MainYN)
.ToList();

Returning Most Relevant Search Results Using LINQ Where/Take

I have a List<Locations> that will be filtered to yield a set of results relevant to a search term.
At the moment, I tried these 'search results' by filtering with the following:
return locations.Where(o => o.Name.Contains(term)).Take(10).ToList();
Problem
If I were to enter 'Chester' as the search term, I will never see the item "Chester" despite it existing in the locations list. The reason for this is that there are 10 or more other items in the list that contain the String "Chester" in their name (Manchester, Dorchester etc.).
How can I use LINQ to first of all take the results that start with the search term?
What I've Got So Far
var startsWithList = list.Where(o => o.Name.StartsWith(term)).Take(10).ToList();
var containsList = list.Where(o => o.Name.StartsWith(term) && !startsWithList.Contains(o)).Take(10 - startsWithList.Count).ToList();
return startsWithList.AddRange(containsList);
I don't like the above code at all. I feel like this should be achieved in one Where as opposed to performing two Where and Take's and combining the two lists.
just order ascending before Take, putting a lower value for items starting with term.
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Name.StartsWith(term) ? 0 : 1)
//or OrderByDescending(m => m.Name.StartsWith(term))
.Take(10)
.ToList();
adapted with the improvement of MikeT (exact match before StartsWith), you could just do
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Name.StartsWith(term)
? (m.Name == term ? 0 : 1)
: 2)
.Take(10)
.ToList();
I have created a new github project that uses expression trees to search for text in any number of properties
It also has a RankedSearch() method which returns the matching items with the number of hits for each record meaning you can do the following:
return locations.RankedSearch(term, l => l.Name)
.OrderByDescending(x => x.Hits)
.Take(10)
.ToList();
If you wanted you could search accross multiple properties
return locations.RankedSearch(term, l => l.Name, l => l.City)
... or for multiple terms,
return locations.RankedSearch(l => l.Name, term, "AnotherTerm" )
... or for both multiple properties and multiple terms
return locations.RankedSearch(new[]{term, "AnotherTerm"},
l => l.Name,
l => l.City)
Checkout this post for more information on the SQL generated and other usages:
http://jnye.co/Posts/27/searchextensions-multiple-property-search-support-with-ranking-in-c-sharp
You can download this as a nuget package to:
https://www.nuget.org/packages/NinjaNye.SearchExtensions/
Raphaƫl's Solution will work but if you were say searching for Warwick you could find that it might not put Warwick the top of the list if Warwickshire is also a possible location,using the scores you can also extend this infinitely with more matching methods, as well as tweaking the score values to refine your search order
return locations.Select(l => New {SearchResult=l,
Score=(L.Name == Term ?
100 :
l.Name.StartsWith(term) ?
10 :
l.Name.Contains(term) ?
1 :
0
)})
.OrderByDescending(r=>r.Score)
.Take(10)
.Select(r => r.SearchResult);
note i would probably do this by making a Match method and do the logic in there rather than in the linq like i did above so it would just be
return locations.OrderByDescending(Match).Take(10);
All Solutions will work but the better score can gain more easier as below
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Name.IndexOf(term))
.Take(10)
.ToList();
as a result each name that contain term at nearest to start, show first.
What about this?
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Length)
.Take(10)
.ToList();

Sort a List so a specific value ends up on top

I have a class Offer which contains a filed Category.
I want all Offers of a specific category to appear on top, followed by all else.
I tried this, but to no avail, what would you recommend?
Offers = Offers.OrderBy(x => x.Category == "Corporate").ToList();
When you order by a boolean value false (0) comes before true (1). To get the elements that match the predicate first you should reverse the sort order by using OrderByDescending:
Offers = Offers.OrderByDescending(x => x.Category == "Corporate").ToList();
The C# Language Specification 5.0 does not specify a byte representation for the true and false values. Therefore, it is better to not rely on the assumption that true is represented by 1. Also, the result of sorting by the Boolean expression x.Category == "Corporate" is not obvious, as true could be represented by a negative value as well. Therefore, I use a ternary operator to explicitly specify a sort value:
Offers = Offers
.OrderBy(x => x.Category == "Corporate" ? 0 : 1)
.ThenBy(x => x.Category)
.ThenBy(x => x.Date) // or what ever
.ToList();

How to sort part of items in generic list

I have a List object that contains items in following type
public class Item{
public int Type; //this can be only 0 or 1
public string Text;
}
I would like to sort the entire list by the Type first and then do another sort by Text for only items that have type is 1?
How to achieve this?
Many thanks
Ming
Here's how I'd do it.
var ordered = list.OrderBy(i => i.Type == 0 ? "" : i.Text);
This is assuming there are no empty Text strings where i == 0. If you can't make that assumption, the following would work:
var ordered = list.OrderBy(i => i.Type).ThenBy(i => i.Type == 0 ? "" : i.Text);
By giving all items with a 0 Type the same value, you'll leave them all in their initial order, while sorting other items by their Text value.
On a side note, this is a code smell:
public int Type; //this can be only 0 or 1
At a glance, that sounds like it ought to be a boolean or a custom enum type. After taking a deeper look at what you're trying to do, it sounds like maybe you're not expecting Text to even be a legitimate value if Type is 0. If that's the case, you should consider using class inheritance to actually differentiate one item type from another.
Your question isn't perfectly clear. It sounds like you want TWO different sorts, not a sort on one field, and then a stable re-sort when the Type equals 1?
Assuming the former:
var sortByType = items.OrderBy(x => x.Type)
.ToList();
var sortTheOnesByText = items.Where(x => x.Type == 1)
.OrderBy(x => x.Text)
.ToList();
List<Item> items;
items = items.OrderBy(i => i.Type).ThenBy(i => i.Type == 1 ? i.Text : null);
Implement IComparable<Item>
See details with example msdn
And than just use List<T>.Sort() method
I think you mean you want to put all the type=0 at the front and all the type=1 at the back, and also sort by text where type=1.
items.OrderBy(item => item.Type).ThenBy (item => type == 0 ? "" : item.Text)
If you don't mind ordering Type 0 items as well then you can do
var sortedItems = items.OrderBy(i => i.Type).ThenBy(i => i.Text);
Otherwise, if you need to preserve the original order for Type 0 items, then you can do
var sortedItems = items.OrderBy(i => i.Type).ThenBy(i => i.Type == 0 ? "A" : i.Text);

Categories

Resources