Ho scritto un'applicazione web ASP.NET Core che ha bisogno di tutti i dati da alcune tabelle del mio database per poi organizzarlo in un formato leggibile per alcune analisi.
Il mio problema è che questi dati sono potenzialmente enormi e quindi per aumentare le prestazioni ho deciso di ottenere questi dati in parallelo e non una tabella alla volta.
Il mio problema è che non capisco come ottenere ciò con l'iniezione della dipendenza ereditaria in quanto per poter eseguire il lavoro parallelo, ho bisogno di DbContext
un'istanza di DbContext
per ognuno di questi lavori paralleli.
Il codice seguente produce questa eccezione:
---> (Inner Exception #6) System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyDbContext'.
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()
Progetto principale ASP.NET:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDistributedMemoryCache();
services.AddDbContext<AmsdbaContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ConnectionString"))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
loggerFactory.AddLog4Net();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseMvc();
}
Metodo di azione del controllore:
[HttpPost("[controller]/[action]")]
public ActionResult GenerateAllData()
{
List<CardData> cardsData;
using (var scope = _serviceScopeFactory.CreateScope())
using (var dataFetcher = new DataFetcher(scope))
{
cardsData = dataFetcher.GetAllData(); // Calling the method that invokes the method 'InitializeData' from below code
}
return something...;
}
Progetto .NET Core Library:
DataFetcher's InitializeData - per ottenere tutti i record della tabella secondo alcuni parametri irrilevanti:
private void InitializeData()
{
var tbl1task = GetTbl1FromDatabaseTask();
var tbl2task = GetTbl2FromDatabaseTask();
var tbl3task = GetTbl3FromDatabaseTask();
var tasks = new List<Task>
{
tbl1task,
tbl2task,
tbl3task,
};
Task.WaitAll(tasks.ToArray());
Tbl1 = tbl1task.Result;
Tbl2 = tbl2task.Result;
Tbl3 = tbl3task.Result;
}
L'attività di esempio di DataFetcher:
private async Task<List<SomeData>> GetTbl1FromDatabaseTask()
{
using (var amsdbaContext = _serviceScope.ServiceProvider.GetRequiredService<AmsdbaContext>())
{
amsdbaContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await amsdbaContext.StagingRule.Where(x => x.SectionId == _sectionId).ToListAsync();
}
}
Non sono sicuro che tu abbia effettivamente bisogno di più contesti qui. Hai notato che nei documenti EF Core, c'è questo avvertimento evidente:
avvertimento
EF Core non supporta più operazioni parallele eseguite sulla stessa istanza di contesto. Si dovrebbe sempre attendere il completamento di un'operazione prima di iniziare l'operazione successiva. In genere ciò viene eseguito utilizzando la parola chiave
await
su ogni operazione asincrona.
Questo non è del tutto preciso, o meglio, è semplicemente formulato in modo un po 'confuso. È possibile eseguire query parallele su una singola istanza di contesto. Il problema arriva con il rilevamento delle modifiche e la correzione degli oggetti di EF. Questi tipi di cose non supportano più operazioni che si verificano nello stesso momento, in quanto devono avere uno stato stabile su cui lavorare quando svolgono il proprio lavoro. Tuttavia, questo limita davvero la tua capacità di fare certe cose. Ad esempio, se dovessi eseguire i salvataggi paralleli / selezionare le query, i risultati potrebbero essere confusi. Potresti non recuperare le cose che sono effettivamente lì ora o cambiare tracciabilità potrebbe essere incasinato mentre si tenta di creare le istruzioni di inserimento / aggiornamento necessarie, ecc. Tuttavia, se stai facendo query non atomiche, come selezioni su tabelle indipendenti come vorresti fare qui, non c'è alcun problema reale, specialmente se non stai pianificando di fare ulteriori operazioni come le modifiche sulle entità che stai selezionando, e solo pensando di restituirle a una vista o qualcosa del genere.
Se si è certi di aver bisogno di contesti separati, la soluzione migliore è rinnovare il contesto con un utilizzo. Non ho ancora provato questo in precedenza, ma dovresti essere in grado di iniettare DbContextOptions<AmsdbaContext>
nella tua classe in cui queste operazioni stanno accadendo. Dovrebbe essere già registrato nella raccolta di servizi poiché è iniettato nel tuo contesto quando la raccolta di servizi lo istanzia. In caso contrario, puoi sempre crearne uno nuovo:
var options = new DbContextOptionsBuilder()
.UseSqlServer(connectionString)
.Build()
.Options;
In entrambi i casi, quindi:
List<Tbl1> tbl1data;
List<Tbl2> tbl2data;
List<Tbl3> tbl3data;
using (var tbl1Context = new AmsdbaContext(options))
using (var tbl2Context = new AmsdbaContext(options))
using (var tbl3Context = new AmsdbaContext(options))
{
var tbl1task = tbl1Context.Tbl1.ToListAsync();
var tbl2task = tbl2Context.Tbl2.ToListAsync();
var tbl3task = tbl3Context.Tbl3.ToListAsync();
tbl1data = await tbl1task;
tbl2data = await tbl2task;
tbl3data = await tbl3task;
}
È meglio usare await
per ottenere il risultato effettivo. In questo modo, non hai nemmeno bisogno di WaitAll
/ WhenAll
/ etc. e non stai bloccando la chiamata al Result
. Poiché le attività ritornano attive o già avviate, è sufficiente posticipare la chiamata in attesa fino a quando ciascuna di esse non è stata creata per acquistare l'elaborazione parallela.
Basta fare attenzione a ciò che si seleziona tutto ciò che è necessario all'interno degli usi. Ora che EF Core supporta il caricamento lento, se lo stai utilizzando, un tentativo di accedere a una proprietà di riferimento o raccolta che non è stata caricata attiverà ObjectDisposedException
, poiché il contesto sarà scomparso.