on asp.net core project have following on startup:
services.adddbcontext<context>(x => x.usesqlserver(connectionstring)); services.addtransient<ivalidationservice, validationservice>(); services.addtransient<ivalidator<model>, modelvalidator>();
the validationservice follows:
public interface ivalidationservice { task<list<error>> validateasync<t>(t model); }
public class validationservice : ivalidationservice {
private readonly iserviceprovider _provider; public validationservice(iserviceprovider provider) { _provider = provider; } public async task<list<error>> validateasync<t>(t model) { ivalidator<t> validator = _provider.getrequiredservice<ivalidator<t>>(); return await validator.validateasync(model); }
}
and modelvalidator follows:
public class modelvalidator : abstractvalidator<model> { public modelvalidator(context context) { // code using context } }
when inject ivalidationservice in controller , use as:
list<error> errors = await _validator.validateasync(order);
i error:
system.objectdisposedexception: cannot access disposed object. common cause of error disposing context resolved dependency injection , later trying use same context instance elsewhere in application. may occur calling dispose() on context, or wrapping context in using statement. if using dependency injection, should let dependency injection container take care of disposing context instances. object name: 'context'.
any idea why having error when using context inside modelvalidator.
how fix this?
update
so changed code to:
services.addscoped<ivalidationservice, validationservice>(); services.addscoped<ivalidator<model>, modelvalidator>();
but same error ...
update - seed data code inside configure method on startup
so on configure method have:
if (hostingenvironment.isdevelopment()) applicationbuilder.seeddata();
and seeddata extension is:
public static class dataseedextensions {
private static iserviceprovider _provider; public static void seeddata(this iapplicationbuilder builder) { _provider = builder.applicationservices; _type = type; using (context context = (context)_provider.getservice<context>()) { await context.database.migrateasync(); // insert data code } }
what missing?
update - possible solution
changing seed method following seems work:
using (iservicescope scope = _provider.getrequiredservice<iservicescopefactory>().createscope()) { context context = _provider.getservice<context>(); // insert data in database }
new answer, applies asp.net core 2.x , newer
with asp.net core 2.0 there have been changes in how ef core tools (dotnet ef migartions
etc.) determine dbcontext , connection string @ design time.
the below answer leads migrations , seeding applied when calling of dotnet ef xxx
commands.
the new pattern getting design time instance ef core tools using buildhostweb
static method.
as per this announcement, ef core use static buildwebhost
method configures whole application, doesn't run it.
public class program { public static void main(string[] args) { var host = buildwebhost(args); host.run(); } // tools use application services public static iwebhost buildwebhost(string[] args) => new webhostbuilder() .usekestrel() .usecontentroot(directory.getcurrentdirectory()) .useiisintegration() .usestartup<startup>() .build(); }
replace in old main
method
public static void main(string[] args) { var host = buildwebhost(args) .seed(); host.run(); }
where seed extension method:
public static iwebhost seed(this iwebhost webhost) { using (var scope = webhost.services.getservice<iservicescopefactory>().createscope()) { // alternatively resolve usermanager instead , pass if think want seed users use(var dbcontext = scope.serviceprovider.getrequiredservice<applicationdbcontext>()) { seeddata.seedasync(dbcontext).getawaiter().getresult(); } } } public static class seeddata { public static async task seedasync(applicationdbcontext dbcontext) { dbcontext.users.add(new user { id = 1, username = "admin", passwordhash = ... }); } }
old answer, still applies asp.net core 1.x
there semi-official pattern on how seed entity framework core in asp.net core application should apply, because during application startup there no request , hence no requestservices
(which resolves scoped services).
in essence boils down creating new scope, resolve types need , dispose scope again once you're finished.
// serviceprovider app.applicationservices configure(iapplicationbuilder app) method using (var servicescope = serviceprovider.getrequiredservice<iservicescopefactory>().createscope()) { var db = servicescope.serviceprovider.getservice<appdbcontext>(); if (await db.database.ensurecreatedasync()) { await seeddatabase(db); } }
one of reasons directly resolving service via app.applicationservices.getservice<myservice>()
applicationservices
application (or lifetime) scope provider , services resolved here stay alive until application shut down.
usually scoped container resolve it's parent container, if object exists there. if instantiate dbcontext way in application, available in applicationservices
container , when request happens, child container created.
now when resolving dbcontext won't resolved scoped, because exists in parent container, instance of parent container returned instead. since has been disposed during seeding, won't accessible.
a scope container nothing else singleton container limited lifetime.
so never resolve scoped services in application startup w/o using pattern above of first creating scope , resolving it.
Comments
Post a Comment