C#项目中如何实现可供所有服务调用的GetCreator方法?
公共日志查询服务的最佳实现方案
核心思路是把分散的日志查询逻辑抽成独立的、可通过依赖注入复用的公共服务,避免在每个业务服务里重复写日志查询代码,后续要调整日志查询规则(比如加缓存、改日志表结构)只需要改一处,不用逐服务排查修改。
第一步:编写独立的日志查询服务
不要把日志查询逻辑做成静态方法,也不要直接在业务服务里写私有方法,要走“抽象接口+具体实现”的模式,方便单元测试和依赖替换:
- 先定义公共接口,明确对外提供的能力:
public interface ILogQueryService { /// <summary> /// 获取指定实体的创建人 /// </summary> /// <param name="entityName">实体名称,比如SalesOrder</param> /// <param name="entityId">实体主键ID</param> /// <returns>创建人信息</returns> string GetCreator(string entityName, int entityId); /// <summary> /// 获取指定实体的最后编辑人 /// </summary> string GetLastEditor(string entityName, int entityId); }
- 再写接口的具体实现,统一封装所有日志表查询逻辑:
public class LogQueryService : ILogQueryService { private readonly YourProjectDbContext _dbContext; // 通过构造函数注入数据库上下文,不手动实例化依赖 public LogQueryService(YourProjectDbContext dbContext) { _dbContext = dbContext; } public string GetCreator(string entityName, int entityId) { // 统一在这里写日志表查询规则:查对应实体、对应ID的最早创建操作记录 var createLog = _dbContext.OperationLogs .Where(log => log.EntityName == entityName && log.EntityId == entityId && log.OperationType == OperationType.Create) .OrderBy(log => log.OperationTime) .FirstOrDefault(); // 做空值兜底,日志缺记录时不抛业务异常 return createLog?.OperatorName ?? string.Empty; } public string GetLastEditor(string entityName, int entityId) { // 最后编辑人直接查时间最新的操作记录即可 var latestLog = _dbContext.OperationLogs .Where(log => log.EntityName == entityName && log.EntityId == entityId) .OrderByDescending(log => log.OperationTime) .FirstOrDefault(); return latestLog?.OperatorName ?? string.Empty; } }
第二步:通过依赖注入在业务服务中引用
严格遵循DI容器的注册规则,不要手动new公共服务实例,保证生命周期匹配:
- 第一步在应用启动时(Program.cs/Startup.cs)把公共服务注册到DI容器,生命周期和DbContext保持一致(Scoped):
// 和DbContext的注册生命周期保持一致,避免跨作用域访问DbContext的线程安全问题 builder.Services.AddScoped<ILogQueryService, LogQueryService>();
- 修改原来的业务服务,通过构造函数注入
ILogQueryService替换原来的私有GetCreator方法:
public class SalesOrderService { private readonly YourProjectDbContext _dbContext; private readonly ILogQueryService _logQueryService; // 把所有依赖都通过构造函数传入,不硬编码依赖实例 public SalesOrderService(YourProjectDbContext dbContext, ILogQueryService logQueryService) { _dbContext = dbContext; _logQueryService = logQueryService; } public SalesOrderViewModel GetSalesOrder(int salesOrderId) { var salesOrder = _dbContext.SalesOrder .Where(o => o.SalesOrderId == salesOrderId) .FirstOrDefault(); if (salesOrder == null) return null; // 直接调用公共方法,不需要自己实现日志查询逻辑 return new SalesOrderViewModel { SalesOrderId = salesOrder.SalesOrderId, // 其他业务字段映射 CreatedBy = _logQueryService.GetCreator("SalesOrder", salesOrderId) }; } }
实操避坑与优化建议
- 生命周期匹配:因为
LogQueryService依赖了Scoped生命周期的DbContext,千万不要把它注册成Singleton,否则会出现DbContext被多线程复用导致的并发异常 - 减少硬编码错误:如果手动传实体名容易出现字符串拼写错误(比如把"SalesOrder"写成"SalesOeder"查不到数据),可以把方法改造成泛型版本,通过反射自动获取实体名:
// 接口里的泛型方法示例 string GetCreator<TEntity>(int entityId) where TEntity : class; // 实现内部自动取实体名,不用手动传字符串 public string GetCreator<TEntity>(int entityId) where TEntity : class { var entityName = typeof(TEntity).Name; // 后续和之前的查询逻辑一致 } // 调用时可以直接写:_logQueryService.GetCreator<SalesOrder>(salesOrderId) - 批量查询优化:如果业务场景是列表页批量获取N条数据的创建人,不要循环单条调用
GetCreator,可以在LogQueryService里补充批量查询的重载,一次性从日志表查出所有对应ID的创建人,减少数据库往返次数 - 异常兜底:所有日志查询逻辑一定要加空值、异常的兜底处理,不能因为日志表的临时数据问题,导致核心业务接口不可用。
内容的提问来源于stack exchange,提问作者steve_c123
相关产品推荐
相关产品推荐

