Sorting a number without the first character using OrderBy query while performing a database search

How do I order a list by the value of a string minus it's first character (in my case the first character is always a C followed by a number), while performing a database search.

I've tried a lot of stuff but I'm pretty much stumbling around in the dark, I'm sure someone here can solve this in a instant.

My code that works (but doesn't do what I want):

        List<Customer> customerlist = null;
        try
        {
            customerlist = db.Customers
                            .Where(u => (u.Cust_ID+u.Given_Name+u.Surname).Contains(searchstring))
                            .OrderBy(u => u.Cust_ID)
                            .ToList();
        }

My buggy code at the moment (which seems to me like it should work, but results in the search always returning no results):

        List<Customer> customerlist = null;
        try
        {
            customerlist = db.Customers
                            .Where(u => (u.Cust_ID+u.Given_Name+u.Surname).Contains(searchstring))
                            .OrderBy(u => Int32.Parse(u.Cust_ID.TrimStart('C')))
                            .ToList();
        }

Más detalles:

I have a column in my table named Cust_ID, which starts with a C and is then followed by a number without leading zeros e.g. C1, C2, ... C43, ... C999.

I am allowing the user to perform a search, the query of which is assigned to searchstring.

I've tried debugging by removing the int parse

                            .OrderBy(u => u.Cust_ID.TrimStart('C'))

that returns no results as well.

The original code I had in here still works to complete the search, but doesn't sort it correctly.

                            .OrderBy(u => u.Cust_ID)

I tried using Replace instead of TrimStart

                            .OrderBy(u => u.Cust_ID.Remove(0, 1))

And this returns a result, but still doesn't sort, and when i put the Int parse in, it stops returning a result again:

                            .OrderBy(u => Int32.Parse(u.Cust_ID.Remove(0, 1)))

I tried switching the int parse to:

                            .OrderBy(u => int.Parse(u.Cust_ID.Remove(0, 1)))

pero eso no ayudó.

edit: found I was getting an exception:

System.NotSupportedException: LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression.
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.DefaultTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input, DbExpressionBinding& binding)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.Convert()
       at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
       at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
       at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
       at lab2.Controllers.DemoController.retrievecustomers() in [[Directory]]Controller.cs:line 52

preguntado el 24 de agosto de 12 a las 01:08

If you were writing real SQL, this would be easy: ORDER BY CONVERT(INT, SUBSTRING(Cust_ID, 2, 255)) ... sorry but I don't know how to translate this into whatever you're using (which I presume is Supuesto to make your database access easier, but it appears that is not always the case). -

If it was real SQL, the ORDER BY wouldn't cause it to not select anything... -

by the way, is the leading 'C' character always uppercase -

I really don't believe the order by itself is causing that. Perhaps it's how this specific framework is trying (unsuccessfully) to implement the order by. -

@Aaron Bertrand It's like you have reservation for this framework :) -

5 Respuestas

Puedes usar AsEnumerable to switch to LINQ to Objects antes de que OrderBy. In other words, you'd be executing OrderBy client-side instead of letting the ORM attempt (unsuccessfully, as it were) map it to SQL's ORDER BY.

That being said, it's a bad idea to store in the database a prefix that never changes. It wastes space, forces you to use the wrong data type (VARCHAR instead of INT), triggers a wrong kind of comparison in ORDER BY and prevents ORDER BY from using an index (although your WHERE seems very effective at doing that already)...

Respondido 24 ago 12, 03:08

I have always used ToArray as I never knew about AsEnumerable. I was thinking it would be nice to have a way to do this without having to store all results in an array. With regards your comment about not storing the C, I would say that you should store whatever value is used in real life. If that piece of data is referred to as "C123" then you should store C123. Storing it as an int would be a mistake imo. - mikekulls

@MikeKulls "store whatever value is used in real life" And 'C' is not used. It's just displayed. - branco dimitrijevic

@BrankoDimitrijevic I would say displaying it is using it. Let's say the C123 is a customer code and this is commonly printed on customer correspondance and internal documents then I think the C is being used. If we stored just the 123 as an int then this could cause problems if they start having customer codes with leading zeros or if someone comes up with idea of prefixing international customers with an I. It could also cause problems interfacing with other systems that store it as C123. - mikekulls

