EF Dependency Resolution
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.
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);
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.
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 class MyDependencyResolver : IDbDependencyResolver
public object GetService(Type type, object key)
if(type == typeof(IDbConnectionFactory))
return new MyConnectionFactory();
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
AddDependencyResolver(new SingletonDependencyResolver<IDbConnectionFactory><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.
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 is defined on IDbDependencyResolver to make it easy to implement this interface without lots of duplicate code. However, the most common way of calling the method 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)
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.
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.