EF Dependency Resolution

Introduction

EF has historically taken a rather ad-hoc approach to runtime configuration and extensibility. The changes for EF6 described here and in the post covering code-based configuration are intended to replace this ad-hoc approach with some building blocks that will provide a common mechanism for configuration. In doing so they also allow EF to be more easily extended by extracting out services that can then be resolved to different implementations at runtime.

This spec describes low level building blocks

The components described in this spec are low level building blocks that will only be used in advanced scenarios. Developers wishing to make simple configuration changes from code should use the DbConfiguration class described in the code-based configuration spec. DbConfiguration provides a simple façade over the components described here.

Goals

The changes described in these specs address the following goals:

  • Provide a common mechanism and building blocks whereby aspects of existing EF functionality can be factored out or new functionality added in such a way that different implementations can be injected without the core EF code knowing about the specifics of these implementations.
  • Provide a unified mechanism for EF code to access configuration regardless of whether that configuration has been set in code (“code-based”) or in the application’s config file.
  • Ensure that configuration can be discovered by design-time tools such that actions such as running the Code First pipeline can be correctly performed by tools.
  • Allow, but not require, EF dependencies to be injected using the application developer’s Inversion-of-Control (IoC) container of choice.

Service Locator pattern

The most fundamental underlying building block here is the use of the Service Locator pattern as represented by the IDbDependencyResolver interface:

public interface IDbDependencyResolver
{
    object GetService(Type type, object key);
    IEnumerable<object> GetServices(Type type, object key);
}

At the most basic level, when EF needs an implementation of some interface or base class it will call GetService passing in the interface or base class type and will get back the implementation to use. Likewise, GetServices is used to get all implementations of a given service type for a given key.

IDbDependencyResolver is similar to the IDependencyResolver interfaces of ASP.NET MVC and Web API.

The resolver chain

Internally, EF maintains a list of classes that implement this interface and uses the Chain-of-Responsibility pattern to resolve services it needs from this chain. That is, it calls GetService on the first IDbDependencyResolver in the list. If this implementation returns null, then it calls GetService on the next. This continues until either a non-null object is returned or the last resolver in the chain is reached. This last resolver is always the “root” resolver which will resolve any service needed by EF with a default implementation if none of the other resolvers in the chain resolve the service first.

In practical terms this means that if you use EF6 out of the box without doing anything then all services needed by EF are resolved by the root resolver to the EF defaults. If you want to change one or more of the defaults then you can add one or more new IDependencyResolver implementations to the chain. EF will then resolve the service using the resolver(s) you have added and will no longer use the defaults provided by the root resolver. For any services that your resolver(s) do not handle, the defaults provided by the root resolver will still be used.

For example, to change the default connection factory you can write a DbConfiguration class like so:

public class MyAppConfiguration : DbConfiguration
{
    public MyAppConfiguration()
    {
        AddDependencyResolver(new MyDependencyResolver());
    }
}

public class MyDependencyResolver : IDbDependencyResolver
{
    public object GetService(Type type, object key)
    {
        if(type == typeof(IDbConnectionFactory))
        {
            return new MyConnectionFactory();
        }

        return null;
    }

    public IEnumerable<object> GetServices(Type type, object key)
    {
        var service = GetService(type, key);
        return service == null ? Enumerable.Empty<object>() : new[] { service };
    }
}

More details about DbConfiguration can be found in the post on code-based configuration (including information about performing configuration without having to worry about resolvers).

We also provide a SingletonDependencyResolver implementation of IDbDependencyResolver that returns the given object when GetService is called for the provided generic type and otherwise returns null. This makes it easy to override a specific dependency without having to write your own resolver:

public class MyAppConfiguration : DbConfiguration
{
    public MyAppConfiguration()
    {
        AddDependencyResolver(new SingletonDependencyResolver<IDbConnectionFactory>(new MyConnectionFactory()));
    }
}

What is the key?

This key argument is optionally used to provide information to the resolver about the requested service. For example, the provider invariant name string  is passed as a key when when GetService is used to request an EF provider. A GetService implementation must then return the provider for the given invariant name.

The type and semantics of the key object, if any, is documented for each service that may be requested.

GetServices

In most situations EF just needs one implementation of a service and therefore calls GetService as described above. However, sometimes EF needs all implementations of a service for a given key. For example, on startup EF will ask for all IDbInterceptor implementations so that it can then dispatch to all registered interceptors. In this situation EF will call GetServices instead of GetService. Each resolver should return a enumeration containing all services of the given type and for the given key that it can provide. If it can’t provide any then it should return an empty enumeration. This is shown in the code example above.

Note that GetServices should not return all services for all keys. It should still only return services for the given key.

Default resolvers

Sometimes it may be desirable to configure a service that will be used only if no other service for the given type and key is resolved. For example, an EF provider may want to configure a default connection factory to be used only if the application has not configured the use of a different connection factory. This can be done using the AddDefaultResolver method instead of the normal AddDependencyResolver method. AddDefaultResolver places the given resolver below other resolvers in the chain-of-responsibility and hence will only be called if some other resolver has not already resolved the service.

Isn’t Service Locator an anti-pattern?

Some people consider Service Locator to be an anti-pattern. This is for two main reasons:

  • It can lead to runtime errors if the locator does not know about and hence does not resolve a service, and it is often hard find out which services a locator must resolve.
  • No context is provided for the code attempting to resolve the service.

