Entity Framework - new() vs DbSet.Create()

This is one of those "I had to explain this couple times already so next time I want something I can redirect people to" kind of post.

What I want to write about is difference in behavior between using new() and DbSet.Create() for instantiating new entities. In order to do this I've created a very simple model and context.

public class Planet
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    ...

    public virtual ICollection Natives { get; set; }
}

public class Character
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    ...

    public virtual int HomeworldId { get; set; }

    public virtual Planet Homeworld { get; set; }
}

public interface IStarWarsContext
{
    DbSet Planets { get; set; }

    DbSet Characters { get; set; }

    int SaveChanges();
}

public class StarWarsContext : DbContext, IStarWarsContext
{
    public DbSet Planets { get; set; }

    public DbSet Characters { get; set; }
}

I've also created a very simple view which lists Charactes already present in database and allows for adding new ones.

@using (Html.BeginForm())
{
    <fieldset>
        <legend>New StarWars Character</legend>
        <div>
            @Html.LabelFor(m => m.Name)
        </div>
        <div>
            @Html.TextBoxFor(m => m.Name)
        </div>
        <div>
            @Html.LabelFor(m => m.Homeworld)
        </div>
        <div>
            @Html.DropDownListFor(m => m.Homeworld, Model.Planets)
        </div>
        ...
        <div>
            <input type="submit" value="Add" />
        </div>
    </fieldset>
}
<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Homeworld</th>
            ...
        </tr>
    </thead>
    <tbody>
        @foreach (Character character in Model.Characters)
        {
            <tr>
                <td>@character.Name</td>
                <td>@character.Homeworld.Name</td>
                ...
            </tr>
        }
    </tbody>
</table>

The view is powered by following ViewModel and controller.

public class StarWarsViewModel
{
    public string Name { get; set; }

    ...

    public int Homeworld { get; set; }

    public IEnumerable Planets { get; set; }

    public IReadOnlyList Characters { get; set; }
}

public class StarWarsController : Controller
{
    private IStarWarsContext _startWarsContext;

    public StarWarsController(IStarWarsContext startWarsContext)
    {
        _startWarsContext = startWarsContext;
    }

    [HttpGet]
    public ActionResult Index()
    {
        return View(GetViewModel());
    }

    [HttpPost]
    public ActionResult Index(StarWarsViewModel viewModel)
    {
        AddCharacter(viewModel);

        return View(GetViewModel());
    }

    private StarWarsViewModel GetViewModel()
    {
        return new StarWarsViewModel
        {
            Planets = _startWarsContext.Planets
                .Select(p => new { p.Id, p.Name })
                .ToList()
                .Select(p => new SelectListItem { Value = p.Id.ToString(), Text = p.Name }),
            Characters = _startWarsContext.Characters.ToList()
        };
    }

    private void AddCharacter(StarWarsViewModel viewModel)
    {
        throw new NotImplementedException();
    }
}

The AddCharacter method is the point of interest here. There are two ways to implement it and they will result in a different behavior.

Creating entity with new()

Following first Entity Framework tutorial which pops up on Google will result in code similar to the one below.

private void AddCharacter(StarWarsViewModel viewModel)
{
    Character character = new Character();
    character.Name = viewModel.Name;
    ...
    character.HomeworldId = viewModel.Homeworld;

    _startWarsContext.Characters.Add(character);
    _startWarsContext.SaveChanges();
}

Running this code and adding a new Character will result in NullReferenceException coming from the part of view which generates the table (to be more exact from @character.Homeworld.Name). The reason for the exception is the fact that Entity Framework needs to lazy load the Planet entity but the just added Character entity is not a dynamic proxy so lazy loading doesn't work for it. Only Entity Framework can create a dynamic proxy, but in this scenario there is no way for it to do it - the caller already owns the reference to the entity and it cannot be changed to a different class.

Creating entity with DbSet.Create()

In order to be able create new entities as proper dynamic proxies the DbSet class provides Create method. This method returns new dynamic proxy instance which isn't added or attached to the context. To use it only a single line of code needs to be changed.

private void AddCharacter(StarWarsViewModel viewModel)
{
    Character character = _startWarsContext.Characters.Create();
    character.Name = viewModel.Name;
    ...
    character.HomeworldId = viewModel.Homeworld;

    _startWarsContext.Characters.Add(character);
    _startWarsContext.SaveChanges();
}

After this simple change the code works as expected, related entities are being lazy loaded when needed.

The takeaway

The sample above is built in a way which highlights the difference between new() and DbSet.Create() (it even has an N+1 selects hiding in there for the sake of simplicity). In real life this rarely causes an issue as there is couple other things which can impact the behavior (related entity already present in context or usage of Include() method). But when this causes an issue it's always unexpected and I've seen smart people wrapping they heads around what is going on. It is important to understand the difference and use both mechanisms appropriately (sometimes lack of lazy loading maybe desired).