Charles
Charles

Reputation: 756

Document Number Evaluated Improperly Due to String Compare

    I have a list of document numbers to be evaluated. This list is very large and I would rather not cast all document numbers to an int because they can also contain letters. Here is a sample list of numbers.

1070
1071
1072
1073
1074
1075
1076
1077
1078
CO1089
CO1099
CO2000

    These numbers are contained in objects in C# and I run Linq to return a list of objects in a Range. Here is my linq code.

results = from row in MyObjectList.AsQueryable<MyObject>()
          where String.Compare(row.Header.DocNumber, _sDocumentStartNumber) >= 0 
          && String.Compare(row.Header.DocNumber, _sDocumentEndNumber) <= 0
          select row;

If I have a range of

From: 1
To:      10000

    A user would expect to get back all of the numeric numbered transactions within that range. However, since i am comparing strings I get back no results.

    I could detect that my to and from values are numeric and then evaluate only transactions with numbers and use a linq statement based on that (or edit the linq statement to do the conversion and comparisons), but I am concerned with performance at this point since the lists can be rather large when returning.

    I am open to suggestions here and know that there may be many paths that all lead to the same result. My key concern in this is performance. Remember that I could be performing this action on very large sets of data and I need the results returned in a reasonable amount of time.

Thank you in advance for any and all suggestions.

~~~ EDIT ~~~

Additional Information

    The objects that I am leveraging is Intuit IPP SDK objects for QuickBooks Online. I am querying intuit for documents (such as invoices) and need to sort on the document numbers. Intuit does not do this on their server side so I have to do this on my side. Depending on what the user enters for other search criteria I could end up with ALL of their documents in a returned List.

Upvotes: 0

Views: 88

Answers (2)

w5l
w5l

Reputation: 5766

Create your own comparer, say DocumentNumberWithinRangeComparer:

public class DocumentNumberWithinRangeComparer
{
    public int? RangeFrom { get; set; }
    public int? RangeTo { get; set; }

    public DocumentNumberWithinRangeComparer(int? from, int? to)
    {
        RangeFrom = from;
        RangeTo = to;
    }

    public bool IncludeInResults(MyObject obj)
    {
        if (!RangeTo.HasValue || !RangeFrom.HasValue)
            return true;

        int docnumber;
        if (!Int32.TryParse(obj.Header.DocNumber, out docnumber))
            return false;

        return docnumber >= RangeFrom.Value && docnumber <= RangeTo.Value;
    }
}

Then create an instance of it and use that to filter:

var comparer = new DocumentNumberWithinRangeComparer(0,100);
var results = from row in MyObjectList.AsQueryable<MyObject>()
              where comparer.IncludeInResults(row)
              select row;

If you want faster comparing, you should pre-process your "DocNumber" to be integers, but this only makes sense if you process the list of documents once and then query it multiple times.


A simple solution for pre-processing would be to create a Dictionary<string, int> from Header.DocNumber string value and the parsed number value. For values that dont parse you could use something like -1, Int32.MinValue, or make a more advanced parser that can deal with non-numeric values.

Once you have the dictionary you could query like this...

var dictionary = new Dictionary<string, int>(); // Fill this...
var results = from row in MyObjectList.AsQueryable<MyObject>()
              where dictionary[row.Header.DocNumber] >= _sDocumentStartNumber &&
                    dictionary[row.Header.DocNumber] <= _sDocumentEndNumber 
              select row;

It will take some extra time to set up this dictionary, but it will pay back if you query the same document set multiple times.

Upvotes: 1

Jim Mischel
Jim Mischel

Reputation: 134045

When you get the range, left-pad the starting number with spaces so that it's the same length as the ending number. So in your example, your ending number is "10000". So make your starting number "__1" (that is, 4 spaces and then a 1).

When comparing, left-pad the document number before comparing. So:

string paddedStart = _sDocumentStartNumber.PadLeft(_sDocumentEndNumber.Length);
string padded;
results = from row in MyObjectList.AsQueryable<MyObject>()
      let padded = row.Header.DocNumber.PadLeft(paddedStart.Length)
      where String.Compare(padded, paddedStart) >= 0 
          && String.Compare(padded, _sDocumentEndNumber) <= 0
      select row;

Upvotes: 0

Related Questions