简体   繁体   中英

Abstract Class with 1 to 1 relationship Entity Framework

Using entity framework I've been trying to create this relationship. Basically I have 1 object which has a Result . The Result object is abstract, as it has to be one of the 3 classes that inherit from Result , ie Approved , Rejected , or Modified :

在此处输入图片说明

I'm trying to create the table structure using Entity Framework. Originally I was going for a TPCT (Table Per Concrete Type) structure, so there would be no Result table, but I wanted to keep the link back in the Action table if I wanted to reference the Result , so now I'm attempting just TPT structure. I find TPCT is cleaner, but ultimately if TPT is the only way to achieve what I want, I'm fine with it.

I've tried variations of the following for my model structure:

public class Action 
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id {get; set;}

    public int Result_Id {get; set;}

    [ForeignKey("Result_Id")]
    public virtual Result Result {get; set;}

    public string Description {get; set;}
}

public abstract class Result
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id {get; set;}

    [Required]
    public int Action_Id {get; set;}

    [ForeignKey("Action_Id")]
    public virtual Action Action {get; set;}

    public string Comment {get; set;}


    public class Approved : Result
    {
        public string Thing {get; set;}
    }

    public class Rejected : Result
    {
        public string Stuff {get; set;}
    }

    public class Modified : Result
    {
        public string Whatever {get; set;}
    }
}

And then I've tried the following 2 strategies in my context file to either implement TPT:

modelBuilder.Entity<Approved>().ToTable("Approved");
modelBuilder.Entity<Rejected>().ToTable("Rejected");
modelBuilder.Entity<Modified>().ToTable("Modified");

Or for TCPT:

modelBuilder.Entity<Approved>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("Approved");
});

modelBuilder.Entity<Rejected>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("Rejected");
});

modelBuilder.Entity<Modified>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("Modified");
});

Everytime I try to add the new migration, whatever I try, I'm faced with this error: Unable to determine the principal end of an association between the types 'Result' and 'Action'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. Unable to determine the principal end of an association between the types 'Result' and 'Action'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

The one time I was able to have it work was if I removed this reference from in the Action class:

public int Result_Id {get; set;}

[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}

But I would really like to keep that reference there so then when I go into my DB to grab that Action object, I can immediately tell if there is a Result associated to it, without having to go through all 3 Result tables to see if there is a reference to that Action (which is why I think I need to have TPT...)

Any help to get this working would be greatly appreciated!

With a lot of research and trial and error, I discovered what I needed to get the result I wanted. It's TPCT DB structure, and the Action object is able to keep the reference to Result . Here are the model classes:

public class Action 
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id {get; set;}

    public virtual Result Result {get; set;} //just virtual here, as Action is the dependent and Result is the principal-- i.e. this Result isn't required

    public string Description {get; set;}
}

public abstract class Result
{
    //got rid of the Result_Id, since it's 1:1 the Action_Id can be the Key

    [Required, Key] //added "Key"
    public int Action_Id {get; set;}

    [ForeignKey("Action_Id")]
    public Action Action {get; set;} //removed this virtual, as Action is Required for Result, that makes Result the principal

    public string Comment {get; set;}


    public class Approved : Result
    {
        public string Thing {get; set;}
    }

    public class Rejected : Result
    {
        public string Stuff {get; set;}
    }

    public class Modified : Result
    {
        public string Whatever {get; set;}
    }
}

And here is the fluent API code from the context:

//this gave me TPCT like I wanted
modelBuilder.Entity<Approved>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("Approved");
});

modelBuilder.Entity<Rejected>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("Rejected");
});

modelBuilder.Entity<Modified>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("Modified");
});

//this defined the principal-dependent relationship I was missing
modelBuilder.Entity<Action>()
    .HasOptional(a => a.Result)
    .WithRequired(a => a.Action)
    .Map(x => x.MapKey("Action_Id"));

And then it worked! Hopefully this example can assist someone else.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM