Nullable owned object after reload entity
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
### File a bug:
I'm trying to reload entity after DbUpdateConcurrencyException but EF Core doesn't reload entity's owned objects.
In my case owned object named as ProductData. I deatch entity and then reload it again by the its key. However, after this my ProductData is null.
ExecuteSqlRawAsync added to simulate DbUpdateConcurrencyException.
### My code
```C#
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace EfCoreReloadEntity;
public class Program
{
public static async Task Main(params string[] args)
{
await SeedData();
await LoadAndReloadEntity();
}
public static async Task SeedData()
{
MyContext context = new();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var product = new Product()
{
Id = 1,
Name = "Product-1",
Items = new()
{
new ProductItem()
{
Id = 1,
Size = 30,
ProductId = 1
},
new ProductItem()
{
Id = 2,
Size = 35,
ProductId = 1
}
},
ProductData = new ProductData()
{
Orders = 1,
}
};
context.Add(product);
await context.SaveChangesAsync();
}
public static async Task LoadAndReloadEntity()
{
MyContext context = new();
Product product = context.Set().First(x => x.Id == 1);
//simulate concurrency
await context.Database.ExecuteSqlRawAsync("UPDATE \"Products\" SET \"Name\" = 'NewName' WHERE \"Id\" = 1");
await context.Database.ExecuteSqlRawAsync("INSERT INTO \"Items\" (\"Id\", \"ProductId\", \"Size\") VALUES (3, 1, 40);");
try
{
product.Name = "Product-3";
//Replace old owned object by new
ProductData newProductData = new()
{
Orders = 111,
};
product.ProductData = newProductData;
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
EntityEntry entry = context.Entry(product);
object?[] keyValues = GetEntityKey(context, entry);
entry.State = EntityState.Detached;
//Single object in entryList
var entryList = context.ChangeTracker.Entries().ToList();
Product newEntity = await context.Set().FindAsync(keyValues)
?? throw new Exception("Entity must not be null");
if (newEntity.ProductData is null)
throw new Exception("ProductData is null");
}
}
public static object?[] GetEntityKey(DbContext context, EntityEntry entry)
{
var metadata = entry.Metadata;
var key = metadata.FindPrimaryKey();
var props = key!.Properties.ToArray();
return props.Select(x => x.GetGetter().GetClrValue(entry.Entity)).ToArray();
}
}
public class Product
{
private readonly uint _version;
public required int Id { get; set; }
public required string Name { get; set; }
public List Items { get; set; }
public ProductData ProductData { get; set; }
}
public class ProductData
{
public int Orders { get; set; }
}
public class ProductItem
{
public int Id { get; set; }
public int Size { get; set; }
public int ProductId { get; set; }
}
public class MyContext : DbContext
{
public DbSet Product { get; set; }
public DbSet Items { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseNpgsql(@"Host=localhost;Port=7435;Database=testdb;Username=admin;Password=testpass")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(builder =>
{
builder
.ToTable("Products");
builder
.Property(p => p.Id)
.HasColumnName("Id");
builder
.Navigation(p => p.Items)
.AutoInclude();
builder
.Property(p => p.Name)
.HasColumnName("Name");
builder
.Property("_version")
.IsRowVersion();
builder
.OwnsOne(x => x.ProductData);
});
}
}
```
### Include the full exception:
```
System.Exception: "ProductData is null"
```
### Include provider and version information
EF Core version: 7.0.10
Database provider: Npgsql 7.0.4
Target framework: NET 7.0
Operating system: Windows 11
IDE: Visual Studio 2022 17.6.4
Contributor guide
Assessment
This issue has not been assessed yet.