Repositories Aren't So Bad

Rob Conery has apparently been messing with .NET data access methods and doesn’t much care for the Repository pattern.

I agree with the assertion that the repository, as he presented it, is a bad idea. However, rather than using CQRS or putting a DbContext directly on MVC controllers, I think repositories can be done correctly.

I made a repository on GitHub that demonstrates three different data access patterns.

Using the EF context from the presentation/service layer

The first pattern (labeled “EFContext”) uses the DbContext directly in the service layer.

This is not a bad pattern at all, but requires the presentation layer to reference EntityFramework.dll. In a solution that puts the data access layer in a different assembly than the presentation layer, this seems wrong to me.

Creating a “service repository”

I don’t know if this pattern has a name, but one pattern I’ve seen is hiding away the DbContext behind a so-called “repository”. This is demonstrated in the “ServiceRepository” projects.

The problem is that business logic tends to get shoved down into the data access layer:

StudentService wants to update a collection of students. It can call the Update(student) method on the repository:

public void PromoteAllClasses()
{
    var eligibleStudents =
        this.repository.GetAllStudents()
        .Where(s => !s.IsGraduated)
        .ToList();   // We must .ToList() it so we don't barf
                     // when Update() calls SaveChanges() in the
                     // middle of iterating

    // Note that this isn't an atomic operation
    foreach (var student in eligibleStudents)
    {
        // Here's some business logic
        student.YearsCompleted += 1;
        if (student.YearsCompleted >= 4)
            student.IsGraduated = true;

        this.repository.Update(student);
    }
}

But with this pattern, each student update is its own transaction, and if the graduation fails halfway through, then half your students have graduated and half haven’t. Oops.

If you instead move the “graudate all students” logic into the repository layer, it can be an atomic operation:

    //// StudentService.cs

    public void PromoteAllClassesAtomic()
    {
        this.repository.PromoteAllClassesAtomic();
    }


    //// StudentServiceRepository.cs

    public void PromoteAllClassesAtomic()
    {
        var eligibleStudents = this.context.Students.Where(s => !s.IsGraduated);

        foreach (var student in eligibleStudents)
        {
            // Business logic in the repository?
            student.YearsCompleted += 1;
            if (student.YearsCompleted >= 4)
                student.IsGraduated = true;
        }

        this.context.SaveChanges();
    }

But, now your data access layer contains both data access logic and business logic. We’re violating the Single Responsibility Principle. Why does the service layer at this point exist, if all it does is ferry method calls to the repository?

I don’t know if this style of repository has a better name, but I don’t think it really represents a repository. It contains business logic, but it also contains data access. It’s a service, but also a repository. I wonder if the name “Suppository” will stick.

IUnitOfWork

This is the pattern I prefer to use. It keeps business logic in the service layer but avoids referencing EF everywhere.

Our StudentRepository gains a new interface:

public interface IStudentUnitOfWork
{
    IRepository<Student> Students { get; }
    int SaveChanges();
}

internal class StudentContext : DbContext, IStudentUnitOfWork
{
    public IRepository<Student> Students { get; private set; }

    public StudentContext()
    {
        this.Students = new Repository<Student>(this);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Map our entities by hand since we have no IDbSet<TEntity> properties
        modelBuilder.Entity<Student>().ToTable("Students");
    }
}

The SaveChanges() method is implemented by DbContext, and we implement the IRepository interface with a Repository object.

Unlike the Service Repository, this Repository has no business logic in it. It exists only because we can’t create a DbSet<T> ourselves (the constructor is internal).

This is my preferred pattern because:

Improving our builds with Psake and Rake

I believe these are good things:

For these reasons, our team has been using Continua CI to build our Windows projects and TeamCity for our iOS projects. I have more experience with Continua so that’s what I’ll discuss here, but the same ideas apply to TeamCity.

Continua CI lets you design a build process with a web-based tool that has first-class support for running MSBuild scripts, creating NuGet packages, running various testing tools, or just executing programs and scripts.

Until now, our build process has been written in this tool. This gave us a graphical way of editing the build process without mucking around in a batch file or other scripting language. Continua was reponsible for remembering how to build and deploy our projects, and we could deploy anything by signing into Continua and starting a build.

Unfortunately, we’ve realized this comes with several problems:

To fix this, we are putting the build logic in a Psake build script. Psake is a build automatation tool written in PowerShell. You define the steps of your build as “tasks”, then choose which task you want to execute.

## default.ps1 (the build script)

Task Build {
    echo "Building the solution"
    & msbuild $solutionFile
}

Task Deploy {
    echo "Deploying to the devepment environment"
}

Then invoke the build script with the Invoke-psake function:

PS C:\> Invoke-psake Build

---------------[Build]---------------
Building the solution
... msbuild output ...

---------------[Deploy]---------------
Deploying to the development environment
... output ...

Build Succeeded!

--------------------------------------
Build Time Report
--------------------------------------
Name            Duration
----            --------
Build           00:00:09.4624557
Deploy          00:00:12.2191711
Total:          00:00:21.6816268

You can do more than just execute commands from Psake. The build script is just PowerShell, so anything you can do from PowerShell can be done during a build. This is great for us because we make heavy use of Microsoft’s development ecosystem, and all of Microsoft’s System Center products and Windows Azure have PowerShell modules to do almost anything we could want, including creating VMs and deploying software do them.

Our build script in Continua has been reduced from this:

to this:

Continua is still invaluable, but as the tool to automatically kick off our builds whenever we push changes and notify us when something bad happens. It no longer knows how to build the projects, only how to call Psake.

For our iOS projects, we’re using Rake, which is equally awesome, but build scripts are written in Ruby instead of PowerShell. Rakefiles have the same basic structure as Psake build files:

task :build do
    # Build with Xcode
    sh "xcodebuild ..."
end

task :testflight => [:build] do
    # Upload to TestFlight
end

and invoked with rake testflight.

Global settings files in WiX

Global settings files have several requirements:

  1. On install, create the file if it does not exist.

  2. Configure certain settings during installation/upgrade, and leave all other settings alone.

  3. Never delete the settings file during installation, upgrade, or uninstallation.

With these requirements, we have the following fragment:

<Component Id="settings.config" NeverOverwrite="yes" Permanent="yes">
    <File Source="path/to/default/settings.config" KeyPath="yes" />
</Component>

<Component Id="settings.config.Configure" KeyPath="yes" Guid="...">
    <util:XmlConfig File="[#settings.config]" .... Value="[PROPERTY1]" />
    <util:XmlConfig File="[#settings.config]" .... Value="[PROPERTY2]" />
</Component>

Here’s how this meets all the requirements:

  1. The settings.config component creates the file if the component isn’t installed when we start the installation.

  2. During installation and upgrade, the second component will modify the existing settings.config that’s on disk. The setting Component/@NeverOverwrite prevents us from overwriting the existing file on upgrade.

  3. During uninstallation, the file is left in place with Component/@Permanent. This attribute also prevents the file from being removed even if MajorUpgrade/@Schedule='afterInstallValidate'.

  4. The XmlConfig actions are in a separate component so they will run on upgrades even though settings.config isn’t being updated.

Today's Progress

I was happy with:

I wasn’t satisfied with:

Hello, World

Perhaps in the future I will make use of this as a blog.

But, for now, it’s just a little placeholder for my domain.