在Entity Framework 6中如何通过DbDataReader获取查询对应的表名?
问题根源
你拿不到正确表名的核心原因是找错了属性:
GetSchemaTable()返回的DataTable是描述查询结果列元数据的结构表,它自身的TableName属性默认就是固定值SchemaTable,和你查询的业务表没有任何关联。- 正确的表名存储在这个元数据表的每行数据的
BaseTableName列里:元数据表的每一行对应查询结果的一列,BaseTableName存储的就是该列所属的底层业务表名。
快速修复方案(无需修改拦截器逻辑)
如果你只需要拿到当前要读取的DateTime列所属的表名,直接调整GetDateTime方法里的取值逻辑即可:
public override DateTime GetDateTime(int ordinal) { var schemaTable = base.GetSchemaTable(); // 拿到当前列对应的元数据行 var columnMetadata = schemaTable.Rows[ordinal]; // 从元数据中取BaseTableName就是该列所属的业务表名 string tableName = columnMetadata["BaseTableName"]?.ToString() ?? string.Empty; // 你的原有逻辑 return DateTime.SpecifyKind(base.GetDateTime(ordinal), base.GetName(ordinal).EndsWith("UTC", StringComparison.OrdinalIgnoreCase) ? DateTimeKind.Utc : DateTimeKind.Local); }
注意:这个方案依赖数据库驱动的元数据支持,微软官方的SqlClient、Npgsql等主流驱动都支持返回
BaseTableName,如果是小众驱动可能不返回该值,遇到空的情况可以用下面的备选方案。
备选方案(驱动不支持BaseTableName时使用)
如果你的数据库驱动不返回BaseTableName,可以在拦截器阶段提前解析SQL拿到表名,再传给自定义的DataReader:
- 首先修改
MyDbDataReader的构造函数,增加参数接收关联表名
public class MyDbDataReader : BaseDbDataReader { private string _dbName; // 存储当前查询关联的所有表名 public IReadOnlyList<string> RelatedTableNames { get; } public MyDbDataReader(string dbName, DbDataReader source, IReadOnlyList<string> relatedTableNames) : base(source) { _dbName = dbName; RelatedTableNames = relatedTableNames ?? Array.Empty<string>(); } // 其余原有逻辑不变 }
- 修改拦截器逻辑,解析当前执行的SQL拿到关联表名
public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { base.ReaderExecuted(command, interceptionContext); if (!command.Connection.Database.Equals("...") && !(interceptionContext.Result is MyDbDataReader) && interceptionContext.Result != null && interceptionContext.Exception == null) { // 解析SQL中的表名,简单单表查询可以用正则,复杂连表可以用开源SQL解析库处理 var tableNames = new List<string>(); var regex = new Regex(@"FROM\s+[\[\]]?(?<table>\w+)[\[\]]?", RegexOptions.IgnoreCase); foreach (Match match in regex.Matches(command.CommandText)) { if (match.Success) { tableNames.Add(match.Groups["table"].Value); } } // 去重后传入MyDbDataReader interceptionContext.Result = new MyDbDataReader( command.Connection.Database, interceptionContext.Result, tableNames.Distinct().ToList()); } }
额外优化建议
如果你的核心需求只是给UTC结尾的字段设置正确的DateTimeKind,其实完全不需要依赖表名:可以直接在EF的模型配置阶段给所有UTC字段打上标记,在拦截器阶段提前缓存需要设置为UTC的列序号,性能比每次读字段都查元数据要高很多。
内容的提问来源于stack exchange,提问作者Denis Kaminsky
相关产品推荐
相关产品推荐

