I've been struggling for 2 days now to find an appropriate solution for my simple scenario.
Scenario I want to accomplish
Doing multiple database access in a .Net Core Parallel loop that will insert multiple items in the same database.
var actions = new List<Action>();
actions.Add(() => { new DbContext.Set<TEntity>().Add(entity); });
actions.Add(() => { new DbContext.Set<TEntity>().Add(entity); });
actions.Add(() => { new DbContext.Set<TEntity>().Add(entity); });
actions.Add(() => { new DbContext.Set<TEntity>().Add(entity); });
Parallel.ForEach(actions, new ParallelOptions { MaxDegreeOfParallelism = 2 },
action =>
{
action();
});
Known limitations
- EF Core's DBContext are not thread safe (we need to recreate them in each trhead)
- MySQL server does not accept 2 connections made withing the same transaction
- It seems we cannot pass an already opened connection to the DBContext
- EF Core opens and closes a connection to the DB internally each time we interact with it
Exception
Multiple simultaneous connections or connections with different connection strings inside the same transaction are not currently supported.
at MySql.Data.MySqlClient.MySqlConnection.Open() at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnection(Boolean errorsExpected) at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Open(Boolean errorsExpected) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable
1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable
1.Enumerator.MoveNext() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable1 results, QueryContext queryContext, IList
1 entityTrackingInfos, IList1 entityAccessors)+MoveNext() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor
1.EnumeratorExceptionInterceptor.MoveNext() at System.Collections.Generic.List1.AddEnumerable(IEnumerable
1 enumerable) at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) at CloseTheMonth.Backend.Data.Repositories.AccountUserRightRepository.ListAccounts(Guid userId) in C:\Work\GitHub\CloseTheMonth\Backend\CloseTheMonth.Data\Repositories\AccountUserRightRepository.cs:line 44 at CloseTheMonth.Backend.Services.AccountUserRightService.ListAccounts(Guid userId) in C:\Work\GitHub\CloseTheMonth\Backend\CloseTheMonth.Services\AccountUserRightService.cs:line 53 at CloseTheMonth.Backend.Controllers.AppController.Init(String authorization, AppInitRequest request) in C:\Work\GitHub\CloseTheMonth\Backend\CloseTheMonth.Backend\Controllers\AppController.cs:line 101
Reflections...
If I could open a connection globally, like this, and pass it to my DBContexts, that would do the trick. But I checked the EF Core and Pomelo source code and I didn't find a way to achieve something like this.
Maybe some other EF Core MySQL drivers beside Pomelo can do this ?
var actions = new List<Action>();
using (var conn = new MySqlConnection())
{
actions.Add(() => { new DbContext(conn).Set<TEntity>().Add(entity); });
actions.Add(() => { new DbContext(conn).Set<TEntity>().Add(entity); });
actions.Add(() => { new DbContext(conn).Set<TEntity>().Add(entity); });
actions.Add(() => { new DbContext(conn).Set<TEntity>().Add(entity); });
Parallel.ForEach(actions, new ParallelOptions { MaxDegreeOfParallelism = 2 },
action =>
{
action();
});
}
I'm using MySQL server (8.0.22) with Pomelo.EntityFrameworkCore.MySql (2.1.4)