-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
Problem
I want to update the original values of an entity manually, therefore I use GetDatabaseValues
.
Unfortunately in a specific edge case the indicies for IProperty
on ArrayPropertyValues
are partially invalid.
This results in an IndexOutOfRangeException because TPH properties indicies do not fit the underlying value array.
The root of the problem might be that
- complex type values are not loaded
- and the indicies TPH properties therfore do not match the value array length.
Not only does this result in an exception, this also:
- affects
EntityEntry.Reload()
because it does the same internally. - complex property data does not get reloaded / loaded.
This problems only occures on TPH properties, there is no exception with normal entities.
Details
The value set loaded contains all proeprties except the complex type ones.
But the index of the additional property of the Supplier
-Type does returns 4
with dbValues.Properties.ToArray()[3].GetIndex()
which results in an IndexOutOfRangeException
System.IndexOutOfRangeException
HResult=0x80131508
Message=Index was outside the bounds of the array.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ArrayPropertyValues.get_Item(String propertyName)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntryPropertyValues.SetValues(PropertyValues propertyValues)
at ConsoleApp2.Program.Main(String[] args) in C:\Users\ericd\source\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 22
Example
The following code should reproduce the exception on the line entry.OriginalValues.SetValues(dbValues);
.
Im using .NET 8.0 with EFCore 8.0.0 and VS Studio 17.8.3.
Before running the code, migrations have to be added (Add-Migration Init
)
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.ComponentModel.DataAnnotations.Schema;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
using (var ctx = new CustomContext())
{
ctx.Database.Migrate();
ctx.Add(new Supplier() { Name = "FooBar", Address = new() { Street = "WhereEver" } });
ctx.SaveChanges();
}
using (var ctx = new CustomContext())
{
var entry = ctx.Entry(ctx.Suppliers.First());
var dbValues = entry.GetDatabaseValues()!;
entry.OriginalValues.SetValues(dbValues);
}
Console.WriteLine("Done!");
Console.ReadLine();
}
}
}
public class CustomContext : DbContext
{
public DbSet<Contact> Contacts { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuting }).EnableSensitiveDataLogging();
builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TestDb;Trusted_Connection=True");
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Contact>().HasDiscriminator(e => e.Discriminator);
}
}
public abstract class Contact
{
public int Id { get; set; }
public string? Discriminator { get; set; }
public required string Name { get; set; }
public required Address Address { get; set; }
}
public class Supplier : Contact
{
public string? Foo { get; set; }
}
public class Customer : Contact
{
public string? Bar { get; set; }
}
[ComplexType]
public class Address
{
public required string Street { get; set; }
}