EF Core 1.1
Creating DbContext instances
This post describes the different ways to create and configure instances of DbContext in EF Core 1.1. This includes:
- Calling a constructor directly and overriding OnConfiguring
- Passing DbContextOptions to the constructor
- Using Dependency Injection (D.I.) to create instances
Explicitly creating DbContext instances
There is no requirement to use EF with Dependency Injection (D.I.). New instances can be created with new
in the normal C# way.
Constructor with OnConfiguring
The simplest way to create a context instance is to create a class derived from DbContext and call its parameterless constructor. Don't forget that context instances should always be disposed, so often the instance is creating within a using statement. For example:
using (var context = new MyContext())
{
}
Configuration of the context instance is done by overriding OnConfiguring. For example:
public class MyContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFTest;Trusted_Connection=True;");
}
Note that OnConfiguring is called every time a new context instance is initialized; this is different from OnModelCreating which is usually only called once to build the model. This means that OnConfiguring can make use of arguments passed to the context constructor or other instance data, as shown in the next section.
Passing parameters to the constructor
The EF6 DbContext had many constructors taking things like the connections string, model, etc. It is easy to use a similar pattern with EF Core by stashing those things in fields and using them in OnConfiguring.
public class MyContext : DbContext
{
private readonly string _connectionString;
public MyContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(_connectionString);
}
Using DbContextOptions without D.I.
The other constructor exposed by DbContext takes a DbContextOptions object. This is often used when creating DbContext instances from a D.I. container, as explained below. However, it can also be used explicitly. For example, an application can create a DbContextOptions object separately from the context, stash it away somewhere, and then use that same options object for every context instance created.
DbContextOptions objects are created using a builder. This is the same builder that is passed to OnConfiguring, so all the configuration methods are the same. Builder methods are designed to be chained in a fluent-like manner. The Options property on the builder returns the built options.
For example, a console app that takes a connection string as its first argument might do something like this:
public class Program
{
private static DbContextOptions _contextOptions;
public static void Main(string[] args)
{
_contextOptions = new DbContextOptionsBuilder()
.UseSqlServer(args[0])
.Options;
// My app stuff...
}
}
Now the class derived from DbContext can take the options and pass them to the base constructor:
public class MyContext : DbContext
{
public MyContext(DbContextOptions options)
: base(options)
{
}
}
There is no longer any need to override OnConfiguring. However, OnConfiguring can still be overridden and will still be called. This means that configuration can be passed to the constructor and then tweaked in OnConfiguring.
Using Dependency Injection to create instances
If the context type is registered in a D.I. container, then it can be resolved from that container in the normal way.
Two things to note when using D.I. to resolve a DbContext are:
- The context instance must be disposed after it has been used. Your D.I. container might do this automatically, especially if you use a scope. However, depending on your container, it may not happen if your context is registered as a transient.
- DbContext is not thread-safe. This means that a context must not be registered as a singleton and then used concurrently without additional locking. Also, some containers may allow a scoped service to be obtained without first creating a scope, in which case it acts like a singleton and will have the same issues.
D.I. with a parameterless constructor
A derived DbContext type can have a parameterless constructor and override OnConfiguring, just as described above. In this case the context class does not have any dependencies and so nothing else needs to be registered in the container other than the context type itself.
D.I. with DbContextOptions
It may be useful to register a DbContextOptions instance in D.I. This can often be created once and registered as a singleton. For example, using the Microsoft.Extensions.DependencyInjection
abstractions:
public class Program
{
private static IServiceProvider _serviceProvider;
public static void Main(string[] args)
{
var contextOptions = new DbContextOptionsBuilder()
.UseSqlServer(args[0])
.Options;
var services = new ServiceCollection()
.AddSingleton(contextOptions)
.AddScoped<MyContext>();
_serviceProvider = services.BuildServiceProvider();
// My app stuff...
}
}
Now whenever MyContext is resolved from the container the DbContextOptions instance will be injected into its constructor.
Generic DbContextOptions
So far all DbContextOptions objects have been non-generic. There is also a version that is generic on the type of the DbContext class. This is useful if there are multiple different DbContext types registered in D.I. by allowing each context type to depend on its own options. For example:
public class MyContext1 : DbContext
{
public MyContext1(DbContextOptions<MyContext1> options)
: base(options)
{
}
}
public class MyContext2 : DbContext
{
public MyContext2(DbContextOptions<MyContext2> options)
: base(options)
{
}
}
var contextOptions1 = new DbContextOptionsBuilder<MyContext1>()
.UseSqlServer(connectionString1)
.Options;
var contextOptions2 = new DbContextOptionsBuilder<MyContext2>()
.UseSqlServer(connectionString2)
.Options;
var services = new ServiceCollection()
.AddSingleton(contextOptions1)
.AddScoped<MyContext1>()
.AddSingleton(contextOptions2)
.AddScoped<MyContext2>();
_serviceProvider = services.BuildServiceProvider();
Resolving MyContext1 will result in DbContextOptions
Using AddDbContext
EF includes the AddDbContext sugar method that makes it easier to register a DbContextOptions and a DbContext when using the Microsoft.Extensions.DependencyInjection
abstractions. For example:
var services = new ServiceCollection()
.AddDbContext<MyContext>(
b => b.UseSqlServer(connectionString));
This registers MyContext as scoped and registers DbContextOptions
The DbContext type is registered as scoped by default. This can be changed by passing an argument to AddDbContext, but be aware of the notes at the start of this section if adding as a transient or singleton service.
AddDbContext doesn't do much more than register the context and options. It does also ensure that if your service provider contains Logging and/or MemoryCache services, then these will be used by EF, but there is nothing in AddDbContext that can't be done by any other mechanism for configuring your D.I. container.
Summary
Configuration of a DbContext is done using DbContextOptionsBuilder to build DbContextOptions. This can be done by overriding OnConfiguring or by building the options outside and passing them to a DbContext constructor, or by a combination of both these things. DbContext instances are usually created either with the parameterless constructor or with a constructor taking DbContextOptions. This is the same regardless of whether the instance is created explicitly or if D.I. is used.