I´m using Entity Framework 5 and Code First.
I have two domain entities Question and Answer for a quiz application. One question has several possible answers. A question also has one correct answer which should reference one of the possible answers. I am experiencing some issues with the combination of a one-to-many and one-to-one relationship between the to entities. See Q1 and Q2.
This is the code for the entities:
public class Question
{
public virtual int Id { get; set; }
[Required]
public virtual string Text { get; set; }
[InverseProperty("Question")]
public virtual ICollection<Answer> PossibleAnswers { get; set; }
public virtual Answer CorrectAnswer { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public virtual DateTime? UpdateStamp { get; set; }
}
public class Answer
{
public virtual int Id { get; set; }
[Required]
public virtual string Text { get; set; }
[ForeignKey("QuestionId")]
public virtual Question Question { get; set; }
public virtual int QuestionId { get; set; }
}
Q1: What should I do to be able to insert the Question object and referenced Answers (thru property PossibleAnswers) in just one roundtrip to the db (eg one call to the contexts SaveChanges)? The error I get when I save the Questions and Answers without adding the Answers first is:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
To solve that problem I tried the following using fluent API to get the Answers to be added prior to Questions when doing it all with just one call the objectcontexts SaveChanges:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Question>()
.HasOptional(q => q.CorrectAnswer)
.WithRequired();
base.OnModelCreating(modelBuilder);
}
However, that lead me to another error:
Conflicting changes detected. This may happen when trying to insert multiple entities with the same key.
Am I on the right path with the fluent API approach for Q1? Why the error message?
Q2: When deleting a question I realize that there will be an error since the question cannot be deleted before the answers and vice versa. How do I solve this? For instance, is WillCascadeOnDelete supposed to be specified on both Question.CorrectAnswer and Question.PossibleAnswers?
For both your questions Q1 and Q2 you will need two roundtrips/two calls to SaveChanges
(aside from solving the problem with a Stored Procedure maybe).
Q1: The first call having Question.CorrectAnswer
set to null
and a second call that sets the CorrectAnswer
to one of the stored answers.
Q2: The first call setting Question.CorrectAnswer
to null
and the second call deletes the Question
and the related answers with enabled cascading delete.
If you are not so worried about the two roundtrips but more about the two transactions corresponding to the two SaveChanges
calls you can wrap the whole operation including the two SaveChanges
calls into a single manual transaction. (Example: EF: How do I call SaveChanges twice inside a transaction?)
About the one-to-one relationship: Although from business perspective the relationship for the CorrectAnswer
is one-to-one it is hard or even impossible to model it as a one-to-one relationship with EF.
The problem is that EF does not support foreign key one-to-one associations, i.e. relationships where the foreign key (CorrectAnswerId or so) has a unique constraint. It only supports shared primary key one-to-one associations where the primary key of the dependent (Question
) is the foreign key (for Question.CorrectAnswer
) to the principal (Answer
) at the same time. Your Fluent code is the configuration for such a shared primary key association. But it would mean that the only valid CorrectAnswer
is the Answer
that has the same primary key value as the Question
. While this is theoretically possible to achieve (your Answer
table has more records than the Question
table) it would most likely require to not use autogenerated keys but supply the keys manually. Changing the CorrectAnswers
from one Answer
to the other would be impossible. So, shared primary keys are not suited for your model in my opinion.
The better solution would be to remove your Fluent mapping. The result would be a one-to-many relationship with a nullable foreign key for CorrectAnswer
in the Question
table. From database perspective it means that the same Answer
can be the CorrectAnswer
for many Question
s which is probably nonsense in your business logic because every question has its own unique set of answers and two questions never share the same answer. But you can "hide" this one-to-many relationship from your business logic by just not adding an inverse collection property (like QuestionsThisIsTheCorrectAnswerFor
) to Answer
. Although it does not model the business constraint perfectly it works technically without problems.
More about the difficulties of one-to-one relationships with EF can be found in these blog posts:
- Shared Primary Key One-to-One Associations
- One-to-One Foreign Key Associations