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.