The first issue is addressed in the EF design through the resolver chain and the associated root resolver which is designed to resolve any dependencies not handled by other resolvers. This means that so long as the EF team updates the root resolver whenever a new service is added then it doesn’t matter that other resolver implementations don’t know about or handle the new service. Put another way, when you implement IDbDependencyResolver you don’t have to worry about resolving everything, you just resolve the services you are interested in.

The second issue is addressed in two ways. First, the key object provides context about the service being resolved. (The word “context” is used here in its general sense; it does not necessary refer to a DbContext/ObjectContext.) Also, in the design for scoping (see below) the key provides context information about the scope of the resolver being created.

How do I know what services can resolved?

The public services that can be resolved by an IDbDependencyResolver are documented on MSDN.

Generic extension methods

Only one GetService method and one GetServices method are defined on IDbDependencyResolver to make it easy to implement this interface without lots of duplicate code. However, the most common way of calling one of the methods is to use one of the extension methods:

public static T GetService<T>(this IDbDependencyResolver resolver, object key)
public static T GetService<T>(this IDbDependencyResolver resolver)
public static object GetService(this IDbDependencyResolver resolver, Type type)

public static IEnumerable<T> GetServices<T>(this IDbDependencyResolver resolver, object key)
public static IEnumerable<T> GetServices<T>(this IDbDependencyResolver resolver)
public static IEnumerable<object> GetServices(this IDbDependencyResolver resolver, Type type)

For example, this:

var provider = resolver.GetService<DbProviderServices>("System.Data.SqlClient");

Is equivalent to this:

var provider = (DbProviderServices)resolver.GetService(typeof(DbProviderServices), "System.Data.SqlClient");

What about scopes?

Imagine a situation where a DbContext instance depends on a service object that must only live while the context exists and must be disposed when the context is disposed. We currently don’t have an services like this, but it is conceivable that we will in the future. We have therefore designed and prototyped a way to handle this and it is described in the EF design meeting notes for September 27, 2012.

Extensibility and open source contributions

Let’s say you want to add to or change EF behavior in some way. With the open source model you could submit a pull request, and in a lot of cases we are likely to accept it. However, there may be cases where the change is not appropriate for the core EF code for a number of reasons:

  • It might break existing applications
  • It might be an uncommon scenario that is useful to you but not the majority
  • Your implementation might work for you but not be general or robust enough to become a supported part of the product

In such situations you may still be able to make the change by creating an interface that is resolved using the mechanisms described above. We are very likely to accept such a changes since it is generally useful to increase the extensibility of the code in this way. With such a change in EF you will then be able to add the functionality you need without further changes to the EF code. If you choose you can still make your functionality available to others so that they can benefit from it.

Related information

The main entry point for EF dependency resolution is the DbConfiguration class which is described in a separate post on code-based configuration. We intend to also publish a post describing how to use IDbDependencyResolver with an off-the-shelf IoC container.

Last edited Aug 2, 2013 at 6:24 PM by ajcvickers, version 6

Comments

araujofilip Mar 28 at 3:08 AM 
is there any way to map an interface? or use EF Dependency Resolution to resolve them?

mminke Jan 25 at 9:21 PM 
Any word on the post describing how to use IDbDependencyResolver with an off-the-shelf IoC container. Maybe Unity could be the container you choose.

maciejw Dec 19, 2012 at 10:02 PM 
I hate service locator because of lack of release method. Service Locator is not an anti pattern it is just LAME to me! for now you work with singletons witch saves your sorry asses:) but when you go deeper into a rabbit hole and you introduce other lifetimes ... you will regret you used it, developers will regret it too.
Why? because developers hook it up with containers e.g. Castle Windsor manages components implementing IDisposable, it knows when to call Dispose, when you can't release a component you have a memory leak, because container has a reference also to newly created component and it thinks, since you did not released it, you are still using it, you also cant call dispose, because you did not created a component, because one witch created something should dispose it after had finished using it -- a container.

since you can very easily misuse this pattern, it is regarded in a community as an anti pattern.

ajcvickers Oct 2, 2012 at 10:52 PM 
@WouterDerKort Extensibility in the query pipeline is on our list but it's not currently high enough priority for anybody to be actively working on it.

ajcvickers Oct 2, 2012 at 10:51 PM 
@CraigStuntz Thanks for the feedback. With the current design context can be provided to the locator in two ways. First, the key parameter in the GetService call provides context for the specific service being requested. Second, the design we have for scoped services (see design meeting notes as indicated above) allows for a scope with context to be created where it makes sense. However, we currently don't have any services that are not at the global scope and so we don't use this. The reason we only have services at the global scope is because we are retrofitting this into an existing product and the services we now resolve are all historically global. We would defiantly consider scoping (and providing context for) any service for which this makes sense.

With regard to being different from MVC and Web API, we did look closely at both of these and borrowed some concepts, but what they had didn't work for us directly so we necessarily had to make changes. There's always a line between being consistent and deviating, and in this case we had to deviate.

CraigStuntz Oct 2, 2012 at 1:02 PM 
The good: That EF is using DI at all. Good change!
The bad: Using the Service Locator anti-pattern, with no indication of the context in which services are initiated. See Mark Seeman's excellent book Dependency Injection in .NET for how to do this right.
The ugly: MVC, ASP.NET Web API, and EF all have completely different methods of DI.

WouterDeKort Oct 2, 2012 at 7:11 AM 
Great to see that EF is improving in this area. I always love all the extensibility points that Mvc and WebApi offer and it would be great if EF moved to a similar level.

Are you also going to make the query pipeline extensible? It would be nice if we could add our own Expression tree parser. We could then add custom commands or adjust the generated SQL for specific scenarios.

Nice work! Looking forward to the results.