Open doboczyakos opened 5 months ago
@doboczyakos which version of EF Core are you using? Also, please provide full standalone repro that shows the problem.
@doboczyakos which version of EF Core are you using? Also, please provide full standalone repro that shows the problem.
EF Core 8.0.5
Sorry, I won't. I provided the entities. I got rid of owned entities and I use a simple Json ValueConverter instead. It's perfect.
@doboczyakos which version of EF Core are you using? Also, please provide full standalone repro that shows the problem.
@maumar, here is a full standalone minimal repro (it was tested with EF Core 8.0.2 and 8.0.5, the results were the same):
void Main()
{
using (var context = new TestContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var theDerivedEntity = new TheDerivedEntity
{
Foo = new Foo
{
Bar = "hello"
}
};
context.TheBaseEntities.Add(theDerivedEntity);
context.SaveChanges();
}
using (var context = new TestContext())
{
// it will cause error: "Cannot get the value of a token type 'Null' as a string."
context.TheBaseEntities.ToList();
}
}
public class TestContext : DbContext
{
public DbSet<TheBaseEntity> TheBaseEntities { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.LogTo(Console.WriteLine)
.UseSqlServer(@"Data Source=sqlserver; Initial Catalog=YYY; Persist Security Info=True; User ID=sa; Password=ThePassword; Encrypt=false");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<TheDerivedEntity>()
.OwnsOne(m => m.Foo)
.ToJson();
}
}
public class TheBaseEntity
{
public int Id { get; init; }
[Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; } = null!;
}
public class TheDerivedEntity : TheBaseEntity
{
public required Foo Foo { get; init; }
}
public class Foo
{
public required string Bar { get; init; }
}
Note that if we have a single TheEntity
entity type with no TPH then there is no error:
public class TheEntity
{
public int Id { get; init; }
[Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; } = null!;
public required Foo Foo { get; init; }
}
The problem seems to be that when using TPH the TableSharingConcurrencyTokenConvention
automatically puts a RowVersion
byte array property into the owned entity type Foo
, which the JSON deserializer cannot handle.
If we have a single TheEntity
entity type, but we put an explicit byte array (does not even have to be a real rowversion with timestamp attribute) into the Foo
entity type, then the error emerges:
public class TheEntity
{
public int Id { get; init; }
[Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; } = null!;
public required Foo Foo { get; init; }
}
public class Foo
{
public required string Bar { get; init; }
public byte[] ByteArray { get; init; } = null!; // causes problem
}
However, if we make the ByteArray
property nullable, then the error goes away:
public class Foo
{
public required string Bar { get; init; }
public byte[]? ByteArray { get; init; }
}
Or, if we initialize the ByteArray
property so it does not contain null
value, then the error also goes away:
public class Foo
{
public required string Bar { get; init; }
public required byte[] ByteArray { get; init; }
}
var theEntity = new TheEntity
{
Foo = new Foo
{
Bar = "hello",
ByteArray = new byte[8]
}
};
@davidnemeti thank you! I am able to reproduce this on my end. It also repros on our latest bits in main.
support for concurrency on JSON types is tracked here: https://github.com/dotnet/efcore/issues/29501
System.InvalidOperationException Cannot get the value of a token type 'Null' as a string.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ExpectedString(JsonTokenType tokenType) at lambda_method2006(Closure, QueryContext, Object[], JsonReaderData) at lambda_method2005(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable
1.AsyncEnumerator.MoveNextAsync() at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable
1 source, CancellationToken cancellationToken)public class Foo { public required Foo2 Foo2{ get; init; } public required string Bar{ get; init; } }
public class Foo2 { ... }
builder.OwnsOne(m => m.Foo).ToJson().OwnsOne(foo=> foo.Foo2);
{..., "_TableSharingConcurrencyTokenConvention_RowVersion":null,"Foo2":{..., "_TableSharingConcurrencyTokenConvention_RowVersion":null}}