dotnet / dotnet/efcore

Nullable owned object after reload entity

Open
#31,440 4 comments 0 reactions 0 assignees View on GitHub
area-change-tracking customer-reported
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.