-
Notifications
You must be signed in to change notification settings - Fork 11
Description
Issue:
Primitive collections persistence works pretty well when set on classes that aren't mapped to a TPH inheritance scenario (haven't tested with TPT/TPC yet). When fetching/reading from an entity of a child class containing a Primitive Collection as a field, the exception IndexOutOfRangeExecption is thrown.
Exception w/ Stack Trace
Exception has occurred: CLR/System.IndexOutOfRangeException
An unhandled exception of type 'System.IndexOutOfRangeException' occurred in System.Private.CoreLib.dll: 'Index was outside the bounds of the array.'
at lambda_method63(Closure, ValueBuffer)
at System.Linq.Utilities.<>c__DisplayClass2_03.<CombineSelectors>b__0(TSource x) at System.Linq.Enumerable.IteratorSelectIterator2.TryGetFirst(Boolean& found)
at lambda_method62(Closure)
at kDg.FileBaseContext.Infrastructure.Query.FileBaseContextQueryExpression.ResultEnumerable.GetEnumerator()
at kDg.FileBaseContext.Infrastructure.Query.FileBaseContextShapedQueryCompilingExpressionVisitor.QueryingEnumerable1.Enumerator.MoveNextHelper() at kDg.FileBaseContext.Infrastructure.Query.FileBaseContextShapedQueryCompilingExpressionVisitor.QueryingEnumerable1.Enumerator.MoveNext()
at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found)
at lambda_method61(Closure, QueryContext)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
Model Setup:
public class Person
{
public int Id { get; set; }
public string Username { get; set; }
} public class Player : Person
{
public uint Level { get; set; }
public List<string> Voicelines { get; set; } = [];
}DbContext Setup:
public class FileDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
private readonly string? _dbPath;
public FileDbContext()
{
_dbPath = "local_db";
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseFileBaseContextDatabase(
_dbPath,
applyServices: services =>
{
services.ConfigureJsonSerializerOptions(options =>
{
options.WriteIndented = true;
});
}
)
.EnableSensitiveDataLogging();
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var jsonOptions = this.GetService<IServiceProvider>()
.GetRequiredService<JsonSerializerOptions>();
modelBuilder
.Entity<Person>()
.HasDiscriminator<string>("person_type")
.HasValue<Person>("person_base")
.HasValue<Player>("player");
modelBuilder.Entity<Player>(e =>
{
e.Property(pl => pl.Voicelines)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<List<string>>(v, jsonOptions) ?? new List<string>()
);
});
}
}Test:
using (var db = new FileDbContext())
{
Player player =
new()
{
Username = "CrazyFrog",
Voicelines =
[
"People die if they are killed..",
"Ze Healing is not as rewarding as the hurting...",
"Just because you're correct doesn't mean you're right.."
]
};
db.Persons.Add(player);
db.SaveChanges();
}
using (var db = new FileDbContext())
{
Player player = (Player)db.Persons.First(); // IndexOutOfBounds thrown!
foreach (var line in player.Voicelines)
{
Console.WriteLine(line);
}
// Similarly, calling e.g. db.Persons.First(); without casting throws this error.
}The data will be saved, it's only when the entities are read from the db that the exception is thrown. Here's how it's stored:
[
{
"Id": 1,
"Username": "CrazyFrog",
"person_type": "player",
"Level": 0,
"Voicelines": "[\n \u0022People die if they are killed..\u0022,\n \u0022Ze Healing is not as rewarding as the hurting...\u0022,\n \u0022Just because you\\u0027re correct doesn\\u0027t mean you\\u0027re right..\u0022\n]"
}
]