In un progetto ASP.NET Core ho il seguente comando all'avvio:
services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));
services.AddTransient<IValidationService, ValidationService>();
services.AddTransient<IValidator<Model>, ModelValidator>();
Il ValidationService è il seguente:
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);
}
}
E ModelValidator è il seguente:
public class ModelValidator : AbstractValidator<Model> {
public ModelValidator(Context context) {
// Some code using context
}
}
Quando iniettare un IValidationService in un controller e usarlo come:
List<Error> errors = await _validator.ValidateAsync(order);
Ottengo l'errore:
System.ObjectDisposedException: impossibile accedere a un oggetto disposto. Una causa comune di questo errore è l'eliminazione di un contesto risolto dall'integrazione delle dipendenze e successivamente il tentativo di utilizzare la stessa istanza di contesto altrove nell'applicazione. Ciò potrebbe accadere se chiami Dispose () nel contesto o avvolge il contesto in un'istruzione using. Se si sta utilizzando l'integrazione delle dipendenze, è necessario lasciare che il contenitore di input delle dipendenze si occupi di eliminare le istanze di contesto. Nome oggetto: "Contesto".
Qualche idea del motivo per cui ho questo errore quando utilizzo Context in ModelValidator.
Come risolvere questo?
AGGIORNARE
Così ho cambiato il codice in:
services.AddScoped<IValidationService, ValidationService>();
services.AddScoped<IValidator<Model>, ModelValidator>();
Ma ottengo lo stesso errore ...
AGGIORNAMENTO - Codice dati seme all'interno del metodo Configure all'avvio
Quindi, sul metodo Configure ho:
if (hostingEnvironment.IsDevelopment())
applicationBuilder.SeedData();
E l'estensione SeedData è:
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
}
}
Cosa mi manca?
AGGIORNAMENTO - Una possibile soluzione
Cambiare il mio metodo Seed al seguente sembra funzionare:
using (IServiceScope scope =
_provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
Context context = _provider.GetService<Context>();
// Insert data in database
}
In ASP.NET Core 2.1 i metodi sono leggermente cambiati. Il metodo generale è simile al 2.0, solo il nome del metodo e i tipi restituiti sono stati modificati.
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build()
.Seed();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return new WebHostBuilder()
...; // Do not call .Build() here
}
Con ASP.NET Core 2.0 sono state apportate alcune modifiche al modo in cui gli strumenti EF Core ( dotnet ef migrations
ecc.) Determinano DbContext e la stringa di connessione in fase di progettazione.
La risposta di seguito indica che le migrazioni e il seeding vengono applicati quando si chiama uno qualsiasi dei comandi dotnet ef xxx
.
Il nuovo modello per ottenere un'istanza in fase di progettazione per gli strumenti EF Core è l'utilizzo di un metodo statico BuildHostWeb
.
Secondo questo annuncio , EF Core utilizzerà ora il metodo statico BuildWebHost
che configura l'intera applicazione, ma non la esegue.
public class Program { public static void Main(string[] args) { var host = BuildWebHost(args); host.Run(); } // Tools will use this to get application services public static IWebHost BuildWebHost(string[] args) => new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); }
Sostituiscilo nel tuo vecchio metodo Main
public static void Main(string[] args)
{
var host = BuildWebHost(args)
.Seed();
host.Run();
}
Dove Seed è un metodo di estensione:
public static IWebHost Seed(this IWebHost webhost)
{
using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
{
// alternatively resolve UserManager instead and pass that if only think you want to seed are the users
using (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 = ... });
}
}
Esiste un modello semi-ufficiale su come eseguire il seeding di Entity Framework Core nell'applicazione ASP.NET Core che dovresti applicare, perché all'avvio dell'applicazione non c'è richiesta e quindi nessun RequestServices
(che risolve i servizi con ambito).
In sostanza si riduce a creare un nuovo ambito, risolvere i tipi necessari e smaltire nuovamente l'ambito al termine.
// serviceProvider is app.ApplicationServices from 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);
}
}
Uno dei motivi per la risoluzione diretta di un servizio tramite app.ApplicationServices.GetService<MyService>()
è che ApplicationServices
è il provider dell'ambito dell'applicazione (o del ciclo di vita) e i servizi risolti qui rimangono attivi fino allo spegnimento dell'applicazione.
Di solito il contenitore con ambito si risolverà dal suo contenitore padre, se l'oggetto esiste già lì. Pertanto, se si crea un'istanza di DbContext in questo modo nell'applicazione, sarà disponibile nel contenitore ApplicationServices
e quando si verifica una richiesta, verrà creato un contenitore figlio.
Ora quando si risolve DbContext non verrà risolto come ambito, poiché esiste già nel contenitore padre, quindi verrà restituita l'istanza del contenitore padre. Ma poiché è stato eliminato durante la semina, non sarà accessibile.
Un contenitore di ambito non è altro che un contenitore singleton con durata limitata.
Quindi non risolvere mai i servizi con ambito nell'avvio dell'applicazione senza utilizzare il modello sopra di prima creare un ambito e risolverlo da esso.
Solo un'ipotesi su ciò che causa il tuo errore:
Stai utilizzando DI e chiamate asincrone. Se da qualche parte nello stack delle chiamate si restituisce un void anziché Task, si ottiene il comportamento descritto. A quel punto la chiamata è terminata e il contesto disposto. Quindi controlla se hai una chiamata asincrona che restituisce un void invece di Task. Se si modifica il valore di ritorno, l'oggetto objectpositedexception è probabilmente corretto.
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static async Task SeedData(this IApplicationBuilder builder) { //This line of code
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
E in configure:
if (hostingEnvironment.IsDevelopment()){
await applicationBuilder.SeedData();
}
Post del blog su come risolvere questo errore: impossibile accedere a un oggetto disposto in-asp-net-core-when-injecting-dbcontext