@MikeKulls Then prefix is not always the same and the whole line of reasoning changes. I was commenting only on the case when prefix nunca cambios. - branco dimitrijevic

@MikeKulls BTW, if the prefix does change, you might still want to separate string prefix and int suffix fields, for storage efficiency and indexing ability (an index on int, not string, is required for efficiently executing ORDER BY when integer comparison is desired). After all, it's easy to create a calculated field or a view that concatenates them together. You could go the opposite route (have a single physical string field, then split it to 2 calculated fields and the index de ellos), but this could be less efficient storage-wise. - branco dimitrijevic

I would just do the ordering on the client side. Why? Because it doesn't increase the amount of traffic going across the network and will give you a lot more flexibility and allow you to check for dodgy values. It could be argued that it is better to do the sorting on the client anyway as it reduces server load.

customerlist = db.Customers
    .Where(u => (u.Cust_ID+u.Given_Name+u.Surname).Contains(searchstring))
    .AsEnumerable()  //EDIT: was ToArray()
    .OrderBy(u => Int32.Parse(u.Cust_ID.SubString(1)))
    .ToList();

Note that the AsEnumerable causes the linq query up to that point to be sent to the server and everything afterwards is done on the client.

As a side note I would add that if you don't need to modify the list then it would be slightly more efficient to store the values in an array instead of a list.

I would also add do you really want to join CustID with their names for the search? This would cause the search term Bob to match someone called Bo Brown.

EDIT: I have changed ToArray to AsEnumerable as this is more efficient as pointed out by Branko Dimitrijevic. My apologies if it's unethical to take parts from other answers.

Respondido 24 ago 12, 04:08

great! that works - after I edited out minor typos: .OrderBy(u => Int32.Parse(u.Cust_ID.Substring(1))) - Josh

Yes, actually I don't need to modify anything here. How would I rewrite it as an array? I have found the use case you demonstrate that makes my concatenate a bit strange, so I've edited to the following which works as expected .Where(u => (u.Cust_ID + " " + u.Given_Name + " " + u.Surname).Contains(searchstring)) - Josh

@Josh oops, I will fix. To return an array just use ToArray instead of ToList and define customerList as "Customer[]" - mikekulls

"My apologies if it's unethical to take parts from other answers." It's perfectly fine, as long as you give proper credit, which you did! - branco dimitrijevic

my guess is your database contains values that aren't of the form 'C[0-9]+' and are incompatible with int.Parse, possibly 'null', causing your OrderBy to raise an exception

Respondido 24 ago 12, 01:08

try checking db.Customers.Any(c => c.Cust_ID == null) and db.Customers.Any(c => !Regex.IsMatch(c.Cust_ID, "^C[0-9]+$")) to verify... or indeed, db.Customers.Any(c => c == null) - personal

the regex won't compile (The name 'Regex' doesn't exist in the current context). everything seems to run without throwing up an exception. - Josh

Hmm, I'm not sure if I'm doing this right, I tried using an if statement to throw up an alert on a found null, but this doesn't seem to return a null even though I know there is one in the table in a different row: if (db.Customers.Any(c => c == null)) - Josh

Wait, I have an issue with my alert implementation, let me fix that and get back to you - Josh

ok, no, doesn't throw out a null, but now I can see an exception on the query which I didn't see before. Have added it to original question. - Josh

I don't know much about LINQ but try this

.OrderBy(u => Convert.ToInt32(u.Cust_ID.Substring(1)))

Respondido 24 ago 12, 02:08

That still results in id C10 coming before C2 - Josh

I have updated the answer. This will only work if the rest of string after C are numbers - codificación

shouldn't make a difference to what the op tried. Convert.ToInt32 and int.Parse are equivalent in the usual case (string of decimal digits). do check my answer - i'm pretty sure something is amiss with your dataset - personal

I agree with MikeKulls that sorting could be done after pulling the data, but if you do prefer sorting on the server, you could pad the strings to get the semantics of integer sorting:

customerlist = db.Customers
                .Where(u => (u.Cust_ID+u.Given_Name+u.Surname).Contains(searchstring))
                .OrderBy(u => u.Cust_ID.Substring(1).PadLeft(10 /* or suitable number */, '0'))
                .ToList();

Respondido 24 ago 12, 03:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.