CRUD操作校验逻辑层级选型:控制器还是仓储层?
CRUD操作前校验逻辑的层级选择最佳实践
1. 校验逻辑的分层原则
不同类型的校验,归属层级要区分开:
- 请求合法性校验(比如参数格式、必填项检查):适合放在控制器(或者用模型验证、请求过滤器),毕竟控制器是HTTP请求的入口,先把明显无效的请求挡掉,避免后续层做无用功。
- 业务规则校验(比如只有创建者能删除、已审批数据不能删这类业务相关的规则):不建议放在控制器或仓储层,最佳位置是业务逻辑层(Service层)。控制器的职责应该是接收请求、调用业务逻辑、返回响应,不该堆业务规则;仓储层只负责数据持久化操作,掺杂业务规则会让它职责混乱,也不利于复用。
2. 关于传递额外参数到仓储层的合理性
如果直接把校验逻辑丢去仓储层,还要传userId这类参数,这种做法不合理——仓储层要和业务规则、当前用户解耦,只专注于数据的增删改查。但如果是通过业务层来处理,业务层接收userId(从控制器传递过来)就完全合理,因为业务层本身就是封装业务规则的地方,需要这些参数来判断操作是否符合业务要求。
3. 重构示例参考
可以新增一个业务层类来封装删除逻辑和校验:
public class DailyReportService { private readonly IUnitOfWork _unitOfWork; public DailyReportService(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public (bool Success, string Message) RemoveReport(int reportId, int currentUserId) { var report = _unitOfWork.DailyReportPipingRepository.Find(reportId); if (report == null) return (false, "Invalid Data"); if (report.fldUserId != currentUserId) return (false, "Incorrect user"); if (report.fldApproved || report.fldFinalized) return (false, "Approved or finalized before"); try { _unitOfWork.DailyReportPipingRepository.Remove(report); var affectedRows = _unitOfWork.SaveChangesByUserId(currentUserId); if (affectedRows == 0) return (false, "unable to save"); return (true, string.Empty); } catch (Exception ex) { return (false, ex.Message); } } }
然后控制器就能简化成只做请求转发和响应:
public IActionResult OnPostRemove(int id) { var currentUserId = HttpContext.Session.GetActiveUser_UserId(); var result = _dailyReportService.RemoveReport(id, currentUserId); if (!result.Success) return new JsonResult(new { status = false, message = result.Message }); return new JsonResult(true); }
4. 总结
- 控制器只做“中转站”,剥离业务逻辑后更简洁,符合单一职责原则。
- 仓储层保持纯粹的数据操作,不掺和业务规则,避免职责边界模糊。
- 业务层接收userId等业务参数是合理的,这是它处理业务规则的必要输入。
内容的提问来源于stack exchange,提问作者Hadi Mazareei
相关产品推荐
相关产品推荐

