Efcore: Migration with dependency injection in a console application

Created on 28 Sep 2018  Â·  5Comments  Â·  Source: dotnet/efcore

I found a solution from stackoverflow how to apply migration to a console application with ef core and dependency injection as follows. It works but the method name BuildWebHost seems to be weird for non asp.net core projects.

Here I request a better name for it. Any suggestion is welcome!

```C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Example
{
public class TheContext : DbContext
{
public TheContext(DbContextOptions options) : base(options) { }
public DbSet Users { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class TheApp
{
    readonly TheContext _theContext;

    public TheApp(TheContext theContext) => _theContext = theContext;

    public void Run()
    {
        // Do something on _theContext            
    }
}

class Program
{
    // this connection string must be retrieved from a secure file.
    private static readonly string _connectionString = "Data Source = MyDatabase.db";

    private static void ConfigureServices(IServiceCollection isc)
    {
        isc.AddDbContextPool<TheContext>(options => options.UseSqlite(_connectionString));
        isc.AddSingleton<TheApp>();
    }

    private static UselessWrapper BuildWebHost(string[] args)
    {
        // create service collection
        IServiceCollection isc = new ServiceCollection();
        ConfigureServices(isc);

        // create service provider
        IServiceProvider isp = isc.BuildServiceProvider();
        return new UselessWrapper { Services = isp };
    }

    private static void Main(string[] args)
    {
        BuildWebHost(args).Services.GetService<TheApp>().Run();
    }

   private class UselessWrapper
   {
        public IServiceProvider Services { get; set; }
   }
}

}
```

closed-question customer-reported

Most helpful comment

