How can I sort a string of text followed by a number using LINQ
It's because you are sorting them as strings, and for strings 11 comes before 2. You'll need to parse the ShortTitle
to give you the (I'm assuming) int
value at the end, and sort by that.
Your LINQ query can be changed to this for it to work:
var query = _cityRepository.GetAll(
.OrderBy(item => item.RowKey.Substring(0, 4))
.ThenBy(item => item.ShortTitle.Split('-').First())
.ThenBy(item => Convert.ToInt16(item.ShortTitle.Split().Last()));
sort string as number in linq to entity
You have two problems here:
- You are storing numbers as strings in your database and
- Youre trying to execute C# code on Sql Server
The exception you are receiving is due to the fact that the compiler cannot translate the comparison logic from SemiNumericComparer
class into a sql query.
In order to achieve the desired result you could:
a) Load all data in memory and perform the comparison using SemiNumericComparer
in memory by iterating through the selected results and ordering them after that like this:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName })
.ToList() // this will load data into memory
.OrderBy(c => c.id, new SemiNumericComparer());
This, however is not a good approach because it will add a lot of useless memory consumption if the dataset is quite small and will crash your application if your dataset is larger than the available memory at a given time.
Edit As pointed out by @gsubiran this approach is not valid.
b) Convert your strings into numbers on Sql Server using SqlFunctions
and order them as numbers using the ordering provided by Sql Server:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName })
.OrderBy(c => SqlFunctions.IsNumeric(c.id));
LINQ OrderBy or Sort does not order correctly for integer string list?
This particular question comes up all the time and I'm pretty sure there are good duplicate questions here on Stack Overflow covering the topic. Please close the question as a proper duplicate and I'd be more than happy to delete my answer in response.
In the meantime...
The problem is us humans.
We see numbers so we think in numbers. To us, 1 is less than 12, and 12 is less than 100. No problems here.
The problem, however, is that we're asking a computer. And a computer is a bit more finicky. In particular, if we're asking a computer to do a alphabetical sort, it will invariably treat our "things" to sort as text. And this is where the problems occur. Or rather, this is where our expectations no longer match what the computer is going to do.
The computer, when asked to sort strings, sort them as any other string sort, character by character.
Let's look at the items:
1
100
12
To us, the natural sort order would be 1, 12, 100. Increasing order.
To the computer, asked to do a text sort, the natural order is different.
The reason is that it's going to compare strings one character at a time.
Broadly it is going to say "all strings that start with 1 is going to come before strings that start with 2", which means "1" and "100" is going to come before "2". This is exactly the same as saying "all words that start with the letter A comes before words that start with the letter B".
Then it's going to say "all strings that then have a 0 comes before strings that have a 1, 2, 3, etc. up to 9, including space", because this is how text sorting is done.
In other words, when you ask "Sort does not order correctly for integer string list?" the simple answer is "Well, no, because when you sort as text, the integer part is never considered".
C# - How to sort list string number by linq?
Since the dates are in the format that can be ordered lexicographically, you could sort by the date prefix using string ordering, and resolve ties by parsing the integer:
var sorted = listStr
.OrderBy(s => s.Split('_')[0])
.ThenBy(s => int.Parse(s.Split('_')[1]));
Demo.
How can I sort a list of numerical strings? Preferably using LINQ
If that format is strict you could parse to Version
and order by that:
string[] orderedThings = things
.Select(t => new { Thing = t, Numbers = (t + "_0").Split('_') })
.Where(x => x.Numbers.All(s => s.All(char.IsDigit)))
.Select(x => new { x.Thing, VersionStr = String.Join(".",x.Numbers.Take(4)) })
.OrderBy(x => new Version(x.VersionStr))
.ThenBy(x => x.Thing)
.Select(x => x.Thing)
.ToArray();
The t + "_0"
trick was necessary to ensure that also single digits can be parsed. A version needs at least a major and aminor part. This "works" also if there are more than 4 tokens(major, minor, build, and revision). Then only the first 4 are taken to initialize the Version
instance.
Linq query order by for string
Well, p.s.w.g has a great answer, but since I worked on this a little I figured I would post mine as well.
My suggestion is to create a class that encapsulates the data from the string, which can only be instantiated from a static Parse
method. This Parse
method takes in a string and then parses it, setting the properties of the class as it does, and then returns the instance of the class.
The class also implements IComparable
, so we can use the Sort
and OrderBy
methods on a list of these items.
I also used the same answer for parsing roman numerals that was used above (it's the first result when searching for "roman numeral comparer").
Here's the class:
public class BandLevelComponent : IComparable<BandLevelComponent>
{
public int Major { get; private set; }
public int Minor { get; private set; }
public string Revision { get; private set; }
public string RomanNumeral { get; private set; }
private static Dictionary<char, int> _romanMap = new Dictionary<char, int>
{
{'I', 1},
{'V', 5},
{'X', 10},
{'L', 50},
{'C', 100},
{'D', 500},
{'M', 1000}
};
private BandLevelComponent()
{
}
public static BandLevelComponent Parse(string input)
{
if (string.IsNullOrWhiteSpace(input)) return null;
BandLevelComponent result = new BandLevelComponent();
int temp;
var parts = input.Split('.');
int.TryParse(parts[0], out temp);
result.Major = temp;
if (parts.Length > 1)
{
var minor = string.Concat(parts[1].TakeWhile(char.IsNumber));
int.TryParse(minor, out temp);
result.Minor = temp;
if (parts[1].Length > minor.Length)
{
var remaining = parts[1].Substring(minor.Length);
var openParen = remaining.IndexOf("(");
if (openParen > 0) result.Revision = remaining.Substring(0, openParen);
if (openParen > -1)
result.RomanNumeral = remaining
.Split(new[] {'(', ')'}, StringSplitOptions.RemoveEmptyEntries)
.Last();
}
}
return result;
}
public int CompareTo(BandLevelComponent other)
{
if (other == null) return 1;
if (Major != other.Major) return Major.CompareTo(other.Major);
if (Minor != other.Minor) return Minor.CompareTo(other.Minor);
if (Revision != other.Revision) return Revision.CompareTo(other.Revision);
return RomanNumeral != other.RomanNumeral
? RomanToInteger(RomanNumeral).CompareTo(RomanToInteger(other.RomanNumeral))
: 0;
}
public override string ToString()
{
var revision = Revision ?? "";
var roman = RomanNumeral == null ? "" : $"({RomanNumeral})";
return $"{Major}.{Minor}{revision}{roman}";
}
private static int RomanToInteger(string romanNumeral)
{
var roman = romanNumeral?.ToUpper();
var number = 0;
for (var i = 0; i < roman?.Length; i++)
{
if (i + 1 < roman.Length && _romanMap[roman[i]] < _romanMap[roman[i + 1]])
{
number -= _romanMap[roman[i]];
}
else
{
number += _romanMap[roman[i]];
}
}
return number;
}
}
And here's a sample usage:
private static void Main()
{
var dbStrings = new[]
{
"1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11",
"1.12", "1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19", "1.20", "2.1a(i)",
"2.1a(ii)", "2.1a(iii)", "2.1a(iv)", "2.1a(v)", "2.1a(vi)", "2.1a(vii)"
};
// Custom extension method for shuffling
dbStrings.Shuffle();
// Select each string into our custom class
var bandLevels = dbStrings.Select(BandLevelComponent.Parse).ToList();
Console.WriteLine("\nShuffled List");
Console.WriteLine(string.Join(", ", bandLevels));
// Sort the list
bandLevels.Sort();
Console.WriteLine("\nSorted List");
Console.WriteLine(string.Join(", ", bandLevels));
// Order the list descending (largest first)
bandLevels = bandLevels.OrderByDescending(b => b).ToList();
Console.WriteLine("\nOrderByDescending List");
Console.WriteLine(string.Join(", ", bandLevels));
GetKeyFromUser("\nDone! Press any key to exit...");
}
Output
How to use Linq to sort an array by Length and then value
Try this snippet:
var sortedValues = values
.OrderByDescending(x => x.Length)
.ThenByDescending(x => Convert.ToInt32(x));
If you really need use it as a List, then add ToList()
at the end.
Related Topics
How to Save Image in Database Using C#
Why Does C# (4.0) Not Allow Co- and Contravariance in Generic Class Types
How to Run and Interact with an Async Task from a Wpf Gui
How to Get the Index of an Item in a List in a Single Step
Access to Foreach Variable in Closure Warning
C# Drag Drop Does Not Work on Windows 7
Can't Convert Value Type Array to Params Object[]
How to Print the Contents of an Array Horizontally
Callbackoncollecteddelegate in Globalkeyboardhook Was Detected
Why Appdomain.Currentdomain.Basedirectory Not Contains "Bin" in ASP.NET App
How to Execute Task in the Wpf Background While Able to Provide Report and Allow Cancellation
How to Set Up HTML/Email Templates with ASP.NET
Upload Files and JSON in ASP.NET Core Web API
How Do C# Events Work Behind the Scenes
What's the Difference Between Application.Run() and Form.Showdialog()