实体框架核心 - 非线程安全 - ServiceLifetime.Transient

时间:2017-07-18 18:44:18

标签: c# .net multithreading entity-framework entity-framework-core

看来我的数据库上下文不是线程安全的,即使是我指定的瞬态:

ServiceLifetime.Transient 

在数据库的启动配置中。

Startup.cs

services.AddEntityFrameworkSqlServer().AddDbContext<DatabaseContext>((serviceProvider, options) => options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking).UseSqlServer(connectionString).UseInternalServiceProvider(serviceProvider), ServiceLifetime.Transient);

阿比

[HttpPost("GetOrganisations")] //Yes post, dont harass me :)
public async Task<IActionResult> GetOrganisations([FromBody] GetOrganisationsModel model)
{
    Task<IEnumerable<OrganisationModel>> organisations = _organisationService.GetOrganisations(model?.Id, model?.StatusIds);

    Task<int> organisationTotalCount = _organisationService.GetOrganisationCount();

    await Task.WhenAll(organisations, organisationTotalCount);

    return Ok(new OrganisationViewModel
    {
        Organisations = await organisations,
        OrganisationTotalCount = await organisationTotalCount
    });
}

回购

public class OrganisationRepository : IOrganisationRepository
{
    private readonly DatabaseContext _database;

    public OrganisationRepository(DatabaseContext database)
    {
        _database = database;
    }

    public async Task<List<OrganisationEntity>> GetOrganisations(int? organisationId, List<int> statusIds)
    {
        IQueryable<OrganisationEntity> organisations = Database.Organisation.Include(o => o.Status).OrderByDescending(d => d.Created).AsQueryable();

        if (organisationId != null)
        {
            organisations = organisations.Where(o => o.Id == organisationId);
        }

        if (statusIds != null && statusIds.Count > 0)
        {
            organisations = organisations.Where(o => statusIds.Contains(o.StatusId));
        }

        return await organisations.ToListAsync();
    }

    public async Task<int> GetOrganisationCount()
    {
        return await Database.Organisation.CountAsync();
    }
}

异常

  

System.InvalidOperationException:未关闭连接。该   连接的当前状态是连接。

这里的问题是我调用GetOrganisations()和GetOrganisationCount()是异步的,出于某种原因,(我认为),相同的数据库上下文用于两个调用,(不同的线程)..我认为通过添加选项:

ServiceLifetime.Transient 

它可以解决我的问题,(因为默认是作用域),但没有..

但是,如果我添加使用语句..

public async Task<List<OrganisationEntity>> GetOrganisations(int? organisationId, List<int> statusIds)
{
    var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
    optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
        .UseSqlServer(
            "Data Source=.;Initial Catalog=MYDATABASE;Integrated Security=True;Connect Timeout=30;");

    using (var db = new DatabaseContext(optionsBuilder.Options, null))
    {
        IQueryable<OrganisationEntity> organisations =
            db.Organisation.Include(o => o.Status).OrderByDescending(d => d.Created).AsQueryable();

        if (organisationId != null)
        {
            organisations = organisations.Where(o => o.Id == organisationId);
        }

        if (statusIds != null && statusIds.Count > 0)
        {
            organisations = organisations.Where(o => statusIds.Contains(o.StatusId));
        }

        return await organisations.ToListAsync();
    }
}

public async Task<int> GetOrganisationCount()
{
    var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
    optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
        .UseSqlServer(
            "Data Source=.;Initial Catalog=MYDATABASE;Integrated Security=True;Connect Timeout=30;");

    using (var db = new DatabaseContext(optionsBuilder.Options, null))
    {
        return await db.Organisation.CountAsync();
    }
}

一切正常!

为什么依赖注入不能正常工作?

我完全错了吗?如果我想要瞬态上下文,我是否被迫使用语句来完成我的所有任务?

ServiceLifetime.Transient有问题?

编辑:

Stacktrade,(某些信息被屏蔽)

  

