无法在 EF 6 上使用 Fluent API 创建 TPH
Unable to create a TPH using Fluent API on EF 6
我正在为一个 Google 类似表单的项目建模。下面的实体非常简单明了(我猜),如下所示。
问题类型:
// Base class for any kind of question
public abstract class Question : Bean
{
public string Statement { get; set; }
}
// Visual questions are questions where images are answers.
public class VisualQuestion : Question
{
public virtual VisualAnswer Answer { get; set; }
}
// Discursive questions are questions where big texts are answers.
public class DiscursiveQuestion : Question
{
public virtual DiscursiveAnswer Answer { get; set; }
}
// Objective questions are questions that can have multiple answers,
// where each of them should be no bigger than 1 character.
public class ObjectiveQuestion : Question
{
public virtual List<ObjectiveQuestionOption> Options { get; set; }
}
// Options for objective questions.
public class ObjectiveQuestionOption : Question
{
public int ObjectiveQuestionId { get; set; }
public virtual ObjectiveQuestion Question { get; set; }
public virtual ObjectiveAnswer Answer { get; set; }
}
答案类型:
public abstract class Answer : Bean
{
public int QuestionId { get; set; }
}
public class DiscursiveAnswer : Answer
{
public string Answer { get; set; }
public virtual DiscursiveQuestion Question { get; set; }
}
public class ObjectiveAnswer : Answer
{
public char Answer { get; set; }
public virtual ObjectiveQuestion Question { get; set; }
}
public class VisualAnswer : Answer
{
public byte[] Blob { get; set; } // Image answer
public virtual VisualQuestion Question { get; set; }
}
Bean 所在位置:
public abstract class Bean
{
public int Id { get; set; }
}
对于问题,我可以有一个 Question
对象和一个 QuestionOption
用于 objective 问题。如果是这样,我们将需要 Question
中的所有 3 个 Answer
对象,这对我来说听起来不对(需要识别问题类型,然后相应地访问其答案成员,例如 is
和 as
转换)。作为解决方法,我决定使用 TPH 方法将问题拆分为上面定义的 3 个对象,并拥有单独的 Answer 成员。
一切似乎只在 1 个条件下工作:所有流畅的 API 设置必须在 DbContext
class 的 void OnModelCreating(DbModelBuilder modelBuilder)
内完成(我已经覆盖了它).这是一个问题,因为我正在为每个实体对象分离我的所有配置并像这样添加它们:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Approach 1: This works
//modelBuilder.Entity<Question>().Map<DiscursiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("D")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<ObjectiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("O")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<VisualQuestion>(p => p.Requires("TP_QUESTION").HasValue("V")).ToTable("TB_QUESTION");
// Approach 2: This does not work: it complains that 2 of the 3 entities cant share the TB_QUESTION table because "they are not in the same type hierarchy
// or do not have a valid one to one foreign key relationship with matching primary keys between them" (???).
modelBuilder.Configurations.Add(new VisualQuestionConfiguration());
modelBuilder.Configurations.Add(new ObjectiveQuestionConfiguration());
modelBuilder.Configurations.Add(new DiscursiveQuestionConfiguration());
}
这些是配置对象:
public abstract class QuestionConfiguration<T> : EntityTypeConfiguration<T> where T : Question
{
public QuestionConfiguration()
{
Property(p => p.Statement).HasColumnName("STATEMENT");
}
}
public class DiscursiveQuestionConfiguration : QuestionConfiguration<DiscursiveQuestion>
{
public DiscursiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("D")).ToTable("TB_QUESTION");
}
}
public class VisualQuestionConfiguration : QuestionConfiguration<VisualQuestion>
{
public VisualQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("V")).ToTable("TB_QUESTION");
}
}
public class ObjectiveQuestionConfiguration : QuestionConfiguration<ObjectiveQuestion>
{
public ObjectiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("O")).ToTable("TB_QUESTION");
}
}
为什么方法 1 有效而方法 2 无效?
编辑:
我删除了配置继承并且它 "almost" 有效(见下文)。像这样:
public class QuestionConfiguration : EntityTypeConfiguration<Question>
{
public QuestionConfiguration()
{
Property(p => p.Statement).HasColumnName("STATEMENT");
// Configures the TPH
Map<VisualQuestion>(p => p.Requires("TYPE").HasValue("Visual").HasMaxLength(10));
Map<ObjectiveQuestion>(p => p.Requires("TYPE").HasValue("Objective").HasMaxLength(10));
Map<DiscursiveQuestion>(p => p.Requires("TYPE").HasValue("Discursive").HasMaxLength(10));
ToTable("TB_QUESTION");
}
}
public class DiscursiveQuestionConfiguration : Configuration<DiscursiveQuestion>
{
public DiscursiveQuestionConfiguration()
{
}
}
public class VisualQuestionConfiguration : Configuration<VisualQuestion>
{
public VisualQuestionConfiguration()
{
}
}
public class ObjectiveQuestionConfiguration : Configuration<ObjectiveQuestion>
{
public ObjectiveQuestionConfiguration()
{
}
}
public class ObjectiveQuestionOptionConfiguration : Configuration<ObjectiveQuestionOption>
{
public ObjectiveQuestionOptionConfiguration()
{
HasRequired(p => p.Question).WithMany(p => p.Options).HasForeignKey(p => p.ObjectiveQuestionId);
Property(p => p.ObjectiveQuestionId).HasColumnName("ID_OBJECTIVE_QUESTION");
Property(p => p.Statement).HasColumnName("STATEMENT"); // <--- This doesnt get mapped! :(
ToTable("TB_OBJECTIVE_QUESTION_OPTION");
}
}
并像这样注册它们:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Approach 1: This works
//modelBuilder.Entity<Question>().Map<DiscursiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("D")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<ObjectiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("O")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<VisualQuestion>(p => p.Requires("TP_QUESTION").HasValue("V")).ToTable("TB_QUESTION");
// Approach 2: This does work too, however ObjectiveQuestionOption* does not inherit the statement column
modelBuilder.Configurations.Add(new QuestionConfiguration());
modelBuilder.Configurations.Add(new QuestionOptionConfiguration());
}
不同之处在于,第一种方法告诉 EF 将基础抽象 class Question
视为实体(modelBuilder.Entity<Question>()
调用),而第二种方法则不然。
您需要为 Question
创建并注册一个单独的配置。由于您将在那里配置所有通用属性,因此 QuestionConfiguration<T>
class 是多余的。
这是第二种方法的正确实现。
配置:
public class QuestionConfiguration : EntityTypeConfiguration<Question>
{
public QuestionConfiguration()
{
Property(p => p.Statement).HasColumnName("STATEMENT");
ToTable("TB_QUESTION");
}
}
public class DiscursiveQuestionConfiguration : EntityTypeConfiguration<DiscursiveQuestion>
{
public DiscursiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("D"));
}
}
public class VisualQuestionConfiguration : EntityTypeConfiguration<VisualQuestion>
{
public VisualQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("V"));
}
}
public class ObjectiveQuestionConfiguration : EntityTypeConfiguration<ObjectiveQuestion>
{
public ObjectiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("O"));
}
}
正在注册:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new QuestionConfiguration());
modelBuilder.Configurations.Add(new VisualQuestionConfiguration());
modelBuilder.Configurations.Add(new ObjectiveQuestionConfiguration());
modelBuilder.Configurations.Add(new DiscursiveQuestionConfiguration());
}
我正在为一个 Google 类似表单的项目建模。下面的实体非常简单明了(我猜),如下所示。
问题类型:
// Base class for any kind of question
public abstract class Question : Bean
{
public string Statement { get; set; }
}
// Visual questions are questions where images are answers.
public class VisualQuestion : Question
{
public virtual VisualAnswer Answer { get; set; }
}
// Discursive questions are questions where big texts are answers.
public class DiscursiveQuestion : Question
{
public virtual DiscursiveAnswer Answer { get; set; }
}
// Objective questions are questions that can have multiple answers,
// where each of them should be no bigger than 1 character.
public class ObjectiveQuestion : Question
{
public virtual List<ObjectiveQuestionOption> Options { get; set; }
}
// Options for objective questions.
public class ObjectiveQuestionOption : Question
{
public int ObjectiveQuestionId { get; set; }
public virtual ObjectiveQuestion Question { get; set; }
public virtual ObjectiveAnswer Answer { get; set; }
}
答案类型:
public abstract class Answer : Bean
{
public int QuestionId { get; set; }
}
public class DiscursiveAnswer : Answer
{
public string Answer { get; set; }
public virtual DiscursiveQuestion Question { get; set; }
}
public class ObjectiveAnswer : Answer
{
public char Answer { get; set; }
public virtual ObjectiveQuestion Question { get; set; }
}
public class VisualAnswer : Answer
{
public byte[] Blob { get; set; } // Image answer
public virtual VisualQuestion Question { get; set; }
}
Bean 所在位置:
public abstract class Bean
{
public int Id { get; set; }
}
对于问题,我可以有一个 Question
对象和一个 QuestionOption
用于 objective 问题。如果是这样,我们将需要 Question
中的所有 3 个 Answer
对象,这对我来说听起来不对(需要识别问题类型,然后相应地访问其答案成员,例如 is
和 as
转换)。作为解决方法,我决定使用 TPH 方法将问题拆分为上面定义的 3 个对象,并拥有单独的 Answer 成员。
一切似乎只在 1 个条件下工作:所有流畅的 API 设置必须在 DbContext
class 的 void OnModelCreating(DbModelBuilder modelBuilder)
内完成(我已经覆盖了它).这是一个问题,因为我正在为每个实体对象分离我的所有配置并像这样添加它们:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Approach 1: This works
//modelBuilder.Entity<Question>().Map<DiscursiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("D")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<ObjectiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("O")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<VisualQuestion>(p => p.Requires("TP_QUESTION").HasValue("V")).ToTable("TB_QUESTION");
// Approach 2: This does not work: it complains that 2 of the 3 entities cant share the TB_QUESTION table because "they are not in the same type hierarchy
// or do not have a valid one to one foreign key relationship with matching primary keys between them" (???).
modelBuilder.Configurations.Add(new VisualQuestionConfiguration());
modelBuilder.Configurations.Add(new ObjectiveQuestionConfiguration());
modelBuilder.Configurations.Add(new DiscursiveQuestionConfiguration());
}
这些是配置对象:
public abstract class QuestionConfiguration<T> : EntityTypeConfiguration<T> where T : Question
{
public QuestionConfiguration()
{
Property(p => p.Statement).HasColumnName("STATEMENT");
}
}
public class DiscursiveQuestionConfiguration : QuestionConfiguration<DiscursiveQuestion>
{
public DiscursiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("D")).ToTable("TB_QUESTION");
}
}
public class VisualQuestionConfiguration : QuestionConfiguration<VisualQuestion>
{
public VisualQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("V")).ToTable("TB_QUESTION");
}
}
public class ObjectiveQuestionConfiguration : QuestionConfiguration<ObjectiveQuestion>
{
public ObjectiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("O")).ToTable("TB_QUESTION");
}
}
为什么方法 1 有效而方法 2 无效?
编辑:
我删除了配置继承并且它 "almost" 有效(见下文)。像这样:
public class QuestionConfiguration : EntityTypeConfiguration<Question>
{
public QuestionConfiguration()
{
Property(p => p.Statement).HasColumnName("STATEMENT");
// Configures the TPH
Map<VisualQuestion>(p => p.Requires("TYPE").HasValue("Visual").HasMaxLength(10));
Map<ObjectiveQuestion>(p => p.Requires("TYPE").HasValue("Objective").HasMaxLength(10));
Map<DiscursiveQuestion>(p => p.Requires("TYPE").HasValue("Discursive").HasMaxLength(10));
ToTable("TB_QUESTION");
}
}
public class DiscursiveQuestionConfiguration : Configuration<DiscursiveQuestion>
{
public DiscursiveQuestionConfiguration()
{
}
}
public class VisualQuestionConfiguration : Configuration<VisualQuestion>
{
public VisualQuestionConfiguration()
{
}
}
public class ObjectiveQuestionConfiguration : Configuration<ObjectiveQuestion>
{
public ObjectiveQuestionConfiguration()
{
}
}
public class ObjectiveQuestionOptionConfiguration : Configuration<ObjectiveQuestionOption>
{
public ObjectiveQuestionOptionConfiguration()
{
HasRequired(p => p.Question).WithMany(p => p.Options).HasForeignKey(p => p.ObjectiveQuestionId);
Property(p => p.ObjectiveQuestionId).HasColumnName("ID_OBJECTIVE_QUESTION");
Property(p => p.Statement).HasColumnName("STATEMENT"); // <--- This doesnt get mapped! :(
ToTable("TB_OBJECTIVE_QUESTION_OPTION");
}
}
并像这样注册它们:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Approach 1: This works
//modelBuilder.Entity<Question>().Map<DiscursiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("D")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<ObjectiveQuestion>(p => p.Requires("TP_QUESTION").HasValue("O")).ToTable("TB_QUESTION");
//modelBuilder.Entity<Question>().Map<VisualQuestion>(p => p.Requires("TP_QUESTION").HasValue("V")).ToTable("TB_QUESTION");
// Approach 2: This does work too, however ObjectiveQuestionOption* does not inherit the statement column
modelBuilder.Configurations.Add(new QuestionConfiguration());
modelBuilder.Configurations.Add(new QuestionOptionConfiguration());
}
不同之处在于,第一种方法告诉 EF 将基础抽象 class Question
视为实体(modelBuilder.Entity<Question>()
调用),而第二种方法则不然。
您需要为 Question
创建并注册一个单独的配置。由于您将在那里配置所有通用属性,因此 QuestionConfiguration<T>
class 是多余的。
这是第二种方法的正确实现。
配置:
public class QuestionConfiguration : EntityTypeConfiguration<Question>
{
public QuestionConfiguration()
{
Property(p => p.Statement).HasColumnName("STATEMENT");
ToTable("TB_QUESTION");
}
}
public class DiscursiveQuestionConfiguration : EntityTypeConfiguration<DiscursiveQuestion>
{
public DiscursiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("D"));
}
}
public class VisualQuestionConfiguration : EntityTypeConfiguration<VisualQuestion>
{
public VisualQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("V"));
}
}
public class ObjectiveQuestionConfiguration : EntityTypeConfiguration<ObjectiveQuestion>
{
public ObjectiveQuestionConfiguration()
{
Map(p => p.Requires("TP_QUESTION").HasValue("O"));
}
}
正在注册:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new QuestionConfiguration());
modelBuilder.Configurations.Add(new VisualQuestionConfiguration());
modelBuilder.Configurations.Add(new ObjectiveQuestionConfiguration());
modelBuilder.Configurations.Add(new DiscursiveQuestionConfiguration());
}