'Why Is ServiceScope Causing Null Reference Exception?

In the following code, I define the _serviceScope variable in the constructor. However, this results in a null reference exception when I call .SaveChanges() on my dbContext.

public class Seed
{
    private readonly IServiceProvider _serviceProvider;
    private IServiceScope _serviceScope;

    public Seed(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        // define service scope in the constructor
        _serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
    }

    public async Task EventTypes()
    {
        string[] eventTypes = { "Copy", "Counter-Proposal" };

        var dbContext = _serviceScope.ServiceProvider.GetService<ApplicationDbContext>();

        foreach (string eventType in eventTypes)
        {
            if (!dbContext.EventTypes.Any(x => x.Name.ToUpperInvariant() == eventType.ToUpperInvariant()))
            {
                await dbContext.EventTypes.AddAsync(new EventType { Id = Guid.NewGuid(), Name = eventType });
                // null reference exception occurs on this line...
                dbContext.SaveChanges();
            }
        }
    }

    public async Task EventScheduleItemTypes()
    {
        string[] eventScheduleItemTypes = { "Orientation", "Event" };

        var dbContext = _serviceScope.ServiceProvider.GetService<ApplicationDbContext>();

        foreach (string eventScheduleItemType in eventScheduleItemTypes)
        {
            if (!dbContext.EventScheduleItemTypes.Any(x => x.Name.ToUpperInvariant() == eventScheduleItemType.ToUpperInvariant()))
            {
                await dbContext.EventScheduleItemTypes.AddAsync(new EventScheduleItemType { Id = Guid.NewGuid(), Name = eventScheduleItemType });
                dbContext.SaveChanges();
            }
        }
    }
}

By contrast, when I define the variable inside the EventTypes method, the code runs without error. Why can't the _serviceScope be defined in the constructor and used throughout the class? Does it have to do with the way the IServiceScope gets disposed?

public class Seed
{
    private readonly IServiceProvider _serviceProvider;

    public Seed(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task EventTypes()
    {
        string[] eventTypes = { "Copy", "Counter-Proposal" };

        // define service scope in the method
        var _serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
        var dbContext = _serviceScope.ServiceProvider.GetService<ApplicationDbContext>();

        foreach (string eventType in eventTypes)
        {
            if (!dbContext.EventTypes.Any(x => x.Name.ToUpperInvariant() == eventType.ToUpperInvariant()))
            {
                await dbContext.EventTypes.AddAsync(new EventType { Id = Guid.NewGuid(), Name = eventType });
                // no errors
                dbContext.SaveChanges();
            }
        }
    }

    public async Task EventScheduleItemTypes()
    {
        string[] eventScheduleItemTypes = { "Orientation", "Event" };

        // define service scope in the method
        var _serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
        var dbContext = _serviceScope.ServiceProvider.GetService<ApplicationDbContext>();

        foreach (string eventScheduleItemType in eventScheduleItemTypes)
        {
            if (!dbContext.EventScheduleItemTypes.Any(x => x.Name.ToUpperInvariant() == eventScheduleItemType.ToUpperInvariant()))
            {
                await dbContext.EventScheduleItemTypes.AddAsync(new EventScheduleItemType { Id = Guid.NewGuid(), Name = eventScheduleItemType });
                // no errors
                dbContext.SaveChanges();
            }
        }
    }
}

For completeness, I use the class like so:

public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...

    var Seed = new Seed(app.ApplicationServices);

    //await Seed.Roles();
    //await Seed.Users();
    await Seed.EventTypes();
    await Seed.EventScheduleItemTypes();
}

Stack trace:

   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.System.IObserver<System.Collections.Generic.KeyValuePair<System.String,System.Object>>.OnNext(KeyValuePair`2 keyValuePair)
   at System.Diagnostics.DiagnosticListener.Write(String name, Object value)
   at Microsoft.EntityFrameworkCore.Internal.CoreLoggerExtensions.SaveChangesFailed(IDiagnosticsLogger`1 diagnostics, DbContext context, Exception exception)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at ProjectX.Seed.<EventScheduleItemTypes>d__6.MoveNext() in C:\VSO\Source\Internal\Project X\ProjectX\Startup.cs:line 174
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ProjectX.Startup.<Configure>d__5.MoveNext() in C:\VSO\Source\Internal\Project X\ProjectX\Startup.cs:line 87

Edit: It also works when I create the methods as static and pass each method a new scope. Apparently, it's getting rid of that scope and not telling anybody. Is there a better way of doing this so I don't need 4 scopes!?

    await Seed.Roles(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetService<RoleManager<IdentityRole>>());
    await Seed.Users(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetService<UserManager<ApplicationUser>>());
    await Seed.EventTypes(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetService<ApplicationDbContext>());
    await Seed.EventScheduleItemTypes(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetService<ApplicationDbContext>());

Note: I have also tried wrapping the methods in a using statement but the service is apparently gone regardless.



Solution 1:[1]

I had the same problem .Do not create the services in the constructor, this will basically make all services singletons. Move them into your method .

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ehsan Akbar