dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.65k stars 3.15k forks source link

Reverse engineering rowversion/timestamp on SQL Server should generate ulong property #12435

Open ajcvickers opened 6 years ago

ajcvickers commented 6 years ago

Because mapping to ulong with conversion is a nicer experience that mapping to byte[]. See #5936

ajcvickers commented 6 years ago

Note that #12434 and #12436 should be implemented first.

patrikrazem commented 6 years ago

There appears to be a bug in the way RowVersion/Timestamp fields are converted from byte[] to ulong.

I have two models, both of which have Timestamp properties:

public class First
{
    public Guid ID { get; set; }
    public ulong Timestamp { get; set; }
}

public class Second
{
    public Guid Id { get; set; }
    public ulong Timestamp { get; set; }

    public Guid? OtherID { get; set; }
    public First Other { get; set; }
}

that are mapped as RowVersion fields:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<First>()
                .Property(e => e.Timestamp)
                .HasColumnType(“RowVersion”)
                .IsRowVersion();

    modelBuilder.Entity<Second>()
                .Property(e => e.Timestamp)
                .HasColumnType(“RowVersion”)
                .IsRowVersion();
}

Note that OtherID on the Second model is a nullable property.

If I run the following query:

dbContext.Seconds.Include(s => Other).ToList();

as long as the OtherID is not NULL, everything works fine. But when it's set to NULL, apparently the Other referenced entity's Timestamp property is still being converted for some reason (or at least an attempt is made to convert it).

Because that's NULL, an exception is thrown from the NumberToBytesConverter:

Unhandled Exception: System.InvalidOperationException: An exception occurred while reading a database value for property 'First.Timestamp'. See the inner exception for more information. ---> System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at Microsoft.EntityFrameworkCore.Storage.ValueConversion.NumberToBytesConverter`1.ReverseLong(Byte[] bytes)
   at lambda_method(Closure , DbDataReader )
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Metadata.Internal.EntityMaterializerSource.ThrowReadValueException[TValue](Exception exception, Object value, IPropertyBase property)
   at lambda_method(Closure , DbDataReader )
   at Microsoft.EntityFrameworkCore.Storage.Internal.TypedRelationalValueBufferFactory.Create(DbDataReader dataReader)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at EFConcurrencyTest.Program.Main(String[] args) in <project_main>

If I understand this correctly, the converter is not handling NULL values properly?

ajcvickers commented 6 years ago

@patrikrazem This looks like a duplicate of issue #12518

patrikrazem commented 6 years ago

You're absolutely right, I must've missed that issue.

AndriySvyryd commented 4 years ago

Also recommend using ulong? for rowversion properties in Docs