5

I have a byte array in my Code First Entity Framework for SQL TimeStamps, mapping as given below:

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

The above property is equal to SQL server "timestamp" Data type in C#.

In SQL server I can compare "timestamp" easily as below...

SELECT * FROM tableName WHERE timestampsColumnName > 0x000000000017C1A2

Same thing I want to achieve in C# or Linq Query. Here I have written my Linq query, which is not working properly.

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

I have also tried with BitConverter to compare a two byte array which is also not working...

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

How can I compare byte arrays in C# or Linq Query.

Note - I just do not want to compare two arrays normally like using SequenceEqual or any other methods which are just compare and return true or false. I want the comparison in Linq query with Greater than > or Less than < operator which gives proper data like SQL Server query.

Paul
  • 4,160
  • 3
  • 30
  • 56
Dilip Langhanoja
  • 4,455
  • 4
  • 28
  • 37
  • 3
    You want to compare that using database (Entity Framework, LINQ to sql etc) LINQ query, or in-memory linq query? – Evk Feb 28 '17 at 07:42
  • how are bytes arranged in the array? most significant bytes first? if so, then try reversing bytes on input to the `BitConverter.ToInt64` call with `.Reverse().ToArray()` – slawekwin Feb 28 '17 at 07:46
  • @Evk I want compare with Database Linq Query not in Memory. – Dilip Langhanoja Feb 28 '17 at 09:17
  • @Dilip0165 ok I provided a way to do that with Entity Framework in my answer. – Evk Feb 28 '17 at 09:19

2 Answers2

15

One way is to use IStructuralComparable, which Array implicitly implements:

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

If for some reason you want to use BitConverter, you have to reverse arrays, because BitConverter is little endian on most architectures (to be safe - you should check BitConverter.IsLittleEndian field and reverse only if it returns true). Note that it's not very efficient to do this.

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

Now if you use Entity Framework and need to compare timestamps in database query, situation is a bit different, because Entity Framework will inspect your query expression looking for patterns it understands. It does not understand IStructuralComparable comparisions (and BitConverter conversions too of course), so you have to use a trick. Declare extension method for byte array with the name 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);
    }
}

And use that in EF LINQ query:

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

When analyzing, EF will see method with name Compare and compatible signature and will translate that into correct sql query (select * from Table where RowVersion > @yourVersion)

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Didn't know `Array` implemented the `IStructuralComparable` interface :-) – xanatos Feb 28 '17 at 08:06
  • ArrayExtension class's method "Compare" resolved my issue but can I rename "Compare" method to "ByteArrayCompare" for better readability ? as in Linq we have already inbuilt "Compare" method exists. – Dilip Langhanoja Feb 28 '17 at 10:04
  • Unfortunately you can't, because that's the name Compare which makes this trick working at all. With any other name it will fail with NotSupportedException. Note that you can use any compatible method with name Compare, not necessary for parameters to be byte arrays. For example "int Compare(this object b1, object b2)" will work just as well. – Evk Feb 28 '17 at 10:05
  • @Dilip0165 I've updated my last comment, maybe you can reuse your existing Compare method. – Evk Feb 28 '17 at 10:12
  • 1
    @xanatos it also implements IStructuralEquatable which can be used instead of usual linq SequenceEqual. – Evk Feb 28 '17 at 11:16
  • Why use this `Compare` trick. Why not use `==`? (See https://stackoverflow.com/q/53600146/939213 ) – ispiro Dec 03 '18 at 19:03
  • @ispiro but OP in this question needs greater than operator, not equality. And C# doesnt define this operator for byte array. – Evk Dec 03 '18 at 19:07
  • @Evk Thanks. I seem to have missed the obvious... So equality will compare all bytes with a simple `==`, right? If so, please feel free to answer that question of mine in the link. – ispiro Dec 03 '18 at 19:10
0

If you know that the two byte arrays are equal length and are most significant byte first then this works:

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();

If I test with the following:

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));

...I get the expected result of:

False
True
Enigmativity
  • 113,464
  • 11
  • 89
  • 172