I've gone through a lot of trial and error to get to this point. I've learned that if I attach an owning relationship (i.e., Value Object, Complex Type, etc) to the base class in an inheritance structure (using TPT), then I get an error when trying to create an instance of the derived class.
For example, if I have a Cat which is a Pet which is an Animal and I've defined a TrackingInformation class that is owned by the Animal base class, I can't seem to instantiate a new Cat with new TrackingInformation on it.
Click to expand
```C#
public class AnimalContext : DbContext
{
public DbSet
public DbSet
public DbSet
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=animals.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Animal>(
entityBuilder => { entityBuilder.OwnsOne(animal => animal.TrackingInformation); });
}
}
public class TrackingInformation
{
public string TrackingId { get; }
public DateTime? Timestamp { get; }
protected TrackingInformation()
{
}
public TrackingInformation(string trackingId, DateTime? timestamp)
{
TrackingId = trackingId;
Timestamp = timestamp;
}
}
[Table("Animals")]
public abstract class Animal
{
public int Id { get; protected set; }
public TrackingInformation TrackingInformation { get; set; }
}
[Table("Pets")]
public abstract class Pet : Animal
{
public string Name { get; set; }
protected Pet()
{
}
public Pet(string name)
{
Name = name;
}
}
[Table("Cats")]
public class Cat : Pet
{
public int Iq { get; set; }
protected Cat()
{
}
public Cat(string name, int iq) : base(name)
{
Iq = iq;
}
}
</details>
#### The driving program
<details>
<summary>Click to expand</summary>
```C#
static void Main()
{
using (var db = new AnimalContext())
{
Console.WriteLine("Creating a cat");
var cat = new Cat("Tabby", 92);
var tracking = new TrackingInformation("abc", DateTime.Now);
cat.TrackingInformation = tracking;
Console.WriteLine("Adding the cat");
db.Cats.Add(cat);
Console.WriteLine("Added the cat. Now saving the cat.");
db.SaveChanges();
Console.WriteLine("Saved the cat");
db.SaveChanges();
Console.WriteLine("Getting the cat");
var theCat = db.Cats.FirstOrDefault(c => c.Id == 1);
Console.WriteLine("Found a cat: " + (theCat.Name ?? "Unnamed"));
Console.WriteLine("It was being tracked at: " + theCat.TrackingInformation?.Timestamp);
Console.WriteLine("Getting all animals");
var animals = db.Animals.ToList();
Console.WriteLine($"Found {animals.Count} Animals");
Console.WriteLine("Getting all pets");
var pets = db.Pets.ToList();
Console.WriteLine($"Found {pets.Count} Pets");
Console.WriteLine("Getting all cats");
var cats = db.Cats.ToList();
Console.WriteLine($"Cats: {cats.Count}");
Console.WriteLine("Removing the cat");
db.Cats.Remove(theCat);
db.SaveChanges();
}
}
Which results in the following output
Click to expand
Creating a cat
Adding the cat
Added the cat. Now saving the cat.
Unhandled exception. System.InvalidOperationException: The value of 'TrackingInformation.AnimalId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave()
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(Boolean cascadeChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.Storage.NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at EFGetStarted.Program.Main() in /home/jsmalley/Documents/Projects/EFCore5Testing/EFGetStarted/Program.cs:line 19
Now, I know that if I add and save the new cat object before attaching the TrackingInformation instance, then attaching it and re-saving (e.g., move the assignment after the Console.WriteLine("Saved the cat") statement), then I can successfully save the object. However, then I get an error when it gets to the line where it tries to get the FirstOrDefault Cat object:
Click to expand
Creating a cat
Adding the cat
Added the cat. Now saving the cat.
Saved the cat
Getting the cat
Unhandled exception. System.InvalidOperationException: The entity type 'Cat' is not mapped to the store object 'Animals'.
at Microsoft.EntityFrameworkCore.Metadata.Internal.TableBase.IsOptional(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.SqlExpressionFactory.AddSelfConditions(SelectExpression selectExpression, IEntityType entityType, ITableBase table)
at Microsoft.EntityFrameworkCore.Query.SqlExpressionFactory.AddConditions(SelectExpression selectExpression, IEntityType entityType, ITableBase table)
at Microsoft.EntityFrameworkCore.Query.SqlExpressionFactory.Select(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.CreateShapedQueryExpression(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitExtension(Expression extensionExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitExtension(Expression extensionExpression)
at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
at EFGetStarted.Program.Main() in /home/jsmalley/Documents/Projects/EFCore5Testing/EFGetStarted/Program.cs:line 24
If I remove all code referencing the TrackingInformation class, then everything works as expected. Is there something I'm doing wrong?
Click to see the .csproj file used in the above example
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0-rc.1.20451.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0-rc.1.20451.13" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations" />
</ItemGroup>
</Project>
EF Core version: I've tried with 5.0.0-rc.1.20451.13 and the daily build 6.0.0-alpha.1.20502.4
Database provider: I've tried with both Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Sqlite
Target framework: netcoreapp3.1
Operating system: Fedora 31 (but also experienced using a docker image based on mcr.microsoft.com/dotnet/core/sdk:3.1)
IDE: Rider 2020.2.4
Issue happens since entity type is Cat and first mapping has table of Animals. Should above code only be executed when entity type is root type since other wise it cannot be optional dependent in table sharing? If the answer to the question is no, then we need to come up with better way to find the table.
@smitpatel Optional dependents could still use TPH. The bug is in IsOptional, we are only adding the directly derived types in Animals
@HaisojYellams This is fixed and should be in the GA daily build any time now. It would be great if you could verify it is fixed on your end.
I will test it out later this week and report my findings. Thanks for getting this worked out so quickly!
Just wanted to follow up (sorry it took so long - illness in the house kept me from work).
I believe the issue has been resolved. I haven't been able to dive too deeply into our specific use-cases yet, but all of the features I expected to work out-of-the-box are working. Thanks again for fixing that! :-)