How-to inject the Entity Framework DbContext into the ConfigurationBasedRepository of SharpRepository
Asked Answered
S

2

20

I really would like to use SharpRepository together with Ninject, but I do not understand how to configure Ninject to share the Entity Framework DbContext between the repositories.

I am using Entity Framework version 5 and Ninject version 3.

Currently I am using Ef5Repository in my source code, but I want to replace it with ConfigurationBasedRepository. But I cannot figure out how to pass (or inject) the EF DbContext to the repositories.

Example (current state):

using SharpRepository.Repository;

public interface IProductRepository : IRepository<Product>
{
}

using SharpRepository.Ef5Repository;
using System.Data.Entity;

// TODO Tightly coupled to Ef5Repository.
public class ProductRepository : Ef5Repository<Product>, IProductRepository
{
    // TODO The DbContext has to be injected manually.
    public ProductRepository(DbContext context) : base(context)
    {
    }

    // [...]
}

Goal:

using SharpRepository.Repository;

public interface IProductRepository : IRepository<Product>
{
}

public class ProductRepository : ConfigurationBasedRepository<Product, int>, IProductRepository
{
    // [...]
}

I've already read the two blog posts SharpRepository: Getting Started and SharpRepository: Configuration, but they both do not help me, since:

  1. The used DIC is StructureMap, not Ninject.
  2. The source code examples are incomplete (e.g. usage of not declared variables).

So my question: Can someone provide me with some source code example how-to to achieve the goal described above (sharing one Entity Framework DbContext instance between all repositories extending ConfigurationBasedRepository)?

Siegel answered 23/4, 2013 at 14:48 Comment(0)
S
16

First, you will need to install the SharpRepository.Ioc.Ninject NuGet package. There are extension methods in here for hooking up Ninject to handle the loading a generic repository and setting the dependency resolver that SharpRepository uses.

Where ever you are setting up your Ninject binding rules (all the calls to kernel.Bind<>), you will need to add:

kernel.BindSharpRepository();

Next, in your Global.asax, or App_Start code, or your Bootstrapper logic (where ever you are calling application startup code) you will need to add the following:

// kernel is the specific kernel that you are setting up all the binding for
RepositoryDependencyResolver.SetDependencyResolver(new NinjectDependencyResolver(kernel));

This will tell SharpRepository to use this Ninject Kernel when getting a new DbContext.

The last thing to do is to setup the rules for binding for the DbContext itself. If you are in a web application you will most likely want the scope of the DbContext to be per request. I personally don't use Ninject but I found this reference for using InRequestScope. I believe your code would look something like this:

kernel.Bind<DbContext>().To<MyCustomEfContext>().InRequestScope().WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["MyCustomEfContext"].ConnectionString);

Most people won't need this next piece but if you have custom logic in your CustomEfContext (I have an override for logging on calls to SaveChanges() for example), then you'll need to define your custom context type in the configuration file like so:

<repositories>
  <repository name="ef5Repository" connectionString="CustomEfContext" cachingStrategy="standardCachingStrategy" dbContextType="My.Data.CustomEfContext, My.Data" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository" />
</repositories>

Where dbContextType defines the type of the custom DbContext you are using using the full type, namespace syntax. If you do this then you'll need to set Ninject to Bind on the custom context by changing .Bind<DbContext>() to .Bind<CustomEfContext>(). But like I said normally you can use DbContext directly without an issue.

Sleight answered 23/4, 2013 at 16:15 Comment(2)
TY very much, Jeff. The correct syntax for binding the EF DbContext is: kernel.Bind<DbContext>().To<EfContext>().InRequestScope().WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["EfContext"].ConnectionString);Siegel
no problem, glad you got it up and running, I'll edit my answer to fix the syntax you mentioned for future reference.Sleight
S
6

First of all, the solution provided in the answer by Jeff T works!

I will conclude the steps I took to make Ninject work in a ASP.NET MVC 4 + EF 5 project. It is important to mention that the Specific Repository pattern is implemented via SharpRepository in the following example.


Required software

  1. Install Ninject and "Ninject.MVC3" (which also installs "Ninject.Web.Common") via NuGet.
  2. Install SharpRepository, "SharpRepository for EF5" and "SharpRepository with Ninject IOC" via NuGet.

