Reputation: 2616
I am getting rowVersion
from database as byte[8]
var rowVersion= new MyContext().Employee.FirstOrDefault(x => x.Id).rowVersion;
// suppose above the actual databse values is 0x0000000000038B8C
var rowVersionToLong = BitConverter.ToInt64(rowVersion,0);
Now if i manually do this:
String rowversionStr = "0x0000000000038B8C";
byte[] mybyteArray = System.Text.ASCIIEncoding.ASCII.GetBytes(rowversionStr);
This gives me byte[18]
and when i convert it to Int64
it gives me different value.
i am not getting this.
I want to pass rowVersion
as parameter to WebApi
get method. As Byte[]
is not allowed so i am passing it as a string
Update:
public IHttpActionResult Get(string rowVersion, int id)
{
var exisitingRowVersion = long.Parse(rowVersion.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
var result = new MyContext().employees.ToList().Where(x => x.Id == 2 && BitConverter.ToInt64(x.RowVersion, 0) > exisitingRowVersion);
return Ok(result);
Not getting why this one doesn't work. We are basically comparing long with long
Upvotes: 4
Views: 6147
Reputation: 146208
In the debug logs EF Core shows timestamps in the same format that they appear in SQL Server Management Studio. I needed to be able to convert the byte[8]
for timestamp
/ rowversion
to the same format for comparison.
eg. optimistic concurrency checks you'll see parameters like this.
@p3='0x0000000005B7566B'
After a lot of poking around in the source code for EF Core and not finding where they do it I eventually found this very simple way:
Convert.ToHexString(order.RowVersion)
// gives 8 bytes
Convert.FromHexString("0000000005B7566B") // don't include the 0x
PS. I never really understood these "AAAxxx==" timestamps. How is that a helpful format for anyone?
Upvotes: 0
Reputation: 31
Before, short story:
For me works to convert from "AAAxxx==" to byte array using Convert.FromBase64String
and vice versa.
Long story:
In my case I get from SQL 2016 DB the output of complex object including RowVersion from a query that uses FOR JSON PATH and SQL transform RowVersion in string base 64 and dates to string and use that output direct as a result but when is used as request pay load as is in MVCx or WCF fails because they works well with rowversion transform like an output serialized from object to JSON like WCF or MVCx does: rowversion as byte array and date as /Date(###). In order to convert from output of JSON-SQL to JSON-MVC-WCF compatible I have to transform the response before the outout in the server using an extension that convert in this case for RowVersion with an example value of AAAxxx== to [0,0,0,#,#,#] and for that pass AAAxxx== as the parameter for Convert.FromBase64String and iterate the byte array to output the expected result.
Upvotes: 2
Reputation: 8364
You have three major problems. It shouldn't be this complicated, but it is.
Here is the solution I use: Timestamp.cs. It's way easier. I'll put an example at the end.
rowVersion
is an 8-byte array. Each byte represents a section of a 64-bit integer and will be 0 - 255.
System.Text.ASCIIEncoding.ASCII.GetBytes
encodes ASCII strings, not integers. It returns an 18-byte array. Each byte represents a textual character and will be '0' (48) - '9' (57), 'A' (65) - 'F' (70), or 'x' (120).
Solution: you're on the right track with long.Parse("0x0000000000038B8C".Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
BitConverter.ToUInt64
is either big-endian or little-endian, depending on whether you're running on a system where ulong
is big-endian or little-ending. You can see this for yourself. You need a conversion that will always be big-endian regardless of the system you are running on:
static ulong BigEndianToUInt64(byte[] bigEndianBinary)
{
return ((ulong)bigEndianBinary[0] << 56) |
((ulong)bigEndianBinary[1] << 48) |
((ulong)bigEndianBinary[2] << 40) |
((ulong)bigEndianBinary[3] << 32) |
((ulong)bigEndianBinary[4] << 24) |
((ulong)bigEndianBinary[5] << 16) |
((ulong)bigEndianBinary[6] << 8) |
bigEndianBinary[7];
}
When SQL Server compares 0x0FFFFFFFFFFFFFFF < 0xFFFFFFFFFFFFFFFF
, 0xFFFFFFFFFFFFFFFF
is bigger. To keep the same meaning that SQL Server treats it with, you must use ulong
and not long
. Otherwise 0xFFFFFFFFFFFFFFFF
becomes -1L
rather than what SQL Server considers it as, ulong.MaxValue
.
Granted, 9 quadrillion things have to happen before the high bit of a timestamp
column gets used, but you might use the same code to compare two binary(8)
timestamps that were generated some other way. The important thing is to duplicate SQL Server's comparison behavior.
Here is the solution I use: Timestamp.cs.
Your code becomes:
var existingRowVersion = (Timestamp)ulong.Parse(rowVersion.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
var result = new MyContext().employees.ToList().Where(x => x.Id == 2 && (Timestamp)x.RowVersion > exisitingRowVersion);
Basically once you cast to Timestamp
, you can't go wrong.
Sadly, no matter which method you use, there's no good way to apply this filter on the server-side instead of the client side. That's the subject of this question. Guess what I discovered! A way to do this with Entity Framework 6.1.3! How cool is that?
HOWEVER, and this is unrelated to your question, you should absolutely put the Id == 2
filter on the server side (before you call ToList). Otherwise you are transferring the entire table to your application and then discarding all but one row on the client side. You should do this:
var existingRowVersion = (Timestamp)ulong.Parse(rowVersion.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
var result = new MyContext().employees.Where(x => x.Id == 2).ToList().Where((Timestamp)x.RowVersion > exisitingRowVersion);
Best:
var existingRowVersion = (Timestamp)ulong.Parse(rowVersion.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
var employee = new MyContext().employees.SingleOrDefault(x => x.Id == 2);
if (employee == null) ... // Deleted
else if ((Timestamp)employee.RowVersion > exisitingRowVersion) ... // Updated
Upvotes: 5