【问题标题】:.NET 5 EF Core SaveChangesAsync hangs on errors.NET 5 EF Core SaveChangesAsync 因错误而挂起
【发布时间】:2021-03-09 12:21:23
【问题描述】:

尽管这个问题有很多结果,但没有一个真正给我一个明确的答案。

每次我尝试通过 AddAsync 和 SaveChangesAsync 方法插入错误数据(例如重复的主键)时,都会看到以下日志: 执行 DbCommand 失败(15 毫秒)

我还在 SQL PROFILER 上看到了错误,很明显这是主键冲突。 在这些情况下,SaveChangesAsync 不会引发任何错误!它只是无限期地挂起,导致内存泄漏,最终应用程序崩溃。 我尝试用 try catch 包装 savechanges 无济于事,甚至添加了一个取消令牌以在 2 秒后自动取消,即使这样也没有停止挂起。结果始终是一个永不结束的 HTTP 请求,它永远不会向客户端返回响应。 *我也尝试在没有配置等待的情况下运行 savechangesasync,结果是一样的。

上下文和存储库的 DI:

        services.AddDbContext<SalesDBContext>(o => 
            o.UseSqlServer(appSettings.ConnectionString)
            .EnableDetailedErrors(true)
            .EnableSensitiveDataLogging()
        , ServiceLifetime.Scoped);

services.AddScoped<IRepository, EfRepository>();

控制器方法:

[HttpPost]
public async Task<IActionResult> Post([FromBody] SomeRequest request)
{
    if (ModelState.IsValid)
    {
        var data = await _service.AddAsync(request);
        return Ok(data);
    }
    return BadRequest(ModelState.Values);
}

addasync 是调用者:

public class EfRepository : IRepository
{
    private readonly SalesDBContext _dbContext;

    public EfRepository(SalesDBContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<int> AddAsync<T>(T entity) where T : Entity
    {
        _dbContext.Set<T>().Add(entity);
        return await _dbContext.SaveChangesAsync();
    }
}

我通过检查没有违规来解决问题,但这并不能解决根本问题,即目前我无法记录这些错误,如果在某处再次发生这种情况,它将破坏网站。

根据要求:整个上下文类:

    public class SalesDBContext : DbContext
{
    public DbSet<Entity> Entities { get; set; }


    public SalesDBContext(DbContextOptions<SalesDBContext> options) : base(options)
    {

    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.ApplyConfigurationsFromAssembly(System.Reflection.Assembly.GetExecutingAssembly());

        foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
        {
            relationship.DeleteBehavior = DeleteBehavior.Restrict;
        }
    }
    //This is the savechanges being called.
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        int result = await base.SaveChangesAsync(cancellationToken); 
        //Hangs here on primary key conflict error
        return result;
    }
}

*上下文的生命周期可能与它无关,我在每个生命周期都重现了这个问题。

这里是调用栈:

*更多信息: 使用非 Async 方法保存时也会发生挂起。 当在 savechanges 中手动抛出错误时,它会正确冒泡。 在另一个共享相同 DLL 的项目中,SaveChanges 会抛出一个正确冒泡的错误,在 Async 方法中也是如此。

【问题讨论】:

  • 这可能是一个死锁 - 看看这个:stackoverflow.com/questions/25588659/…
  • 我认为在 ASP.NET Core 中甚至不再需要 ConfigureAwait,所以我认为这不是问题吗? stackoverflow.com/questions/42053135/…
  • 我也尝试在没有配置等待的情况下保存,但结果是一样的。我不明白为什么会发生死锁。我要做的就是捕获密钥冲突异常
  • 你可以尝试使用 SaveChanges not async 吗?
  • 你在哪里打电话。我认为我们也应该检查调用动作和依赖注入注册位置以防万一。你也可以分享调用动作吗?我还建议为您尝试实现错误的存储库操作编写测试。如果您可以通过从测试中调用它来获取错误,则应检查调用存储库的控制器/操作。

标签: c# entity-framework .net-5


【解决方案1】:

我终于找到问题了!!我坐下来开始一行一行地删除代码,直到找到罪魁祸首!

.UseSerilog((hostingContext, loggerConfiguration) => {
    loggerConfiguration
    .ReadFrom.Configuration(hostingContext.Configuration)
    .Enrich.FromLogContext()
    .Enrich.WithProperty("ApplicationName", typeof(Program).Assembly.GetName().Name)
    .Enrich.WithProperty("Environment", hostingContext.HostingEnvironment);
#if DEBUG
    loggerConfiguration.Enrich.WithProperty("DebuggerAttached", Debugger.IsAttached);
#endif
});

Serilog 一直以来都是艰难的时期。没有错误指出日志记录有问题,因为所有日志都有效。所以我只是一个接一个地删除了特性,直到当我删除这个时,挂起停止并且异常开始冒泡。

感谢每一位评论并试图帮助我的人,非常感谢!

我最终放弃了 Serilog,直接将 Seq 与 ILogging 扩展一起使用,现在一切正常!

【讨论】:

  • 很高兴您能够找到前进的道路。是否有任何其他线索表明 Serilog 参与其中,例如调用堆栈上是否有任何日志记录代码,或者在基本上下文的SaveChangesAsync() 中等待(以某种方式)?
  • @NicholasBlumhardt 没有!没有抛出任何错误,也没有任何线索表明 Serilog 是罪魁祸首。另外,我将 Serilog 设置更改为仅登录到控制台而不是 Seq,但问题仍然存在。所以它绝对是 Serilog 而不是 Sinks 之一。
  • 它不是 Serilog,也可能是 EF 正在假设哪个日志记录提供程序在其 ILoggers 后面,或者一些副作用(例如程序集加载?) - TBH 它没有对我来说这听起来不像是 Serilog 问题,但似乎也很难确定:-)
  • 我遇到了同样的问题,这是由于github.com/RehanSaeed/Serilog.Exceptions/issues/100
  • 是的,Serilog.Exception 是罪魁祸首!!!非常感谢。
【解决方案2】:

在 cmets 中,您说您将数据库上下文注入为瞬态。我建议将其作为作用域注入,这将确保您在整个数据流中对每个请求使用“相同”的 dbcontext。

使用不同的 dbcontext,就像使用瞬态生命周期一样,在不同的消费者中对您的数据访问流的同一请求可能导致奇怪的行为,像这样

【讨论】:

  • 我知道行为差异,我在所有生命周期都重现了错误。
  • 我明白了,那么我建议检查未选中的,从外观上看,代码似乎还可以
  • 我看不出这种说法有任何依据,除非您提供更多背景信息。事实上,我会反对。 Entity Framework 不支持从同一连接发出的多个并发请求。这意味着如果您将上下文注册为Scoped,并在不等待的情况下进行异步调用,并且在单独的实体(也依赖于同一上下文)中进行异步调用而不等待,您将遇到并发错误。
  • 我不是说不等待,我说的是依赖。
  • 我不是在谈论不等待,我在谈论依赖。如果 A 依赖于 B 和 DbContext,而 B 依赖于 db 上下文,如果您添加为作用域,则可以确定,在同一个请求中,到位的 dbcontext 是相同的,所以如果 B 做了一些开始一些跟踪的事情例如,然后 A 将使用该跟踪。如果添加为瞬态,将有 2 个不同的实例不共享一个公共状态。根据您的需要,您必须将其添加为瞬态或范围。
猜你喜欢
  • 1970-01-01
  • 2019-05-02
  • 1970-01-01
  • 2021-05-08
  • 1970-01-01
  • 2021-11-09
  • 2023-03-31
  • 1970-01-01
  • 2013-03-27
相关资源
最近更新 更多