You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6站点启动后执行数据库操作的最佳方式(解决503错误)

.NET 6 启动时稳定执行数据库清理操作避免503错误的解决方案

问题描述

我在Program.cs中编写了中间件,期望站点启动时删除数据库中的指定信息。该逻辑在本地运行正常,可成功执行查询并删除数据,但发布至服务器后数分钟会出现503错误;移除该中间件后,503错误不再出现,站点恢复正常运行。更换服务器后问题仍存在。当前实现代码如下:

IHostApplicationLifetime lifetime = app.Lifetime;

static async void AddLogs(IApplicationBuilder app){

using (var serviceScope = app.ApplicationServices.CreateScope())      
{
    var context = serviceScope.ServiceProvider.GetService<DBContext>();
    
    DateTime dateNow = DateTime.ParseExact(DateConvertor.ToShamsi(), "yyyy/MM/dd", null);

    bool ExistRememberNow = context.HistoryReserves.AsEnumerable().Any(h => DateTime.ParseExact(h.RemmberDate.ToString(), "yyyy/MM/dd", null) == dateNow);

    if (ExistRememberNow)
    {
       var ExistHistoryDateNow = context.HistoryReserves.Include(u => u.User).ThenInclude(u => u.UserDetail).AsEnumerable().Where(h => DateTime.ParseExact(h.RemmberDate, "yyyy/MM/dd", null) == dateNow).ToList();
        foreach (var user in ExistHistoryDateNow)
        {
           string message = "Sen Text Message";
           SmsSender.VerifySend(user.User.Mobile, message);
        }

        context.HistoryReserves.RemoveRange(ExistHistoryDateNow);
        context.SaveChanges();
    }

    string timeNow = DateConvertor.GetShamsiTime();
    var reserveUsers = context.ReserveTimes
                              .Include(r => r.ReserveDate)
                              .AsEnumerable()
                              .Where(r => DateTime.ParseExact(r.ReserveDate.DateReserve, "yyyy/MM/dd", null) == dateNow
                                          && TimeSpan.Parse(r.EndTimeReserve, CultureInfo.InvariantCulture) < TimeSpan.Parse(timeNow, CultureInfo.InvariantCulture))
                              .FirstOrDefault();
    if (reserveUsers != null)
    {
       var deleteReserveTime = context.ReserveTimes.Find(reserveUsers.Id);
       context.ReserveTimes.Remove(deleteReserveTime);
       context.SaveChanges();
   }

   var pastDate = context.ReserveDates.AsEnumerable().Where(r => DateTime.ParseExact(r.DateReserve, "yyyy/MM/dd", null) < DateTime.Now).ToList();

   if (pastDate.Count() != 0)
   {
        context.RemoveRange(pastDate);
        context.SaveChanges();
   }

   lifetime.ApplicationStarted.Register(() => AddLogs(app));

   app.UseHttpsRedirection();
   app.UseStaticFiles();
}

问题根源分析

  • async void 异步风险:async void在非UI场景下无法捕获未处理异常,一旦出现异常可能直接导致进程崩溃,触发服务器503错误。
  • 递归注册启动事件:在AddLogs内部重复注册ApplicationStarted事件,导致清理逻辑被多次执行,消耗大量服务器资源。
  • 中间件管道混乱:在启动任务中调用UseHttpsRedirection、UseStaticFiles等中间件配置,破坏了中间件管道的正常执行顺序。
  • 低效EF查询:AsEnumerable()将全表数据拉取到内存后过滤,数据量大时会占用大量内存和CPU,导致服务器资源耗尽。
  • 多次数据库提交:多次调用SaveChanges()增加数据库连接开销,加重数据库负载。

解决方案步骤

1. 替换async void为async Task

异步方法返回Task而非void,确保异常可以被捕获和处理,避免进程崩溃。

2. 分离启动清理任务与中间件

将数据库清理逻辑从中间件中剥离,作为独立异步方法,通过IHostApplicationLifetime的ApplicationStarted事件注册,仅执行一次。

3. 优化EF Core查询性能

  • 使用EF Core异步扩展方法(如ToListAsync、FirstOrDefaultAsync)避免阻塞线程。
  • 移除不必要的AsEnumerable(),让过滤逻辑在数据库层面执行,减少内存占用。

4. 合并数据库提交

将所有修改操作合并后一次性调用SaveChangesAsync(),减少数据库交互次数,降低负载。

5. 恢复中间件管道正常配置

中间件配置应放在Program.cs的主管道中,启动任务不应该修改管道结构。

修改后的代码示例

独立的数据库清理方法

using System.Globalization;
using Microsoft.EntityFrameworkCore;

static async Task CleanupDatabaseAsync(IServiceProvider services)
{
    using var scope = services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<DBContext>();
    var dateNow = DateTime.ParseExact(DateConvertor.ToShamsi(), "yyyy/MM/dd", null);
    bool hasChanges = false;

    // 处理HistoryReserves:发送短信并删除记录
    var historyRecords = await context.HistoryReserves
        .Include(u => u.User)
        .ThenInclude(u => u.UserDetail)
        .Where(h => DateTime.ParseExact(h.RemmberDate.ToString(), "yyyy/MM/dd", null) == dateNow)
        .ToListAsync();

    if (historyRecords.Any())
    {
        foreach (var record in historyRecords)
        {
            string message = "发送文本消息";
            SmsSender.VerifySend(record.User.Mobile, message);
        }
        context.HistoryReserves.RemoveRange(historyRecords);
        hasChanges = true;
    }

    // 处理ReserveTimes:删除过期预约时段
    string timeNow = DateConvertor.GetShamsiTime();
    var expiredReserve = await context.ReserveTimes
        .Include(r => r.ReserveDate)
        .Where(r => DateTime.ParseExact(r.ReserveDate.DateReserve, "yyyy/MM/dd", null) == dateNow
                    && TimeSpan.Parse(r.EndTimeReserve, CultureInfo.InvariantCulture) < TimeSpan.Parse(timeNow, CultureInfo.InvariantCulture))
        .FirstOrDefaultAsync();

    if (expiredReserve != null)
    {
        context.ReserveTimes.Remove(expiredReserve);
        hasChanges = true;
    }

    // 处理ReserveDates:删除过期预约日期
    var pastDates = await context.ReserveDates
        .Where(r => DateTime.ParseExact(r.DateReserve, "yyyy/MM/dd", null) < DateTime.Now)
        .ToListAsync();

    if (pastDates.Any())
    {
        context.RemoveRange(pastDates);
        hasChanges = true;
    }

    // 一次性提交所有更改
    if (hasChanges)
    {
        await context.SaveChangesAsync();
    }
}

Program.cs中注册启动任务

var builder = WebApplication.CreateBuilder(args);

// 注册数据库上下文等服务
builder.Services.AddDbContext<DBContext>(options => 
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

// 正常配置中间件管道
app.UseHttpsRedirection();
app.UseStaticFiles();
// 其他中间件(如路由、授权等)...

// 注册启动时的清理任务
var lifetime = app.Lifetime;
lifetime.ApplicationStarted.Register(async () =>
{
    try
    {
        await CleanupDatabaseAsync(app.Services);
    }
    catch (Exception ex)
    {
        // 记录异常日志,避免未处理异常导致进程崩溃
        Console.WriteLine($"启动清理任务失败: {ex.Message}");
        // 可根据需要添加日志框架记录
    }
});

app.Run();

额外优化建议

  • 日期存储优化:如果数据库中日期以字符串存储,建议改为DateTime类型,避免频繁字符串解析开销;若无法修改,可使用EF Core的EF.Functions或数据库特定函数在数据库层面完成日期比较。
  • 短信发送异步化:若SmsSender.VerifySend是同步方法,建议改为异步版本,避免阻塞清理任务线程。
  • 日志记录:在清理任务中添加详细日志,便于排查执行过程中的问题。

内容的提问来源于stack exchange,提问作者RoyalDesigner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:07:06