【问题标题】:Entity Framework Core - Not thread safe - ServiceLifetime.Transient实体框架核心 - 不是线程安全的 - ServiceLifetime.Transient
【发布时间】:2017-12-23 18:59:37
【问题描述】:

即使我指定了,我的数据库上下文似乎也不是线程安全的、瞬态的:

ServiceLifetime.Transient 

在数据库的启动配置中。

Startup.cs

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

API

[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 

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

但是,如果我添加 using 语句..

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();
    }
}

一切正常!

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

我完全错了吗?如果我想要瞬态上下文,我是否必须对所有任务使用 using 语句?

ServiceLifetime.Transient 有问题?!

编辑:

Stacktrade,(一些信息被屏蔽)

“在 System.Data.ProviderBase.DbConnectionClosedConnecting.TryOpenConnection(DbConnection 外部连接,DbConnectionFactory 连接工厂, 任务完成源1 retry, DbConnectionOptions userOptions)\r\n
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource
1 重试)\r\n 在 System.Data.SqlClient.SqlConnection.OpenAsync(CancellationToken cancelToken)\r\n--- 来自先前位置的堆栈跟踪结束 抛出异常的地方 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 Microsoft.EntityFrameworkCore.Storage.RelationalConnection.d__31.MoveNext()\r\n--- 从先前抛出异常的位置结束堆栈跟踪 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable.AsyncEnumerator.d__9.MoveNext()\r\n--- 从先前抛出异常的位置结束堆栈跟踪 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerExecutionStrategy.d__62.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
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 Microsoft.EntityFrameworkCore.Query.Internal.TaskResultAsyncEnumerable1.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
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 System.Runtime.CompilerServices.TaskAwaiter1.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\Projects\XXXXXXX\XXXXXXX.Api\Services\Organisation\OrganisationService.cs:line 31\r\n--- 堆栈跟踪从上一个异常位置结束 被抛出 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at XXXXXXX.Api.Controllers.Administration.OrganisationController.d__5.MoveNext() 在 C:\Users\XXXXXXX\Documents\Visual Studio 2017\Projects\XXXXXXX\XXXXXXX.Api\Controllers\Administration\OrganisationController.cs:line 51"

【问题讨论】:

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


    【解决方案1】:

    ServiceLifetime.Transient 有问题?!

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

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

    这是Entity Framework的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,你可能想看看Dapper ORM,它可以和EF一起使用。

    【讨论】:

    • 啊哈.. 好吧.. 其实我一开始用 await 没有WhenAll,但如果我没记错的话,没有WhenAll,GetOrganisationCount() 只会在 getOrganisations() 完成后执行,所以。 . 同步调用? (澄清一下)谢谢
    • 如果使用await,则只有在GetOrganisations完成后才会调用GetOrganisationCount
    【解决方案2】:

    另请注意,在 Microsoft 依赖注入中,临时服务在范围结束之前不会被释放。

    有一个干净的方法是使用 Wea​​kReference 作为 Transient 而不是 TService。然后,WeakReference 的用户可以对 Dispose 负责。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-29
      • 2011-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多