dotnet / dotnet/efcore

Loading derived entity

Open
#27,633 3 comments 0 reactions 0 assignees View on GitHub
area-query customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Minimal reproducible example

please see [minimal reproducible example](https://github.com/enterprisebug/EfCoreMinimalReproducibleExample)

DemoDbContext .cs

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EfCoreMinimalReproducibleExample
{
public class DemoDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
optionsBuilder.UseSqlServer("Server=127.0.0.1,5433;Initial Catalog=DemoDbContextTable;User Id=sa;Password=Pass@word;MultipleActiveResultSets=true");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(b =>
{
b.ToTable("BaseEntities");
b
.HasDiscriminator("Discriminator")
.HasValue("ServiceContractCalculationInformationDiscriminator")
.HasValue("MaintenanceCalculationInformationDiscriminator");
});

modelBuilder.Entity(b =>
{
b.OwnsOne(x => x.OperatingHourRange, ob =>
{
ob.Property(x => x.Start).HasConversion(ValueTypeConverters.OperatingHoursValueConverter);
ob.Property(x => x.End).HasConversion(ValueTypeConverters.OperatingHoursValueConverter);
});
b.Property(x => x.RunTimePerYearAndEngine).HasConversion(ValueTypeConverters.OperatingHoursValueConverter);
});
}

public DbSet CalculationInformations => Set();
public DbSet DerivedEntity1s => Set();
public DbSet MaintenanceCalculationInformations => Set();
public DbSet RootEntities => Set();
}

public static class ValueTypeConverters
{
public static ValueConverter OperatingHoursValueConverter { get; } = new(
v => v.Value,
v => new OperatingHours(v)
);
}

public class RootEntity {

protected RootEntity() => (BaseEntity) = (null!);

public RootEntity(CalculationInformation baseEntity)
{
BaseEntity = baseEntity;
}

public Guid Id { get; set; }
public virtual CalculationInformation BaseEntity { get; private set; }
}

public abstract class CalculationInformation
{
protected CalculationInformation() { }
public Guid Id { get; private set; }
internal abstract OperatingHours OverallOperatingHours { get; }
}

public class ServiceContractCalculationInformation : CalculationInformation
{
protected ServiceContractCalculationInformation() => (OperatingHourRange, RunTimePerYearAndEngine) = (null!, null!);

internal ServiceContractCalculationInformation(OperatingHourRange operatingHourRange, OperatingHours runTimePerYearAndEngine) : base()
{
OperatingHourRange = operatingHourRange;
RunTimePerYearAndEngine = runTimePerYearAndEngine;
}
public OperatingHourRange OperatingHourRange { get; private set; }
public OperatingHours RunTimePerYearAndEngine { get; private set; }

internal override OperatingHours OverallOperatingHours => OperatingHours.Zero;
}

public class MaintenanceCalculationInformation : CalculationInformation
{
protected internal MaintenanceCalculationInformation() : base()
{

}
internal override OperatingHours OverallOperatingHours => OperatingHours.Zero;
}
}

public record OperatingHours
{
public OperatingHours(int value)
{
Value = value;
}

public static OperatingHours Zero => new(0);

public int Value { get; }
}

public record OperatingHourRange
{
// if the next line is uncommented it works. Why?
//protected OperatingHourRange() => (Start, End) = (null!, null!);
public OperatingHourRange(OperatingHours start, OperatingHours end)
{
Start = start ?? throw new ArgumentNullException(nameof(start));
End = end ?? throw new ArgumentNullException(nameof(end));
}
public OperatingHours Start { get; private init; }
public OperatingHours End { get; private init; }
}

Program.cs

using EfCoreMinimalReproducibleExample;

using var ctx = new DemoDbContext();

await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

var maintenanceCalculationInformation = new MaintenanceCalculationInformation();
var rootEntity = new RootEntity(maintenanceCalculationInformation);
await ctx.AddAsync(rootEntity);
await ctx.SaveChangesAsync();

using var newCtx = new DemoDbContext();
var loaded = newCtx.RootEntities.First();

Console.WriteLine(loaded.BaseEntity);

### Stacktrace

```
System.ArgumentNullException: 'Value cannot be null. (Parameter 'start')'
at OperatingHourRange..ctor(OperatingHours start, OperatingHours end) in C:\Source\Repos\EfCoreMinimalReproducibleExample\EfCoreMinimalReproducibleExample\DemoDbContext.cs:line 117
at Castle.Proxies.OperatingHourRangeProxy..ctor(IInterceptor[] , OperatingHours start, OperatingHours end)
```

### Observations

#### Run with "F5".
Error that is thrown is:
![image](https://user-images.githubusercontent.com/1539741/158059006-4927b3bf-80d5-47f7-a6cd-20de48e4e9b3.png)

#### Behavior when inspecting the variable in debug session
if you set a debugger breakpoint in `Program.cs:16` and hover over `loaded.BaseEntity` you see:
![image](https://user-images.githubusercontent.com/1539741/158059020-ce8bcbc9-9a59-4caa-9e90-ce52b984703a.png)
if you then continue the execution no error occurs.
![image](https://user-images.githubusercontent.com/1539741/158059012-fd907b2e-f04e-481e-b784-bc593bf3e6b1.png)

#### Interesting observation
if you uncomment `DemoDbContext.cs:114`, run with "F5" no error occurs

### Include provider and version information

EF Core version:
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: net6.0
Operating system: Windows 21H2 (OS Build 19044.1526)
IDE: Visual Studio 2022 17.0.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.