0

I have string column contains 1st Batch, 2nd Batch, 10th Batch etc. Now I am trying to sort them using this string field on ascending order e.g. 1, 2, 3 but having no luck.

public ActionResult PublicGrid()
{
    ViewBag.members = db.Members.Count().ToString();    

    return View(db.Members.OrderBy(a => Regex.Replace(a.Batch, @"[^\d]", String.Empty)).ThenBy(a=> a.LastName).ToList());
}

I have tried the above reg expression but getting error -

LINQ to Entities does not recognize the method 'System.String Replace(System.String, System.String, System.String)' method, and this method cannot be translated into a store expression.

I have tried this Linq expression but still, throws error -

var members = (from m in db.Members
                      orderby Char.IsDigit(Convert.ToChar(m.Batch)) ascending
                      select m).ToList();

I have tried creating a static function to split the string like below but still not working.

public ActionResult PublicGrid()
{
    ViewBag.members = db.Members.Count().ToString();

    return View(db.Members.OrderBy(a => NumberOnly((a.Batch))).ThenBy(a=> a.LastName).ToList());
}

public static string NumberOnly(string strRaw)
{
    string numbers = string.Empty;

    foreach (char c in strRaw)
    {            
        if (Char.IsNumber(c))
        {
            numbers += c;
        }
    }
    return numbers;
}

Any help much appreciated. Thanks in advance.

Someone mentioned this question is similar to previously asked questions but I could not find any similarity.

Tajuddin
  • 73
  • 3
  • 13
  • 2
    You would first need to materialize the query to memory (e.g. `db.Members.ToList().....` since `Regex.Replace()` cannot be converted to SQL –  Aug 04 '17 at 02:35
  • Possible duplicate of [LINQ to Entities does not recognize the method 'System.String Format(System.String, System.Object, System.Object)'](https://stackoverflow.com/questions/10079990/linq-to-entities-does-not-recognize-the-method-system-string-formatsystem-stri) – Max Aug 04 '17 at 02:48
  • 1
    Possible duplicate of [Using my own method with LINQ to Entities](https://stackoverflow.com/questions/17187395/using-my-own-method-with-linq-to-entities) – Tetsuya Yamamoto Aug 04 '17 at 02:54
  • Can you provide some examples for what *real* `Batch` values look like? I think you "might" be able to solve this within the query if `Batch` values always follow a structured pattern – Andrés Robinet Aug 04 '17 at 02:56
  • @AndrésRobinet Batch values are - 1st Batch, 2nd Batch, 3rd Batch ...38th Batch – Tajuddin Aug 04 '17 at 03:04
  • @Tajuddin There are ordinal numbers pattern, you can check the last digit & possible using switch statement (what you're want to: convert to number or convert to string ordinals?). – Tetsuya Yamamoto Aug 04 '17 at 03:17

3 Answers3

0

Thanks Stephen for pointing the light on my issue. I have to materialize the dataset first then apply the sorting condition. I have changed my code and it seems to be sorting correctly after the change. Here is my code in case if someone faces the similar issue -

public ActionResult PublicGrid()
    {
        ViewBag.members = db.Members.Count().ToString();

        var members = db.Members.ToList();

        return View(members.OrderBy(a => NumberOnly((a.Batch))).ThenBy(a=> a.LastName).ToList());

    }

    public static int NumberOnly(string strRaw)
    {

        string numbers = string.Empty;

        foreach (char c in strRaw)
        {

            if (Char.IsNumber(c))
            {
                numbers += c;
            }
        }

        return Int32.Parse(numbers);
    }
Tajuddin
  • 73
  • 3
  • 13
  • this code will fail if there is a number after batches. Try putting a condition to end the loop when Char.IsNumber returns false. Also put another condition if number is empty string return something useful maybe 0 so it comes on top of the order by query – Ziregbe Otee Aug 06 '17 at 00:51
0

Disclaimer: I didn't try it with a real EF model and context. But both Substring and Length are supported by query providers

Also, if you are going to do Skip and Take after the OrderBy, it may be better to let the SQL engine deal with it... otherwise you have to materialize ALL results to sort in memory (just a blind guess)

Notice that this works because I'm assuming suffix has a fixed length (which is the only interesting part about the suffix... one could as well just declare suffixLength = 8)

var batchSuffix = "xx Batch";

// Use db.Members instead of this
var query = new List<Member>
{
    new Member("1st Batch"), new Member("2nd Batch"), new Member("3rd Batch"), new Member("38th Batch"),
    new Member("12nd Batch"), new Member("23rd Batch"), new Member("324th Batch"), new Member("32th Batch")
}.AsQueryable();

var result = query
    .Select(m => new
    {
        Member = m,
        BatchNumber =  m.Batch.Substring(0, m.Batch.Length - batchSuffix.Length)
    })
    .OrderBy(m => m.BatchNumber.Length)
    .ThenBy(m => m.BatchNumber)
    .Select(m => m.Member)
    .ToList();
Andrés Robinet
  • 1,527
  • 12
  • 18
0

While your solution may work, I see some room for improvement:

NumberOnly

  • char.IsNumber checks for a unicode category which contains more characters than '0'..'9', so parsing may fail in general.
  • If the first character is not a number, you parse an empty string -> exception.
  • Building a string character by character just to parse it as an int is inefficient.
  • Would prefer to name it to something more specific.

What about this?

private static int IntPrefix(string s) => s
    .TakeWhile(ch => ch >= '0' && ch <= '9')
    .Aggregate(0, (a, c) => 10 * a + (c - '0'));

member ordering

  • You materialize a list, then enumerate that list for sorting (which internally has to also materialize it), then build another list; we see way too many .ToList()s on here.
  • The NumberOnly may be called many times on a single entity during the sort; better to precalculate that.

The sorted list can be expressed like this:

db.Members
    .AsEnumerable()
    .Select(m => new { Member = m, BatchNo = IntPrefix(m.Batch) })
    .OrderBy(mb => mb.BatchNo)
    .ThenBy(mb => mb.Member.LastName)
    .Select(mb => mb.Member)
    .ToList();
tinudu
  • 1,139
  • 1
  • 10
  • 20
  • I have tried your solution but it does not sort the records as I wanted. Thanks anyway for your help. – Tajuddin Aug 07 '17 at 01:43
  • Could you please give an example input that does not sort as you want, along with the desired correct result? – tinudu Aug 07 '17 at 06:58