EF Core结合PostgreSQL按时区过滤:现有方案是否正确及优化方式
问题解答
现有Raw SQL方案的问题
你的方案功能上能运行,但存在严重的SQL注入风险——直接将timezoneName和dateString拼接进SQL字符串,若传入恶意内容(比如timezoneName设为'UTC'; DROP TABLE SN_ATS_Event; --),会直接执行破坏数据库的操作,这是绝对不安全的,因此该方案不正确。
无需Raw SQL的更优实现方式
方式1:利用Npgsql EF Core扩展的时区转换方法
Npgsql.EntityFrameworkCore.PostgreSQL提供了对PostgreSQL AT TIME ZONE语法的LINQ支持,只需引用Npgsql.EntityFrameworkCore.PostgreSQL命名空间,即可用类型安全的方式实现时区转换和日期过滤,同时自动参数化查询避免注入:
using Npgsql.EntityFrameworkCore.PostgreSQL; public async Task<(IReadOnlyList<AdminEventSummaryDto>, int)> GetEventListForDay(Paginator paginator, string timezoneName, DateTime date, Guid userId) { var targetDate = date.Date; var query = _context.Events // 先将UTC时间转为带时区的时间,再转目标时区,最后取日期对比 .Where(e => EF.Functions.AtTimeZone(EF.Functions.AtTimeZone(e.EventStartDateTimeUtc, "UTC"), timezoneName).Date == targetDate) .Where(e => e.InterViewers.Any(a => a.InterviewerId == userId)); var totalRecordCount = await query.CountAsync(); var list = await query.Select(e => new AdminEventSummaryDto() { // 填充DTO字段 }) .Skip((paginator.PageNumber - 1) * paginator.PageSize) .Take(paginator.PageSize) .AsSplitQuery() .ToListAsync(); return (list, totalRecordCount); }
方式2:在应用端计算UTC时间范围(更高效)
若EventStartDateTimeUtc字段有索引,推荐这种方式:先将目标日期的起止时间转换为UTC,直接用UTC范围过滤,避免数据库端做时区转换计算,性能更好:
public async Task<(IReadOnlyList<AdminEventSummaryDto>, int)> GetEventListForDay(Paginator paginator, string timezoneName, DateTime date, Guid userId) { // 获取目标时区 var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timezoneName); // 目标日期的本地起始时间 var startOfDayLocal = date.Date; // 转UTC起始时间 var startUtc = TimeZoneInfo.ConvertTimeToUtc(startOfDayLocal, timeZone); // 目标日期的本地结束时间(次日0点) var endOfDayLocal = startOfDayLocal.AddDays(1); // 转UTC结束时间 var endUtc = TimeZoneInfo.ConvertTimeToUtc(endOfDayLocal, timeZone); var query = _context.Events .Where(e => e.EventStartDateTimeUtc >= startUtc && e.EventStartDateTimeUtc < endUtc) .Where(e => e.InterViewers.Any(a => a.InterviewerId == userId)); var totalRecordCount = await query.CountAsync(); var list = await query.Select(e => new AdminEventSummaryDto() { // 填充DTO字段 }) .Skip((paginator.PageNumber - 1) * paginator.PageSize) .Take(paginator.PageSize) .AsSplitQuery() .ToListAsync(); return (list, totalRecordCount); }
注意:使用方式2时,要确保timezoneName是有效的Windows或IANA时区ID(比如"Asia/Shanghai"或"China Standard Time"),若前端传入的是其他格式,需要先做转换。
内容的提问来源于stack exchange,提问作者thanzeel
相关产品推荐
相关产品推荐

