如何在 C# 或 Linq 中比较大于或小于运算符值的两个字节数组?

Posted

技术标签:

【中文标题】如何在 C# 或 Linq 中比较大于或小于运算符值的两个字节数组?【英文标题】:How to compare two byte arrays with greater than or less than operator value in C# or Linq? 【发布时间】:2017-07-19 01:31:27 【问题描述】:

我的 Code First Entity Framework for SQL TimeStamps 中有一个字节数组,映射如下:

 [Column(TypeName = "timestamp")]
 [MaxLength(8)]
 [Timestamp]
 public byte[] TimeStamps  get; set; 

上述属性相当于C#中的SQL server“timestamp”数据类型。

在 SQL Server 中,我可以轻松地比较“时间戳”,如下所示...

SELECT * FROM tableName WHERE timestampsColumnName > 0x000000000017C1A2

我想在 C# 或 Linq Query 中实现相同的目标。在这里,我编写了我的 Linq 查询,但无法正常工作。

 byte[] lastTimeStamp = someByteArrayValue;
 lstCostCenter.Where(p => p.TimeStamps > lastTimeStamp);

我也试过用BitConverter 来比较一个同样不起作用的两字节数组...

 lstCostCenter.Where(p => BitConverter.ToInt64(p.TimeStamps, 0) > BitConverter.ToInt64(lastTimeStamp, 0));

如何在 C# 或 Linq Query 中比较字节数组。

注意 - 我只是不想像使用 SequenceEqual 或任何其他仅比较并返回真或假的方法那样比较两个数组。我希望在 Linq 查询中与大于 > 或小于

【问题讨论】:

您想比较使用数据库(实体框架、LINQ to sql 等)LINQ 查询还是内存中 linq 查询? 字节在数组中是如何排列的?首先是最高有效字节?如果是这样,则尝试使用 .Reverse().ToArray() 将输入字节反转到 BitConverter.ToInt64 调用 @Evk 我想与不在内存中的数据库 Linq 查询进行比较。 @Dilip0165 好的,我在回答中提供了一种使用实体框架的方法。 【参考方案1】:

一种方法是使用IStructuralComparableArray 隐式实现:

byte[] rv1 = new byte[]  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01 ;
byte[] rv2 = new byte[]  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x05 ;

var result = ((IStructuralComparable)rv1).CompareTo(rv2, Comparer<byte>.Default); // returns negative value, because rv1 < rv2

如果出于某种原因您想使用BitConverter,则必须反转数组,因为BitConverter 在大多数体系结构上都是小端(为了安全起见-您应该检查BitConverter.IsLittleEndian 字段并仅在它返回true 时反转)。请注意,这样做效率不高。

var i1 = BitConverter.ToUInt64(rv1.Reverse().ToArray(), 0);
var i2 = BitConverter.ToUInt64(rv2.Reverse().ToArray(), 0);

现在,如果您使用 Entity Framework 并且需要比较数据库查询中的时间戳,情况会有所不同,因为 Entity Framework 会检查您的查询表达式以寻找它理解的模式。它不理解IStructuralComparable 比较(当然还有BitConverter 转换),所以你必须使用一个技巧。声明名称为Compare的字节数组的扩展方法:

static class ArrayExtensions 
    public static int Compare(this byte[] b1, byte[] b2) 
        // you can as well just throw NotImplementedException here, EF will not call this method directly
        if (b1 == null && b2 == null)
            return 0;
        else if (b1 == null)
            return -1;
        else if (b2 == null)
            return 1;
        return ((IStructuralComparable) b1).CompareTo(b2, Comparer<byte>.Default);
    

并在 EF LINQ 查询中使用它:

var result = ctx.TestTables.Where(c => c.RowVersion.Compare(rv1) > 0).ToList();

在分析时,EF 将看到名称为 Compare 和兼容签名的方法,并将其转换为正确的 sql 查询(从 RowVersion > @yourVersion 的表中选择 *)

【讨论】:

不知道Array 实现了IStructuralComparable 接口:-) ArrayExtension 类的方法“Compare”解决了我的问题,但我可以将“Compare”方法重命名为“ByteArrayCompare”以获得更好的可读性吗?在 Linq 中,我们已经内置了“比较”方法。 不幸的是你不能,因为这就是 Compare 的名字,它使这个技巧完全有效。如果使用任何其他名称,它将因 NotSupportedException 而失败。请注意,您可以使用名称为 Compare 的任何兼容方法,参数不必是字节数组。例如“int Compare(this object b1, object b2)”也可以。 @Dilip0165 我已经更新了我上一条评论,也许你可以重用你现有的 Compare 方法。 @xanatos 它还实现了 IStructuralEquatable ,可以用来代替通常的 linq SequenceEqual。【参考方案2】:

如果你知道这两个字节数组长度相等并且首先是最重要的字节,那么这有效:

Func<byte[], byte[], bool> isGreater =
    (xs, ys) =>
        xs
            .Zip(ys, (x, y) => new  x, y )
            .Where(z => z.x != z.y)
            .Take(1)
            .Where(z => z.x > z.y)
            .Any();

如果我使用以下内容进行测试:

byte[] rv1 = new byte[]  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01 ;
byte[] rv2 = new byte[]  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x05 ;

Console.WriteLine(isGreater(rv1, rv2));
Console.WriteLine(isGreater(rv2, rv1));

...我得到了预期的结果:

错误的 真的

【讨论】:

以上是关于如何在 C# 或 Linq 中比较大于或小于运算符值的两个字节数组?的主要内容,如果未能解决你的问题,请参考以下文章

确定范围重叠,包括小于和大于范围

Oracle 数据类型比较规则

如何和我将“或”运算符引入 linq 查询连接

如何检查时间是不是大于或小于PLSQL中的另一个时间

如何找到小于或等于 X 的最大值和大于或等于 X 的最小值?

如何检查存储过程中两个 SELECT 语句的输出是不是相等、大于或小于?