-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Query: SqlFunction to compare byte arrays (necessary to filter on rowversion) #5936
Comments
We want to discuss this with @divega when he is back in the office |
Clearing up for triage. I think for EF Core we should initially consider adding support for translating existing idioms that express this kind of comparisons between byte arrays in .NET, e.g.:
The proposed methods For EF 6.x I solved the problem some time ago for a customer using model functions. I have added that as an alternative answer to the question in StackOverflow.com. PS: @jnm2 your answer in StackOverflow.com is a nice hack! |
@divega, what is your take on a |
@jnm2 I don't have a strong opinion on that right now . I can see that
We have other means to specify (1) and (2) that are not terribly verbose, e.g. data annotations and fluent APIs. Also I think that (3) is actually debatable because In the end it is also a matter of how general purpose (i.e. provider agnostic) those types are vs. the value they add. E.g. it seems to me that |
I only really care about 3. I can add conventions for 1 and 2; I don't care if EF has provider-specific knowledge of them. Or It adds value in polish and making it easy to write the right thing by default. It's always going to feel hackish to write I don't believe operators imply big-endian comparisons and less or more than StructuralComparisons. No storage provider that I'm aware of does little-endian binary comparisons. It's not a very useful concept, for either binary or strings. Plus, are you going to support more kinds of structural comparers, like Rather than comparer instances, I think operators are the ideal level at which to work in terms of conciseness, obviousness, and semantics. |
@jnm2 Personally I agree that easy to use relational operators or methods with the existing comparability semantics would be nice, though I doubt this would require coming up with a new type ( Though from experience, when something so obvious isn't supported, there is usually some reason I didn't know about 😄 I will follow up on that. |
I'm in the similar situation. I need to get entries whose timestamp/rowversion is greater than some number. FromSql is ugly solution so I tried some syntax hacks and I found one acceptable:
translates to
It's also working with shadow property and as a bonus, you don't need to care about endianness. Nevertheless, the built-in support for this column type would be appreciated :) |
@MichalPavlik So long as EF never decides to run that client-side, of course. |
@jnm2 Yeah, that's why I'm looking forward the custom type mapping. For now it seems that explicit casting is ignored (at least in this case). Need to check with every feature version :) |
@MichalPavlik thank you very much for this workaround! |
@divega are there any news since the last message in this thread? or still the best approach is @MichalPavlik 's workaround? |
To provide an update on the thread, With the release of the 2.1 version and the support of value conversions, it's now easier to do that. In Entity public class Entity
{
public ulong RowVersion { get; set; } // anymore byte[]
} In Context modelBuilder.Entity<Entity>(entity =>
{
entity
.Property(e => e.RowVersion)
.HasConversion(new NumberToBytesConverter<ulong>())
.IsRowVersion();
}); In Query ulong sinceRowVersion;
...
.Where(e => e.RowVersion > sinceRowVersion);
... |
@kvpt / MS team, I must be doing something wrong, as in my case, the
The generated migration is clearly wrong when using protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Orders",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
ReferenceNumber = table.Column<string>(nullable: true),
Timestamp = table.Column<byte[]>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Orders", x => x.Id);
});
} As you can see, the field is only I was testing this with "localdb" provider (which should be the same as SQL Server?). The whole @kvpt, can you share your setup with which you've been successful? (I though there might be something off 'cause I'm using |
@skyflyer I think the problem is that @kvpt's description of the workaround is incomplete. I tried a few things and I believe the minimal configuration to make it work is this (notice the modelBuilder.Entity<Order>(entity =>
{
entity
.Property(e => e.RowVersion)
.HasColumnType("RowVersion")
.IsRowVersion();
}); @ajcvickers do we have a test for this? Otherwise, should we add a test to make sure this continues to work in future rleases? |
@divega, excellent! Thanks! The generated migration is still not the same as before, but since the db type hint is present, it is correctly created in the DB and EF concurrency handling works correct as well.. For example: CREATE TABLE [Orders] (
[Id] uniqueidentifier NOT NULL,
[ReferenceNumber] nvarchar(max) NULL,
[Timestamp] RowVersion NOT NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY ([Id])
); |
Mine is a little different, I use the RowVersion type, like this : [RowVersion] RowVersion NOT NULL Because timestamp seem to be deprecated in the documentation. |
@divega Yeah, tests would be good. Did you test whether the query generated is correct? (For example, not client evalled?) |
Now yes 😄 Here is an example of a translated query for a constant: var q = db.Orders.Where(o => o.RowVersion > 2017).ToList(); SELECT [o].[Id], [o].[RowVersion]
FROM [Orders] AS [o]
WHERE [o].[RowVersion] > 0x00000000000007E1 |
Clearing up milestone to bring this up in next triage.
|
Notes from triage:
|
@skyflyer RowVersion is just the name of my column of RowVersion type. |
@kvpt, OK, I misread We were referring to different layers (I was talking about c# prop annotation, which (should) drive the model field type as well), whereas you were referring to the DB type... I believe it is clear now. The renaming of |
At least the following simple way of doing it in EF6 should work on Core also: |
Right now you can look at https://stackoverflow.com/questions/7437970/how-to-query-code-first-entities-based-on-rowversion-timestamp-value and see how difficult this is- it's next to impossible to get right.
Since we can't have comparison operators on byte arrays, the next most idiomatic thing would be to have the following methods in SqlFunctions (or DbFunctions, if possible), naming convention following the
Expression
class:bool LessThan(byte[], byte[])
bool LessThanOrEqual(byte[], byte[])
bool GreaterThan(byte[], byte[])
bool GreaterThanOrEqual(byte[], byte[])
Questions
Equal
andNotEqual
. They would be useful if evaluated client-side, because they would be semantically purer than the==
operator which compares byte arrays by reference. The semantics of byte array==
are incorrect because SQL compares binary by value (as it should).Considered alternatives
Casting
A function that would be cool for other purposes would be
long SqlFunctions.Cast(byte[])
, but that would not work for this scenario because SQL Server only has signed comparisons, plus it's less performant server-side:Doing a multi-step comparison would get around the signed comparison issue, but that's hacky and slower. Casting to
char(8)
would subject you to collation comparisons.Idiomatic binary type
Another alternative would be to use a Binary primitive struct that wraps a byte array and provides all the value-comparison operators and has an implicit conversion to and from a byte array. Additionally, a Timestamp primitive type (mine) for rowversion columns. This puts comparison operators back on the table and would be ideal over SqlFunctions, but obviously will have to wait for #242. If it speeds up #242 all the better!
The text was updated successfully, but these errors were encountered: