tags:

views:

216

answers:

4

I have a query in linqtosql that returns a LabelNumber:

var q = from list in db.Lists
        select list.LabelNumber;

var q then becomes an IEnumerable<string> with elements like this:

{"1","2","2.A","2.B","3","3.A","3.B"}

I basically want to order the elements as they appear above, but I can't use the OrderBy(x=>x.LabelNumber) because "10" would get placed after "1" and before "2".

I assume I have to write a custom comparator function, but how do I do this with linq?

Edit: I think all of the answers below will work, but one caveat must be added to all responses.

If you are using Linq2SQL you cannot use array indexes within the query. To overcome this, you should have two queries. One that reads from SQL. The second does the ordering:

var q = from list in db.Lists
            select list.LabelNumber;

var q2 = q.AsEnumerable()
          .OrderBy(x => int.Parse(x.LabelNumber.Split('.')[0]))
          .ThenBy(x => x.Number
                        .Contains(".") ? 
                              x.LabelNumber.Split('.')[1].ToString() 
                              : 
                              string.Empty);
+1  A: 

If you are sure that q is well-formatted and sequenced:

var result = q.OrderBy(x => int.Parse(x.Split('.')[0]));
Danny Chen
You're assuming that only the numbers are out of order. What if you had 2.B,2,2.A?
Michael Kropat
@Michael: The OP diddn't say he wants to orderby the letters..and I'm lazy enough. Anyway it's quite probable that he wants it. LOL.
Danny Chen
+5  A: 

You probably don't have to write a custom comparer. If all your labels are in the form number.letter, you could use this.

var query = from list in db.Lists
            let split = list.LabelNumber.Split('.')
            let order = split.Length == 1
                ? new { a = int.Parse(split[0]), b = String.Empty }
                : new { a = int.Parse(split[0]), b = split[1] }
            orderby order.a, order.b
            select list.LabelNumber;

If you need more control, you could always convert the sortby fields (a and b) to the appropriate types rather than ints and strings.


If this is LINQ-to-SQL, this actually won't work since some methods used here are not supported. Here's a LINQ-to-SQL friendly version. It won't yield the prettiest query, but it will work.

var query = from list in db.Lists
            let dot = list.LabelNumber.IndexOf('.')
            let name = list.LabelNumber
            let order = dot == -1
                ? new { a = Convert.ToInt32(name.Substring(0, dot)), b = String.Empty }
                : new { a = Convert.ToInt32(name.Substring(0, dot)), b = name.Substring(dot+1) }
            orderby order.a, order.b
            select list.LabelNumber;
Jeff M
I get this error when I run this: `System.InvalidOperationException: Unrecognized expression node: ArrayIndex`
Shawn
This is in LINQ-to-SQL right? Of course this wouldn't work there and would require some tweaking.
Jeff M
I added an edit that addresses the tweaking. thanks.
Shawn
Thanks for the edit. You say it won't yield the prettiest query because it probably has to use TSQL to do the substring stuff; is this correct? How does your second version compare performance wise to using the GetEnumerator() method. I would assume your method requires more work by the db server, but my method uses more memory. Are these accurate assumptions. Could you discuss the performance of these approaches? I'm just learning Linq and trying to understand what can be done versus what should be done.
Shawn
The C# equivalent is more concise whereas the TSQL is very verbose. AFAIK, `Substring()` isn't near as bad as using `IndexOf()` as far as complicating the query goes. You probably don't have to worry. Your assumptions are accurate. Using `AsEnumerable()`, you're using LINQ-to-Objects and the following queries (filtering, sorting, etc.) are executed on your machine. If you kept it LINQ-to-SQL friendly, everything would be executed on the server. After all, it is just a TSQL query. For large sets of data, try to keep it L2SQL friendly to offload the processing. Otherwise, it's up to you.
Jeff M
+3  A: 
OrderBy(x=>x.LabelNumber, new AlphanumComparator())

where AlphanumComparator is the excellent Alphanum natural sort algorithm by David Koelle. No need to reinvent the wheel.

Michael Kropat
+1, for introducing me to AlphanumComparator
adrift
This syntax doesn't work. `Argument 3: cannot convert from 'AlphanumComparator' to 'System.Collections.Generic.IComparer<string>'`
Shawn
That's 'cause OrderBy expects a generic IComparer, while AlphanumComparator is non-generic. The change's quite easy to do, though. Add the <T>'s in the declarations, change all objects to Ts and remove casts.
Kyte
Oops, I had forgotten that I had modified my local copy of AlphanumComparator. I submitted a patch to the author just now with my simple changes. Perhaps IComparer<string> support will be included in the official version in the future.
Michael Kropat
A: 

Here is my contribution.. using Regular Expression and LAMBDA expression

List<String> Lst = new List<string> { "1", "2", "2.A","10.A", "2.C", "3", "3.A", "3.B","2.B","11.D" };
Lst = Lst.Select(X => new
        {
            Number = int.Parse( Regex.Match(X, @"([0-9]*).?([a-zA-Z]*)").Groups[1].Value),
            Strings=Regex.Match(X, @"([0-9]*).?([a-zA-Z]*)").Groups[2].Value,
            OriginalString = X
        }).OrderBy(X => X.Number).ThenBy(X=>X.Strings)
        .Select(X => X.OriginalString).ToList();

Output:

"1"
"2"
"2.A"
"2.B"
"2.C"
"3"
"3.A"
"3.B"
"10.A"
"11.D"
Pramodh
`Method 'System.Text.RegularExpressions.Match Match(System.String, System.String)' has no supported translation to SQL.` Although it does appear to work in your example.
Shawn
then make a `list` with your labelnumbers and then sort that using the above expression.
Pramodh
Got it solved. Thanks, I got you code running with Linq2SQL. I was unaware of the array limitation. I ended up using a simpler version, as regex over complicates my situation. The sub nesting will eventually get more complicated and I'll have to resort to your solution. Thanks.
Shawn