Define the Repository layer

  1. Create a DbContext derived class, e.g. Domain.EfContext. It is the

    "recommended way to work with context".

    • Declare all required DbSet<T> as public properties, e.g. public DbSet<Product> Products { get; set; }
    • Declare the following two constructors in the class Domain.EfContext:

      public EfContext() : base() {}
      public EfContext(string connectionName) : base(connectionName) {}
      

  2. Define an interface for the Specific Repository, e.g.:

    // TODO By extending IRepository, the interface implements default Create-Read-Update-Delete (CRUD) logic.
    // We can use "traits" to make the repository more "specific", e.g. via extending "ICanInsert".
    // https://github.com/SharpRepository/SharpRepository/blob/master/SharpRepository.Samples/HowToUseTraits.cs
    public interface IProjectRepository : IRepository<Project>
    {
        // TODO Add domain specific logic here.
    }
    
  3. Define a class which is implementing the Specific Repository and inherits from SharpRepository.Repository.ConfigurationBasedRepository<T, TKey>, e.g.:

    public class ProductRepository : ConfigurationBasedRepository<Product, int>, IProductRepository
    {
        // TODO Implement domain specific logic here.
    }
    

Define the Consumer layer

  1. Create a Controller, e.g. Controllers.ProductController.

    public class ProductController : Controller
    {
        private IProductRepository Repository { get; private set; }
    
        // TODO Will be used by the DiC.
        public ProductController(IProductRepository repository)
        {
            this.Repository = repository;
        }
    }
    

Set up Dependency Injection (DI) via the Dependency Injection Container (DiC) Ninject

The file App_Start/NinjectWebCommon.cs is automatically created by Ninject.Web.Common and we can load our modules and register our services in the method RegisterServices(IKernel kernel) : void of the class NinjectWebCommon. Here is the complete source code of that method for the example:

    private static void RegisterServices(IKernel kernel)
    {
        kernel.BindSharpRepository();
        RepositoryDependencyResolver.SetDependencyResolver(
            new NinjectDependencyResolver(kernel)
        );

        string connectionString = ConfigurationManager.ConnectionStrings["EfContext"].ConnectionString;
        kernel.Bind<DbContext>()
            .To<EfContext>()
            .InRequestScope()
            .WithConstructorArgument("connectionString", connectionString);

        kernel.Bind<IProductRepository>().To<ProductRepository>();
    }

Define the following sharpRepository section in the Web.config:

    <sharpRepository>
        <repositories default="ef5Repository">
            <repository name="ef5Repository"
                connectionString="EfContext"
                cachingStrategy="standardCachingStrategy"
                dbContextType="Domain.EfContext, Domain"
                factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository"
            />
        </repositories>
    </sharpRepository>

In addition, the connectionStrings section to make the example complete (I am using SQL Server LocalDB).

    <connectionStrings>
        <add name="EfContext" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=Domain;Integrated Security=True" />
    </connectionStrings>

I hope that this conclusion helps other people to get ASP.NET MVC 4 together with Entity Framework 5 and SharpRepository up and running!

Please leave me a reply if I took one or more unnecessary steps or if you see possibilities to improve the architecture described in the example.

Btw, I had to add the dbContextType attribute to the repository section to make it work (in contrast to the answer of Jeff T).


EDIT (2013-08-28): Striked out unnecessary steps (not required with the latest version of SharpRepository).

Siegel answered 25/4, 2013 at 9:41 Comment(3)
How would U approach Batching using this approach. MyProductRepository can see the BeginBatch Method but cannot see the Commit() method.Progestational
Great write up. There was a bug in previous versions of SharpRepository that made it so the DbContext wasn't shared properly among repositories and made it so you needed to define the dbContextType. That was fixed as of 1.3.6.10. Now you don't need to define the type if you have the RepositoryDependencyResolver set and in your Ioc mappings you map from DbContext to your specific context (EfContext in your example). Thanks for putting this detailed writeup together.Sleight
You do realize that defining multiple constructors is an example of the bastard injection anti-pattern? Or am I missing something?Wicklund

© 2022 - 2024 — McMap. All rights reserved.