Mapping but not exposing ICollections in Entity Framework

Jimmy Bogard wrote a post a couple of days ago sizing up EF 6 for fitness in use for a fully encapsulated domain model.

One of the points he brought up was the lack of ability to encapsulate collections. After I wrote a comment alluding to a way that my team and I use to (kind of) work around this, his asked for more information which kicked me into gear to write this post.

The problem

If we're writing truly encapsulated domain models we should be creating classes like so:

public class Parent  
{
    private readonly ICollection<Child> _children;

    public IEnumerable<Child> Children
    {
        get
        {
            return _children.Skip(0);
        }
    }

    public void AddChild(Child child)
    {
        // some domain logic to ensure internal consistency
        _children.Add(child);
    }

    // some other methods that mutate state
}

As an aside, the .Skip(0) is a small hack just so that the client isn't as easily able to cast back to an ICollection and shoot themselves in the foot.

The problem with this is that in order for Entity Framework to map this collection, it has to be able to get to it. If our context is in the same assembly as our models, we can make the collection internal but I often try to abstract my db and infrastructure code out to its own assembly. As it currently stands (in this instance), EF is only able to map to public Properties, meaning our class ends up being (at best):

public class Parent  
{
    public ICollection<Child> Children { get; private set; }

    // etc etc
}

You may think this solves the problem because we can't set Children ourselves. This is incorrect, in our client code we're still able to (even accidentally):

    Parent parent; 
    Child child;
    // some kind of assignment here
    p.Children.Add(child);

Oops! We've just walked around our own carefully designed domain methods.

The solution

In order to let Entity Framework map our private member, we need to expose something that our DbContext can map to. So from our previous example, if we were to refactor our entity class to make the ICollection a private property instead of a read only field:

    private virtual ICollection<Child> ChildrenStorage { get; set; }

Then we add a static accessor like so:

    public static Expression<Func<Parent, ICollection<Child>>> ChildrenAccessor = f => f.ChildrenStorage;

Then in your EntityTypeConfiguration (or at least in your OnModelCreating within your context [which is what I'm doing here]) we map the relationship like so:

    modelBuilder.Entity<Parent>()
        .HasMany(Parent.ChildrenAccessor)
        .WithRequired();

This allows Entity Framework to map the collection, whithout exposing the collection directly to any clients. The client is still able to access the collection and work around any of your methods, but only if they go out of their way to do it. In other words you can't completely protect the user (or yourself) from doing the wrong thing, but you can make it so you don't do it accidentally.

Putting it all together

public class Parent  
{
    private virtual ICollection<Child> ChildrenStorage { get; set; }

    // accessor for ef to be able to map what we're after
      public static Expression<Func<Parent, ICollection<Child>>> ChildrenAccessor = f => f.ChildrenStorage;

    public IEnumerable<Child> Children
    {
        get
        {
            return ChildrenStorage.Skip(0);
        }
    }

    public void AddChild(Child child)
    {
        // some domain logic to ensure internal consistency
        ChildrenStorage.Add(child);
    }

    // some other methods that mutate state
}

public class Child  
{
    // wonderful domain driven stuff
}

public class ExampleContext:DbContext  
{
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Parent>()
            .HasMany(Parent.ChildrenAccessor)
            .WithRequired();
    }
}

As I was saying earlier this isn't a foolproof way around the problem, but it does allow us to protect ourselves (and anyone writing client code for our models) from a simple error.

Git bash aliases

I like living in git bash on windows, however I also sometimes really need to jump into visual studio or sublime, so below are the two commands I run in git bash to add the shortcuts into bash.

visual studio

echo "alias vs=\"/c/Program\ Files\ \(x86\)/Microsoft\ Visual\ Studio\ 11.0/Common7/IDE/devenv.exe\"" >> ~/.bashrc  

N.B. you may need to alter your path if you're running x86

sublime text 2

echo "alias subl=\"/c/Program\ Files/Sublime\ Text\ 2/sublime_text.exe\"" >> ~/.bashrc  

Then I can just use

vs solution.sln &  

or

subl file.md &  

CS0135: 'Model' conflicts with the declaration 'System.Web.Mvc.WebViewPage.Model'

After adding a declaration to pipe in some text from our model in a view in one of our MVC apps:

@Model.Project

I was hit with the above error message:

CS0135: 'Model' conflicts with the declaration 'System.Web.Mvc.WebViewPage<TModel>.Model'  

it turns out that the person that had originally authored the view had used the upper case Model in some HtmlHelper calls like so:

@Html.HiddenFor(Model => Model.Project)

A quick change to using either the lower case variant of model (or even a completely different identifier) for the parameter of the lambda fixed this up.

Hopefully this helps someone else when they come across it.

Unable to generate an explicit migration/Unable to update database

While working on a small feature for something at work today I needed to add a field in to one of our domain entities. It really was a tiny change:

public DateTime? LastUpdated { get; private set; }  

After continuing on to add unit tests etc I tried to add the migration:

Add-Migration EnabledProjectLastUpdated  

This threw a big red error in my face:

Unable to generate an explicit migration because the following explicit migrations are pending: [201311010453495_InitialProduction]. Apply the pending explicit migrations before attempting to generate a new explicit migration.  

So I did the logical thing and tried to re build my database locally:

Update-Database  

To be greeted by another error:

Unable to update database to match the current model because there are pending changes and automatic migration is disabled. Either write the pending model changes to a code-based migration or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration.  
You can use the Add-Migration command to write the pending model changes to a code-based migration.  

After going around in circles dropping my localDB, re-attemtping the update and re-attempting the add for rather longer than I'd like to admit I just tried a simple

Add-Migration  

For some reason this enabled me to add the Migration when I specified the name and update-database as if nothing had happened. I thought I'd write it down to help anyone else (or myself) if they ran into this.