My problem relates to sales orders and sales invoices but I find it easier to think of pets and their offspring... without creating a full pedigree model.
My DbContext
using System;
using DevExpress.ExpressApp.EFCore.Updating;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.ExpressApp.Design;
using DevExpress.ExpressApp.EFCore.DesignTime;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using DevExpress.ExpressApp.DC;
using System.Collections.Generic;
namespace Pets.Module.BusinessObjects
{
[TypesInfoInitializer(typeof(PetsContextInitializer))]
public class PetsEFCoreDbContext : DbContext
{
public PetsEFCoreDbContext(DbContextOptions<PetsEFCoreDbContext> options) : base(options)
{
}
public DbSet<Cat> Cats { get; set; }
public DbSet<Dog> Dogs { get; set; }
public DbSet<Kitten> Kittens { get; set; }
public DbSet<Puppy> Puppys { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Pet>()
.HasDiscriminator(x=> x.IsCat)
.HasValue<Cat>(true)
.HasValue<Dog>(false);
modelBuilder.Entity<BabyPet>()
.HasDiscriminator(x => x.IsCat)
.HasValue<Kitten>(true)
.HasValue<Puppy>(false);
modelBuilder.Entity<Puppy>().HasOne(x => x.Parent).WithMany(x => x.Puppies);
modelBuilder.Entity<Kitten>().HasOne(x => x.Parent).WithMany(x => x.Kittens);
}
}
}
My classes
public abstract class Pet
{
[Key] public int Id { get; set; }
public string Name { get; set; }
public bool? IsCat { get; set; }
}
public abstract class BabyPet
{
[Key] public int Id { get; set; }
public int ParentPetId { get; set; }
[ForeignKey("ParentPetId")]
public virtual Pet Parent { get; set; }
public string Name { get; set; }
public bool? IsCat { get; set; }
}
public class Kitten : BabyPet
{
new public virtual Cat Parent { get; set; }
}
public class Dog : Pet
{
public Dog()
{
Puppies = new List<Puppy>();
}
[Aggregated]
public virtual List<Puppy> Puppies { get; set; }
}
public class Cat : Pet
{
public Cat()
{
Kittens = new List<Kitten>();
}
[Aggregated]
public virtual List<Kitten> Kittens { get; set; }
}
public class Puppy : BabyPet
{
new public virtual Dog Parent { get; set; }
}
Also there is
public class PetsContextInitializer : DbContextTypesInfoInitializerBase
{
protected override DbContext CreateDbContext()
{
var optionsBuilder = new DbContextOptionsBuilder<PetsEFCoreDbContext>()
.UseSqlServer(@";");
return new PetsEFCoreDbContext(optionsBuilder.Options);
}
}
However this creates the following structure in BabyPet
Where as I just want
[Update] I was able to get the structure I want by specifying the foreignkey in OnModelCreating
modelBuilder.Entity<Puppy>().HasOne(x => x.Parent).WithMany(x => x.Puppies).HasForeignKey(x=>x.ParentPetId);
modelBuilder.Entity<Kitten>().HasOne(x => x.Parent).WithMany(x => x.Kittens).HasForeignKey(x => x.ParentPetId);
However when I try to add a Kitten to a cat via the XAF Winforms UI I get:
Unable to cast object of type 'SimplePets.Module.BusinessObjects.Kitten' to type 'SimplePets.Module.BusinessObjects.Puppy'.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.get_Item(IPropertyBase propertyBase)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.GetCurrentValue(IPropertyBase propertyBase)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.TryAddPropertyNameToCollection(InternalEntityEntry entity, ICollection`1 propertiesToCheck, IPropertyBase property)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.TryAddPropertyNameToCollection(InternalEntityEntry entity, IProperty property, ICollection`1 propertiesToCheck)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.GetPropertiesToCheck(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.CheckReadWritePermissionsForNonIntermediateObject(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.CheckReadWritePermissions(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.CheckIsGrantedToSave(InternalEntityEntry entity)
at DevExpress.EntityFrameworkCore.Security.NetStandard.ChangeTracking.SecurityStateManager.GetEntriesToSave(Boolean cascadeChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.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 DevExpress.ExpressApp.EFCore.EFCoreObjectSpace.DoCommit()
at DevExpress.ExpressApp.BaseObjectSpace.CommitChanges()
at DevExpress.ExpressApp.Win.SystemModule.WinModificationsController.Save(SimpleActionExecuteEventArgs args)
at DevExpress.ExpressApp.SystemModule.ModificationsController.saveAction_OnExecute(Object sender, SimpleActionExecuteEventArgs e)
at DevExpress.ExpressApp.Actions.SimpleAction.RaiseExecute(ActionBaseEventArgs eventArgs)
at DevExpress.ExpressApp.Actions.ActionBase.ExecuteCore(Delegate handler, ActionBaseEventArgs eventArgs)
I put my example on GitHub here
Docs link about relationships here and tph inheritance is here
I think I must have the data structures correct after my update to onModelCreating. That is :
I was able to work around the Cast Object error by using DBContext instead of ObjectSpace