How to declare a parent child relationship when both tables are TPH and the relationship is in the base classes?

370 views Asked by At

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

Baby Pet Structure

Where as I just want

What I 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

1

There are 1 answers

0
Kirsten On BEST ANSWER

I think I must have the data structures correct after my update to onModelCreating. That is :

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); 

I was able to work around the Cast Object error by using DBContext instead of ObjectSpace

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using SimplePets.Module.BusinessObjects;
using System.Linq;

namespace SimplePets.Module.Win.Controllers
{
    public class KittenViewController : ViewController
    {
        SimpleAction actionAddKittenEF;
        SimpleAction actAddKittenXAF;

        public KittenViewController() : base()
        {
            TargetObjectType = typeof(Kitten);
            TargetViewNesting = Nesting.Nested;

            actAddKittenXAF = new SimpleAction(this, "Add via OS", "View");
            actAddKittenXAF.Execute += actAddKittenXAF_Execute;

            actionAddKittenEF = new SimpleAction(this, "Add via Db", "View");
            actionAddKittenEF.Execute += actionAddKittenEF_Execute;
        }

        private void actionAddKittenEF_Execute(object sender, SimpleActionExecuteEventArgs e)
        {
            var cat = View.ObjectSpace.GetObject(((NestedFrame)Frame).ViewItem.CurrentObject) as Cat;
            var db = Helpers.MakeDb();
            var kitten = new Kitten
            {
                Parent = db.Cats.FirstOrDefault(c => c.Id == cat.Id),
                Name = $"baby {cat.Kittens.Count + 1} of {cat.Name}"
            };
            db.Kittens.Add(kitten);
            db.SaveChanges();
            View.ObjectSpace.Refresh();
        }

        //Errors
        private void actAddKittenXAF_Execute(object sender, SimpleActionExecuteEventArgs e)
        {
            var cat = View.ObjectSpace.GetObject(((NestedFrame)Frame).ViewItem.CurrentObject) as Cat;
            var os = View.ObjectSpace;
            var kitten = os.CreateObject<Kitten>();
            kitten.Parent = cat;
            kitten.Name = $"baby {cat.Kittens.Count + 1} of {cat.Name}";
            View.ObjectSpace.CommitChanges();
            View.ObjectSpace.Refresh();
        }
    }
}