@sadqiang Using IDesignTimeDbContextFactory would be the mode idiomatic way of doing it. However, you should also be careful about the lifetime used to register the context. By default it gets registered as scoped, but the code above doesn't ever create a scope to resolve it. Registering as singleton instead and using the factory looks like this:
```C#
public class TheContext : DbContext
{
public TheContext(DbContextOptions options) : base(options) { }
public DbSet Users { get; set; }
}

public class User
{
public int Id { get; set; }
public string Name { get; set; }
}

public class TheApp
{
readonly TheContext _theContext;

public TheApp(TheContext theContext) => _theContext = theContext;

public void Run()
{
    // Do something on _theContext            
}

}

class Program
{
// this connection string must be retrieved from a secure file.
private static readonly string _connectionString = "Data Source = MyDatabase.db";

private static void ConfigureServices(IServiceCollection isc)
{
    isc.AddDbContext<TheContext>(
        options => options.UseSqlite(_connectionString), 
        ServiceLifetime.Singleton, 
        ServiceLifetime.Singleton);

    isc.AddSingleton<TheApp>();
}

private static IServiceProvider CreateServiceProvider()
{
    // create service collection
    IServiceCollection isc = new ServiceCollection();
    ConfigureServices(isc);

    // create service provider
    return isc.BuildServiceProvider();
}

private static void Main(string[] args)
{
    CreateServiceProvider().GetService<TheApp>().Run();
}

private class Factory : IDesignTimeDbContextFactory<TheContext>
{
    public TheContext CreateDbContext(string[] args) 
        => CreateServiceProvider().GetService<TheContext>();
}

}

To keep registering it as scoped requires an appropriate scope to be created to resolve it from:
```C#
public class TheContext : DbContext
{
    public TheContext(DbContextOptions<TheContext> options) : base(options) { }
    public DbSet<User> Users { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class TheApp
{
    readonly TheContext _theContext;

    public TheApp(TheContext theContext) => _theContext = theContext;

    public void Run()
    {
        // Do something on _theContext            
    }
}

class Program
{
    // this connection string must be retrieved from a secure file.
    private static readonly string _connectionString = "Data Source = MyDatabase.db";

    private static void ConfigureServices(IServiceCollection isc)
    {
        isc.AddDbContext<TheContext>(options => options.UseSqlite(_connectionString));

        isc.AddSingleton<TheApp>();
    }

    private static IServiceProvider CreateServiceProvider()
    {
        // create service collection
        IServiceCollection isc = new ServiceCollection();
        ConfigureServices(isc);

        // create service provider
        return isc.BuildServiceProvider();
    }

    private static void Main(string[] args)
    {
        using (var scope = CreateServiceProvider().CreateScope())
        {
            scope.ServiceProvider.GetService<TheApp>().Run();
        }
    }

    private class Factory : IDesignTimeDbContextFactory<TheContext>
    {
        public TheContext CreateDbContext(string[] args) 
            => CreateServiceProvider().CreateScope().ServiceProvider.GetService<TheContext>();
    }
}

All 5 comments

@sadqiang Using IDesignTimeDbContextFactory would be the mode idiomatic way of doing it. However, you should also be careful about the lifetime used to register the context. By default it gets registered as scoped, but the code above doesn't ever create a scope to resolve it. Registering as singleton instead and using the factory looks like this:
```C#
public class TheContext : DbContext
{
public TheContext(DbContextOptions options) : base(options) { }
public DbSet Users { get; set; }
}

public class User
{
public int Id { get; set; }
public string Name { get; set; }
}

public class TheApp
{
readonly TheContext _theContext;

public TheApp(TheContext theContext) => _theContext = theContext;

public void Run()
{
    // Do something on _theContext            
}

}

class Program
{
// this connection string must be retrieved from a secure file.
private static readonly string _connectionString = "Data Source = MyDatabase.db";

private static void ConfigureServices(IServiceCollection isc)
{
    isc.AddDbContext<TheContext>(
        options => options.UseSqlite(_connectionString), 
        ServiceLifetime.Singleton, 
        ServiceLifetime.Singleton);

    isc.AddSingleton<TheApp>();
}

private static IServiceProvider CreateServiceProvider()
{
    // create service collection
    IServiceCollection isc = new ServiceCollection();
    ConfigureServices(isc);

    // create service provider
    return isc.BuildServiceProvider();
}

private static void Main(string[] args)
{
    CreateServiceProvider().GetService<TheApp>().Run();
}

private class Factory : IDesignTimeDbContextFactory<TheContext>
{
    public TheContext CreateDbContext(string[] args) 
        => CreateServiceProvider().GetService<TheContext>();
}

}

To keep registering it as scoped requires an appropriate scope to be created to resolve it from:
```C#
public class TheContext : DbContext
{
    public TheContext(DbContextOptions<TheContext> options) : base(options) { }
    public DbSet<User> Users { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class TheApp
{
    readonly TheContext _theContext;

    public TheApp(TheContext theContext) => _theContext = theContext;

    public void Run()
    {
        // Do something on _theContext            
    }
}

class Program
{
    // this connection string must be retrieved from a secure file.
    private static readonly string _connectionString = "Data Source = MyDatabase.db";

    private static void ConfigureServices(IServiceCollection isc)
    {
        isc.AddDbContext<TheContext>(options => options.UseSqlite(_connectionString));

        isc.AddSingleton<TheApp>();
    }

    private static IServiceProvider CreateServiceProvider()
    {
        // create service collection
        IServiceCollection isc = new ServiceCollection();
        ConfigureServices(isc);

        // create service provider
        return isc.BuildServiceProvider();
    }

    private static void Main(string[] args)
    {
        using (var scope = CreateServiceProvider().CreateScope())
        {
            scope.ServiceProvider.GetService<TheApp>().Run();
        }
    }

    private class Factory : IDesignTimeDbContextFactory<TheContext>
    {
        public TheContext CreateDbContext(string[] args) 
            => CreateServiceProvider().CreateScope().ServiceProvider.GetService<TheContext>();
    }
}

@ajcvickers I have 2 questions, I hope you can answer

1.why does AddDbContextPool have no overload method for ServiceLifetime parameters?

2.I have a service on the generic host, how to get a brand new DbContext from the DbContextPool on every call.

@Varorbc

  1. Because it's only designed to work with scoped context instances; the pool depends on this
  2. You can't; DbContext pooling is designed to return the same instance. If you want new instances each time, then don't using context pooling.

@ajcvickers can you provider an orleans with ef core sample?

@ajcvickers:

I am still thinking of your second example in which the default scope of TheContext is used.
TheApp is registered as a singleton, why did you make a scope when resolving TheApp? Is it due to the scoped TheContext that is internally used by TheApp?

private static void Main(string[] args)
{
    using (var scope = CreateServiceProvider().CreateScope())
    {
        scope.ServiceProvider.GetService<TheApp>().Run();
    }
}
Was this page helpful?
0 / 5 - 0 ratings