如何复用LINQ查询处理同库中不同Serilog日志表?
消除Serilog日志表重复LINQ查询代码的方案
问题背景
数据库包含多个Schema完全一致的Serilog专属日志表(如Weather_StructureLog、Store_StructureLog),使用LINQPad监控这些表的近期日志时,当前代码需通过switch语句针对不同表编写重复的LINQ查询逻辑,新增表时必须复制粘贴代码块,维护成本高。
已实现动态获取TypedDataContext的方法:
TypedDataContext SetDynamicConnection(string databaseName) { new { ServerName, databaseName }.Dump("Connection Info"); var dynamicConnection = new SqlConnection($"Data Source={ServerName};Integrated Security=SSPI;Initial Catalog={databaseName};app=LinqPad"); return new TypedDataContext(dynamicConnection); }
当前存在重复逻辑的代码:
var baseQueryable = new List<SerilogSchemaTable>(); switch (tableName) { case "Weather_StructureLog": { baseQueryable = dbContext.Weather_StructureLog .Where(ln => ln.TimeStamp > DateTime.Now.AddMonths(-1)) .OrderByDescending(ln => ln.Id) .Select(ln => new SerilogSchemaTable() { Exception = ln.Exception, Id = ln.Id, Level = ln.Level, Message = ln.Message, MessageTemplate = ln.MessageTemplate, Properties = ln.Properties, TimeStamp = ln.TimeStamp }) .ToList<SerilogSchemaTable>(); break; } case "Store_StructureLog": { baseQueryable = dbContext.Store_StructureLog .Where(ln => ln.TimeStamp > DateTime.Now.AddMonths(-1)) .OrderByDescending(ln => ln.Id) .Select(ln => new SerilogSchemaTable() { Exception = ln.Exception, Id = ln.Id, Level = ln.Level, Message = ln.Message, MessageTemplate = ln.MessageTemplate, Properties = ln.Properties, TimeStamp = ln.TimeStamp }) .ToList<SerilogSchemaTable>(); break; } }
解决方案
通过接口抽象+反射缓存的方式统一查询逻辑,彻底消除重复代码:
1. 定义通用日志接口
创建包含所有日志表共同属性的接口,让所有日志实体类实现该接口(LINQPad中可通过编辑实体类的部分类实现):
public interface ISerilogLog { int Id { get; } DateTime TimeStamp { get; } string Level { get; } string Message { get; } string MessageTemplate { get; } string Exception { get; } string Properties { get; } }
2. 编写通用日志查询方法
实现通用方法,通过反射获取指定表的查询对象,执行统一LINQ逻辑,并加入缓存避免重复反射开销:
// 缓存表属性信息,避免重复反射 private static readonly Dictionary<string, PropertyInfo> _tablePropertyCache = new Dictionary<string, PropertyInfo>(); List<SerilogSchemaTable> GetLogs(TypedDataContext dbContext, string tableName) { // 从缓存获取或反射获取表对应的属性 if (!_tablePropertyCache.TryGetValue(tableName, out var tableProperty)) { tableProperty = dbContext.GetType().GetProperty(tableName); if (tableProperty == null) throw new ArgumentException($"日志表 {tableName} 未在DataContext中找到"); _tablePropertyCache[tableName] = tableProperty; } // 获取表的IQueryable实例 var queryable = tableProperty.GetValue(dbContext) as IQueryable; if (queryable == null) throw new InvalidOperationException($"属性 {tableName} 不是有效的IQueryable类型"); // 转换为通用接口类型并执行统一查询 return queryable.Cast<ISerilogLog>() .Where(ln => ln.TimeStamp > DateTime.Now.AddMonths(-1)) .OrderByDescending(ln => ln.Id) .Select(ln => new SerilogSchemaTable() { Exception = ln.Exception, Id = ln.Id, Level = ln.Level, Message = ln.Message, MessageTemplate = ln.MessageTemplate, Properties = ln.Properties, TimeStamp = ln.TimeStamp }) .ToList(); }
3. 替换原有重复代码
原来的switch语句可简化为一行调用:
var baseQueryable = GetLogs(dbContext, tableName);
后续维护
新增日志表时,只需:
- 确保对应实体类实现
ISerilogLog接口 - 直接传入新表名调用
GetLogs方法即可,无需修改查询逻辑代码
内容的提问来源于stack exchange,提问作者MADCookie
相关产品推荐
相关产品推荐

