【问题标题】:Document Number Evaluated Improperly Due to String Compare由于字符串比较,文档编号评估不正确
【发布时间】:2013-03-06 15:51:17
【问题描述】:

    我有一份要评估的文件编号列表。这个列表非常大,我不想将所有文档编号都转换为 int,因为它们也可以包含字母。这是一个数字列表示例。

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

    这些数字包含在 C# 中的对象中,我运行 Linq 以返回 Range 中的对象列表。这是我的 linq 代码。

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

如果我的范围是

From: 1
To:      10000

    用户希望取回该范围内的所有数字编号交易。但是,因为我正在比较字符串,所以我没有得到任何结果。

    我可以检测到我的 to 和 from 值是数字,然后只评估带有数字的交易并使用基于该数字的 linq 语句(或编辑 linq 语句以进行转换和比较),但我关心的是性能这一点是因为返回时列表可能相当大。

    我对这里的建议持开放态度,并且知道可能有许多路径都会导致相同的结果。我最关心的是性能。请记住,我可能会对非常大的数据集执行此操作,并且我需要在合理的时间内返回结果。

提前感谢您的任何建议。

~~~编辑~~~

其他信息

    我使用的对象是 QuickBooks Online 的 Intuit IPP SDK 对象。我正在查询文档(例如发票)的直觉,并且需要对文档编号进行排序。 Intuit 不在他们的服务器端执行此操作,因此我必须在我方执行此操作。根据用户为其他搜索条件输入的内容,我最终可能会将他们的所有文档都包含在返回的列表中。

【问题讨论】:

  • “非常大”有多大?我建议首先以最简单的方式让代码工作,然后测试它的性能,而不是假设它会太慢。 (另外,你为什么使用AsQueryable?)“CO2000”应该被视为2000,还是不被视为一个值?
  • 在选择范围时是否要包含“字母数字”文档编号?我建议创建自己的IComparer 而不是使用String.Compare
  • 在这种情况下,不返回包含字母字符的文档编号。我使用 AsQueryable 是因为“MyObject”实际上是我用来与其他产品交互的 SDK 对象列表。
  • 很大的数字很容易超过 100,000。我没有确切的数字,这取决于用户在外部包中创建的对象数量。

标签: c# .net string linq


【解决方案1】:

创建你自己的比较器,比如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;
    }
}

然后创建它的一个实例并使用它来过滤:

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

如果您想要更快的比较,您应该将您的“DocNumber”预处理为整数,但这仅在您处理文档列表一次然后多次查询时才有意义。


预处理的一个简单解决方案是从Header.DocNumber 字符串值和解析的数字值创建一个Dictionary&lt;string, int&gt;。对于不解析的值,您可以使用-1Int32.MinValue 之类的东西,或者制作更高级的解析器来处理非数字值。

一旦你有了字典,你就可以像这样查询......

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;

设置这个字典需要一些额外的时间,但是如果你多次查询同一个文档集,它会得到回报。

【讨论】:

  • 我希望对列表进行预处理。那会让事情变得容易得多。也许随着 intuit 的 IPP v3 将于今年晚些时候发布,它们将包括额外的过滤,这样我就不必做所有这些了。感谢您的回复。我将使用我们的样本数据进行一些测试,看看它们与性能的比较。
  • 我最终使用了您建议的比较器类。我没有做字典路由,因为我只会传递一次数据(尽管我在这个程序中都有字典用于重复使用的数据。对云的调用越少越好)。比较器方法对性能没有任何明显影响,并且当用户仅使用数字搜索时可能实际上提高了性能,因为我添加了一个检查以简化处理。谢谢。
【解决方案2】:

当你得到范围时,用空格左填充起始数字,使其与结束数字的长度相同。所以在你的例子中,你的结束号码是“10000”。因此,将您的起始编号设为“__1”(即 4 个空格,然后是 1)。

比较时,在比较前左填充文档编号。所以:

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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-03
    相关资源
    最近更新 更多