1

Been doing some sample code with ASP.NET Core to try to understand how it fits together and I am stumped as to why I am unable to successfully resolve a service.

The configure services method has the call to add ISeedDataService

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions();
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddDbContext<CustomerDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddScoped<ICustomerDbContext, CustomerDbContext>();
    services.AddScoped<ICustomerRepository, CustomerRepository>();
    services.AddScoped<ISeedDataService, SeedDataService>();
}

In Configure I am calling AddSeedData() as below

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   app.AddSeedData();
}

which is calling the extension method below

public static async void AddSeedData(this IApplicationBuilder app)
{
    var seedDataService = app.ApplicationServices.GetRequiredService<ISeedDataService>();
    await seedDataService.EnsureSeedData();
}

and the SeedDataService is below

public class SeedDataService : ISeedDataService
{
    private ICustomerDbContext _context;
    public SeedDataService(ICustomerDbContext context)
    {
        _context = context;
    }

    public async Task EnsureSeedData()
    {
        _context.Database.EnsureCreated();

        _context.Customers.RemoveRange(_context.Customers);
        _context.SaveChanges();

        Customer customer = new Customer();
        customer.FirstName = "Chuck";
        customer.LastName = "Norris";
        customer.Age = 30;
        customer.Id = Guid.NewGuid();

        _context.Add(customer);

        Customer customer2 = new Customer();
        customer2.FirstName = "Fabian";
        customer2.LastName = "Gosebrink";
        customer2.Age = 31;
        customer2.Id = Guid.NewGuid();

        _context.Add(customer2);

        await _context.SaveChangesAsync();
    }
}

Totally unsure as to what I am doing wrong, the error is System.InvalidOperationException: 'Cannot resolve scoped service 'secondapp.Services.ISeedDataService' from root provider.'

2
  • 3
    Tell us the full error message or unfortunately we will have to close the question as unclear and not having a minimal reproducible example Commented Sep 18, 2018 at 13:54
  • @DavidG updated, thanks Commented Sep 19, 2018 at 7:31

2 Answers 2

3

You are (and should be) adding the ISeedDataService as scoped service. However, you are attempting to resolve it from the root service provider (e.g. app.ApplicationServices) which is not scoped. This means that scoped services resolved from it effectively are turned into a singleton service and are not disposed until the application shuts down or it will result in an error.

The solution here is to create a scope yourself:

public void Configure(IApplicationBuilder app)
{
    using (var scope = app.ApplicationServices.CreateScope())
    {
        var seedDataService = scope.ServiceProvider.GetRequiredService<ISeedDataService>();
        // Use seedDataService here
    }
}

Please take a look at the documentation regarding dependency injection scopes.


On a second note: your AddSeedData extension method is async void and you are not waiting for the result. You should return a task (async Task) call AddSeedData().GetAwaiter().GetResult() to make sure you block until the seeding is complete.

Sign up to request clarification or add additional context in comments.

5 Comments

Please note that this is the old way of seeding/migrating the database. This isn't advised with ASP.NET Core 2.x, since it will create issues with the new EF Core dotnet tools (i.e. dotnet ef migrations add Xxx as it will trigger seeding). The new recommended seeding and migration pattern can be found here.
@Tseng: I wish I could upvote your comment more. Unfortunately, when people search for how to seed with EF Core, there's a ton of outdated posts out there advocating the old approach, which used to be the only approach. Definitely a better way now.
@Tseng Since when is this the new recommended seeding and migration pattern? EF Core 2.1 introduced Data Seeding which will populate seed data during a migration.
@Brad: as in the linked answer, since EF Core 2.0. EF Core tools will now use the DI configuration of the main application. On each dotnet ef ... command, it will execute the ConfigureServices and Configure methods. Then problem is, when you run dotnet ef migrations remove it will apply all migrations and seeding, but in order to reverse an already applied migration, you have to do dotnet ef database update <lastgoodmigration>. When you now run dotnet ef migrations remove it will reapply the migration and tell you its not possible to migrate until you reverse it on DB first
For that reason there have been added the BuildWebHost method (and now CreateWebHostBuilder) which will do the same, but not RUN the host (since that happens outside the CreateWebHostBuilder method. Also see the announcement in the linked answer: stackoverflow.com/a/38704828/455493. P.S. .HasData is meant to add fixed/mandatory data (i.e. when you add a table named DocumentTypes to initially seed it. Seeding testdata like adding an default admin user or test data for development is done outside the context
-1

The Configure() method allows parameter dependency injection so you can do the following.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ISeedDataService seedService)
{
    seedService.EnsureSeedData().Wait(); // Configure() is not async so you have to wait
}

3 Comments

.Wait() isn't recommended in general, it has different throw semantics. It throws AggregateException rather than the specific exception that was triggered. .GetAwaiter().GetResult() is the correct way of async awaiting to be able to catch the specific rather than the generic (and possibly containing mulltiple) exceptions within the AggregateException
@Tseng Thanks for the explainer and good to know the difference, I wasn't actually aware of that. I was trying to highlight the fact it needed some sort of waiting because the OP was not doing it in their original question so their seed method would not have even executed.
ISeedDataService is a scoped service, therefore you can't inject it into the Configure method as it will be resolved from the root service provider. This will either result in an error or your scoped service will be become a singleton service.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.