“在   System.Data.ProviderBase.DbConnectionClosedConnecting.TryOpenConnection(的DbConnection   outerConnection,DbConnectionFactory connectionFactory,   TaskCompletionSource 1 retry, DbConnectionOptions userOptions)\r\n
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource
1   重试)\ r \ n at   System.Data.SqlClient.SqlConnection.OpenAsync(的CancellationToken   cancellationToken)\ r \ n ---从前一个位置开始的堆栈跟踪结束   抛出异常的地方--- \ r \ n at   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(个)\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n at   Microsoft.EntityFrameworkCore.Storage.RelationalConnection.d__31.MoveNext(个)\ r \ n ---   从抛出异常的先前位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n at   Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable.AsyncEnumerator.d__9.MoveNext(个)\ r \ n ---   从抛出异常的先前位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n at   Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerExecutionStrategy.d__6 2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable.AsyncEnumerator.<MoveNext>d__8.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Microsoft.EntityFrameworkCore.Query.AsyncQueryMethodProvider.<GetResult>d__16
1.MoveNext(个)\ r \ n ---   从抛出异常的先前位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n at   Microsoft.EntityFrameworkCore.Query.Internal.TaskResultAsyncEnumerable 1.Enumerator.<MoveNext>d__3.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor
1.EnumeratorExceptionInterceptor.d__5.MoveNext(个)\ r \ n ---   从抛出异常的先前位置开始的堆栈跟踪结束   --- \ r \ n在System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\ r \ n \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n at   System.Runtime.CompilerServices.TaskAwaiter 1.GetResult()\r\n at XXXXXXX.Data.Repositories.Organisation.OrganisationRepository.<GetOrganisationCount>d__8.MoveNext() in C:\\Users\\XXXXXXX\\Documents\\Visual Studio 2017\\Projects\\XXXXXXX\\XXXXXXX.Data\\Repositories\\Organisation\\OrganisationRepository.cs:line 65\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult()\ r \ n at   XXXXXXX.Api.Services.Organisation.OrganisationService.d__4.MoveNext()   在C:\ Users \ XXXXXXX \ Documents \ Visual Studio中   2017年\项目\ XXXXXXX \ XXXXXXX.Api \ SERVICES \组织\ OrganisationService.cs:行   31 \ r \ n ---来自先前位置的堆栈跟踪结束异常   被扔了--- \ r \ n at   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(个)\ r \ n
  在   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务   任务)\ r \ n at   System.Runtime.CompilerServices.TaskAwaiter.GetResult()\ r \ n at   XXXXXXX.Api.Controllers.Administration.OrganisationController.d__5.MoveNext()   在C:\ Users \ XXXXXXX \ Documents \ Visual Studio中   2017年\项目\ XXXXXXX \ XXXXXXX.Api \控制器\管理\ OrganisationController.cs:行   51"

2 个答案:

答案 0 :(得分:2)

  

ServiceLifetime.Transient有问题?!

这不是错误。我们知道DbContext不是线程安全的,两个方法调用都使用相同的OrganisationService实例,该实例本身是使用DatabaseContext的相同实例创建的。

Task<IEnumerable<OrganisationModel>> organisations = 
    _organisationService.GetOrganisations(model?.Id, model?.StatusIds);

Task<int> organisationTotalCount = _organisationService.GetOrganisationCount();

这是线程切换环境中实体框架'DbContext的限制。因此,我们使用以下传统方法 -

[HttpPost("GetOrganisations")]
public async Task<IActionResult> GetOrganisations([FromBody] GetOrganisationsModel model)
{
    var organisations = 
        await  _organisationService.GetOrganisations(model?.Id, model?.StatusIds);

    var organisationTotalCount = await _organisationService.GetOrganisationCount();

    return Ok(new OrganisationViewModel
    {
        Organisations = organisations,
        OrganisationTotalCount = organisationTotalCount
    });
}

在存储库中 newing 的缺点是它们变得紧密耦合,你不能像this那样对存储库进行单元测试。如果我必须在特定场景中选择 Task.WhenAll与单元测试 ,我将选择单元测试。

如果您发现自己经常使用Task.WhenAll,可能需要查看可与EF一起使用的Dapper ORM

答案 1 :(得分:1)

还请注意,在Microsoft Dependency Injection中,直到范围结束,才会处置临时服务。

有一种解决方法,就是使用WeakReference作为Transient而不是TService。然后,WeakReference的用户可以负责Dispose。