ASP.NET MVC(非.NET Core)实现独角兽超龄时发邮件/日志的最优方案
嘿,这个需求我之前在传统ASP.NET MVC(非.NET Core)项目里折腾过,给你几个不同场景下的最优实现思路,你可以根据业务需求挑:
方案1:定时任务(最通用的无侵入方式)
如果不需要实时触发,只是定期检查哪些独角兽达到了最大年龄,用定时任务是最稳妥的选择。推荐用Hangfire这个库,它在.NET Framework里的集成很丝滑,比Windows任务计划更灵活。
步骤大概是这样:
- 先安装NuGet包:
Install-Package Hangfire - 在
Global.asax的Application_Start里配置Hangfire的启动(比如连接你的数据库存储任务信息) - 写一个后台任务类,负责扫描和处理:
public class UnicornAgeCheckerJob { private readonly YourDbContext _dbContext; private readonly IEmailService _emailService; private readonly ILogger _logger; // 用依赖注入容器(比如Autofac)注入需要的服务 public UnicornAgeCheckerJob(YourDbContext dbContext, IEmailService emailService, ILogger logger) { _dbContext = dbContext; _emailService = emailService; _logger = logger; } public void CheckAndNotify() { var now = DateTime.UtcNow; // 筛选出已达最大年龄且未通知过的独角兽 var matureUnicorns = _dbContext.Unicorns .Where(u => now >= u.DateOfBirth.Add(u.MaximalAge)) .Where(u => !u.HasBeenNotified) .ToList(); foreach (var unicorn in matureUnicorns) { // 执行发送邮件逻辑 _emailService.SendAgeAlert(unicorn.Id); // 记录日志 _logger.Info($"独角兽ID {unicorn.Id} 已达到最大年龄,已发送通知"); // 标记为已处理,避免重复执行 unicorn.HasBeenNotified = true; } _dbContext.SaveChanges(); } }
- 最后设置定时触发,比如每天凌晨1点跑一次:
RecurringJob.AddOrUpdate<UnicornAgeCheckerJob>(job => job.CheckAndNotify(), Cron.Daily(1));
这个方案的好处是完全不侵入你的实体和业务逻辑,维护起来很方便,适合大多数非实时的场景。
方案2:业务逻辑/EF拦截(实时触发场景)
如果需要在独角兽的年龄刚达标瞬间就触发通知,那可以在业务逻辑层或者EF的变更拦截里处理。
方式A:业务服务层触发
先给Unicorn实体加个辅助方法判断是否达标:
public class Unicorn { public int Id { get; set; } public DateTime DateOfBirth { get; set; } public TimeSpan MaximalAge { get; set; } public bool HasBeenNotified { get; set; } // 新增标记字段,避免重复通知 // 判断是否已达最大年龄 public bool IsAtMaxAge() { return DateTime.UtcNow >= DateOfBirth.Add(MaximalAge); } }
然后在业务服务里处理更新或新增逻辑时检查:
public class UnicornService { private readonly YourDbContext _dbContext; private readonly IEmailService _emailService; private readonly ILogger _logger; public UnicornService(YourDbContext dbContext, IEmailService emailService, ILogger logger) { _dbContext = dbContext; _emailService = emailService; _logger = logger; } public void UpdateUnicornDetails(Unicorn updatedUnicorn) { var existingUnicorn = _dbContext.Unicorns.Find(updatedUnicorn.Id); existingUnicorn.DateOfBirth = updatedUnicorn.DateOfBirth; existingUnicorn.MaximalAge = updatedUnicorn.MaximalAge; // 检查是否达标且未通知 if (existingUnicorn.IsAtMaxAge() && !existingUnicorn.HasBeenNotified) { _emailService.SendAgeAlert(existingUnicorn.Id); _logger.Info($"独角兽ID {existingUnicorn.Id} 已达最大年龄,实时发送通知"); existingUnicorn.HasBeenNotified = true; } _dbContext.SaveChanges(); } }
方式B:重写EF的SaveChanges拦截
如果不想在每个业务方法里写检查逻辑,可以重写DbContext的SaveChanges方法,统一拦截所有Unicorn的变更:
public class YourDbContext : DbContext { private readonly IEmailService _emailService; private readonly ILogger _logger; public YourDbContext(IEmailService emailService, ILogger logger) { _emailService = emailService; _logger = logger; } public DbSet<Unicorn> Unicorns { get; set; } public override int SaveChanges() { var now = DateTime.UtcNow; // 筛选出新增或修改的、已达最大年龄且未通知的独角兽 var unicornsToNotify = ChangeTracker.Entries<Unicorn>() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified) .Select(e => e.Entity) .Where(u => now >= u.DateOfBirth.Add(u.MaximalAge) && !u.HasBeenNotified) .ToList(); foreach (var unicorn in unicornsToNotify) { _emailService.SendAgeAlert(unicorn.Id); _logger.Info($"独角兽ID {unicorn.Id} 已达最大年龄,通过EF拦截触发通知"); unicorn.HasBeenNotified = true; } return base.SaveChanges(); } }
这个方式适合需要全局统一处理的场景,但要注意不要在SaveChanges里做太耗时的操作,避免影响接口性能。
方案3:数据库触发器(不推荐,除非特殊场景)
如果你的数据库是SQL Server,也可以写触发器来检查,但这种方式耦合性极高,而且.NET的邮件服务没法直接在数据库里调用,通常只能先写日志表,再用定时任务处理日志。除非有跨多个应用共享数据的特殊需求,否则不建议用这种方式。
总结
- 非实时需求:**定时任务(Hangfire)**是最优解,无侵入、易维护;
- 实时需求:业务层触发或EF SaveChanges拦截更合适;
- 数据库触发器尽量少用,耦合性太强。
内容的提问来源于stack exchange,提问作者Ondrej Krsicka
相关产品推荐
相关产品推荐

