I encountered an interesting thing today that I have never noticed before. It appears that SQL and LINQ order AlphaNumeric strings differently.
Data table contains rows: A G 6 P 1 D J 2 T Z 9 F 0
If I perform an Order By in the SQL, I receive the following results: A D F G J P T Z 0 1 2 6 9
Now consider this LINQ sample:
class Program
{
static void Main(string[] args)
{
var list = new List<string>()
{
"A",
"G",
"6",
"P",
"1",
"D",
"J",
"2",
"T",
"Z",
"9",
"F",
"0"
};
Console.WriteLine("Default Order:");
list.ForEach(s => Console.WriteLine(s));
Console.WriteLine();
Console.WriteLine("Sorted Order:");
foreach (string s in list.OrderBy(f => f))
{
Console.WriteLine(s);
}
}
}
The output for this is 0 1 2 6 9 A D F G J P T Z
So where SQL places Letters first and Numbers 2nd, LINQ orders Numbers first and Letters 2nd. I put these results in a DataGrid and clicked the header, and sure enough it also orders ala LINQ, so this could be a deeper divide, like at the .NET/Windows level.
The problem I have is that my users expect the ordering behavior they are used to seeing as a result of SQL ordering. How do I get LINQ to behave the same way?
UPDATE
The answer is flagged below, but just for anyone who stumbles onto this later, I wanted to recap, because it took a combination of answers to solve the problem.
1) dcp went immediately where my brain didn t: the Data Source. The problem turns out to be the difference between IBM s SQL, which uses EBCDIC sorting, and every other technology in the known universe using ASCII sorting. Thanks IBM for once again reminding me why I am now a .NET developer.
2) Recognizing this, Ahmad provided a very elegant solution that also eluded me: a custom IComparer<string>. I used the code he provided and it sorted the List as desired.
Thanks to both StackOverflow comes through again!
UPDATE 2
After yesterdays posting I got this finished and wanted to share the final results.
The post yesterday was a simple list of single characters, but in reality these were embedded in longer strings. To make this work with longer strings, I changed the original string comparer to a char comparer, and then looped through the strings and compared each character until I either found a mismatch or ran out of characters to compare. Here are the final two Comparer classes:
public class EbcdicCharComparer : IComparer { public int Compare(char x, char y) { int xNum, yNum; bool xIsNum = Int32.TryParse(x.ToString(), out xNum); bool yIsNum = Int32.TryParse(y.ToString(), out yNum);
// compare numbers
if (xIsNum && yIsNum)
{
return xNum.CompareTo(yNum);
}
// compare num to char
if (xIsNum)
{
return 1;
}
// compare num to char
if (yIsNum)
{
return -1;
}
// compare normally
return x.CompareTo(y);
}
}
public class EbcdicStringComparer : IComparer { public int Compare(string x, string y) { var xArr = x.ToCharArray(); var yArr = y.ToCharArray();
var iterations = xArr.Length > yArr.Length ? yArr.Length : xArr.Length;
var charComp = new EbcdicCharComparer();
for (int i = 0; i < iterations; i++)
{
var compValue = charComp.Compare(xArr[i], yArr[i]);
if (compValue != 0)
return compValue;
}
// compare as strings
return x.CompareTo(y);
}
}
The real data is more like this:
- 024 A 17
- 024 A 18
- 024 A 19
- 024 1 19
- 024 C 19A
- 024 3 3
- 024 A 3B
And as desired, the comparer now returns the data in this order:
- 024 A 3B
- 024 A 17
- 024 A 18
- 024 A 19
- 024 C 19A
- 024 1 19
- 024 3 3
Thanks again to all who helped.