简体   繁体   中英

Asp.Net Core - How to seed data - Object reference not set to an instance of an object

After converted all my tables to start using Guid type in identity columns, I failed to seed data, so I simplified a lot the code to localize the error, and ended with a seeding class as follows:

public class SeedTest
{
    private readonly MyDbContext _context;

    public SeedTest(MyDbContext context)
    {
        _context = context;
    }

    public async Task SeedTest()
    {
        Values value1 = new Values
        {
            Id = Guid.Parse("29c48913-1b5c-47b8-g144-08d6d2273deb"),
            ValueName = "value 1",
            Created = DateTime.Now
        };

        _context.Values.Add(value1);

        await _context.SaveChangesAsync();
    }

    public SeedTest()
    {
    }
}

This class is called from another one:

public interface IDatabaseInitializer
{
    Task SeedAsync();
}

public class DatabaseInitializer : IDatabaseInitializer
{
    public async Task SeedAsync()
    {
            SeedTest _seedTest = new SeedTest();
            await _seedTest.SeedTest();
    }
}

which is called from startup.cs

    public class Startup
    {
        public IConfiguration Configuration { get; }
        private readonly IHostingEnvironment _hostingEnvironment;

        public Startup(IConfiguration configuration, IHostingEnvironment env)
        {
            Configuration = configuration;
            _hostingEnvironment = env;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddMyDbContext<MyDbContext>(options =>
                options.UseSqlServer("ConnectionStrings:MyCn"));
            ...
            // DB Seeding
            services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
            ...
            ...
     }

And here is how it is triggered from program.cs

public class Program
{

    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            try
            {
                var databaseInitializer = services.GetRequiredService<IDatabaseInitializer>();
                databaseInitializer.SeedAsync().Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogCritical(LoggingEvents.INIT_DATABASE, ex, LoggingEvents.INIT_DATABASE.Name);
            }
        }

        host.Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>()
                    .Build();
}

Unfortunately this implementation didn't seed any data in the database, the unique error I could find is in the logs files, and it says:

System.NullReferenceException: Object reference not set to an instance of an object. and it points to the last line of SeedTest class.

So what am I doing wrong ?

new SeedTest() does not initialize its _context field. You could use DI on your DatabaseInitializer to instantiate a SeedTest with a MyDbContext .

public class DatabaseInitializer : IDatabaseInitializer
{
    private readonly MyDbContext _context;

    public DatabaseInitializer(MyDbContext context)
    {
        _context = context;
    }

    public async Task SeedAsync()
    {
        SeedTest _seedTest = new SeedTest(_context);
        await _seedTest.SeedTest();
    }
}

You are explicitly newing an instance of SeedTest in DatabaseInitialize, while the instance of DatabaseInitialize is being created by the dependency injection service. Register the SeedTest class in the services with the correct scope and let the dependency injection do its thing.

In ConfigureServices add something like

services.AddTransient<SeedTest>();

Modify DatabaseInitializer

public class DatabaseInitializer : IDatabaseInitializer{
     private readonly SeedTest _seedTest;
     public DatabaseInitializer(SeedTest seedTest) 
     {
          _seedTest = seedTest;
     }

     public async Task SeedAsync()
     {
         await _seedTest.SeedTest();
     }
}

Remove the parameterless SeedTest constructor and make sure the MyDbContext type registered is what is passed in the other constructor as you have both MyDbContext and DbContext.

You can try this, i have used .net core 2.2 for this sample -

MyDbContext.cs

public class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
            Database.EnsureCreated();
        }

        public DbSet<Values> Values { get; set; }
    }

SeedTest.cs

public class SeedTest
    {
        private readonly MyDbContext _context;

        public SeedTest(MyDbContext context)
        {
            _context = context;
        }

        public async Task SeedTest1()
        {
            Values value1 = new Values
            {
                Id = Guid.Parse("AFE1052A-A694-48AF-AA77-56D2D945DE31"),
                ValueName = "value 1",
                Created = DateTime.Now
            };

            _context.Values.Add(value1);

            var value  = await _context.SaveChangesAsync();
        }

        public SeedTest()
        {
        }
    }

Service

public interface IDatabaseInitializer
    {
        Task SeedAsync();
    }

    public class DatabaseInitializer : IDatabaseInitializer
    {
        private readonly MyDbContext _cotext;

       // Inject DbContext 
        public DatabaseInitializer(MyDbContext dbContext)
        {
            _cotext = dbContext;
        }
        public async Task SeedAsync()
        {
            // Object with contructor which having DbContext parameter
            SeedTest _seedTest = new SeedTest(_cotext);
            await _seedTest.SeedTest1();
        }
    }

startup.cs

 services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
 services.AddDbContext<MyDbContext>(option=> option.UseSqlServer("Data Source=localhost;Initial Catalog=StackOverFlow1;Integrated Security=True"));

program.cs

public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateWebHostBuilder(args).Build();

            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var databaseInitializer = services.GetRequiredService<IDatabaseInitializer>();
                    databaseInitializer.SeedAsync().Wait();
                }
                catch (Exception ex)
                {
                }
            }
            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }

You can read more about seed data .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM