The Code Gorilla

Monday 18 March 2013

Entity framework, why the virtual on references?

I've been perplexed for a little while about the entity framework when using code first.

Consider the following:

A multiple choice database, one question with multiple answers:

public class Answer
{
    public int AnswerID { get; set; }
    public int QuestionID { get; set; }

    public string Text { get; set; }
    public bool IsCorrect { get; set; }

    public virtual Question Question { get; set; }
}

public class Question
{
    public int QuestionID {get;set;}
    public string Text { get; set; }

    public virtual ICollection<Answer> Answers { get; set; }
}

public class MultiChoiceContext : DbContext, IDataModel
{
    public DbSet<Question> Questions { get; set; }
}

The question that's always been in my mind is why the virtual on the external reference on Question (line 9) and ICollection (line 17), I've seen lots of examples on the internet that do and do not have the virtual keyword and until recently I've never understood why. So I'm here to enlighten you.


The virtual allows the Entity Framework to allow for lazy loading (see Loading Related Entities), simple. Which means it's not populated until it's used.

Now this leads to another dilemma, as you can see from the above I don't have a DbSet of Answers, and for good reason, in my use case I don't share answers between questions, questions own the answers. Now when I want to create a new question I also wish to add a new set of answers as per the following:

using( MultiChoiceContext ctx = new MultiChoiceContext() )
{
    Question qs = new Question() { Text = "What is your name?" };

    qs.Answers.Add( new Answer() { Text = "My name is Bill" } );
    qs.Answers.Add( new Answer() { Text = "My name is Robert" };

    ctx.Questions.Add( qs );
    ctx.SaveChanges();
}

All well and good, except an NullReferenceException will be thrown on line 5, as the collection is null. Its a shame the Entity Framework cannot take care of this for us, but then you have to remember that we created the question ourselves and until we add and save it it is not the responsibility of the entity framework.

To resolve this common problem its good practice to provide an implementation for all you collection in the case where its null, such as:

public class Question
{
    public int QuestionID {get;set;}
    public string Text { get; set; }

    private ICollection<Answer> _answers;
    public virtual ICollection<Answer> Answers
    {
        get
        {
            return _answers ?? (_answers = new HashSet<Answer>());
        }
        set
        {
            _answers = value;
        }
    }
}

To conform with the lazy loading, it's best to only create it if it doesn't exist when first accessed. This approach (as apposed to creating it on construction) does not get in the way of the lazy loading of the Entity Framework.


No comments:

Post a Comment