在没有提供列名的情况下,是否有一种方法(使用类型映射或其他解决方案)为dynamic
结果集提供默认名称,例如“(无列名)”?
我正在编写一个查询编辑器,允许用户对数据库编写和运行用户提供的查询。我一直在使用Dapper作为我们所有的查询,它一直工作漂亮的99%的我们需要。不过,我遇到了麻烦,我希望有人能找到解决办法。
查询编辑器类似于SSMS。我不知道脚本会是什么样子,结果集的形状或类型是什么,甚至会返回多少个结果集。因此,我一直在批处理脚本,并使用Dapper的QueryMultiple
从GridReader
读取dynamic
结果。然后将结果发送到第三方UI数据网格(WPF)。数据网格知道如何使用动态数据,显示给定行所需的唯一条件是至少有一个键值对具有非空值,但不一定是唯一键和可空值。到现在为止还好。
Dapper调用的简化版本如下所示:
public async Task<IEnumerable<IEnumerable<T>>> QueryMultipleAsync<T>(string sql,
object parameters,
string connectionString,
CommandType commandType = CommandType.Text,
CancellationTokenSource cancellationTokenSource = null)
{
using (IDbConnection con = _dbConnectionFactory.GetConnection(connectionString))
{
con.Open();
var transaction = con.BeginTransaction();
var sqlBatches = sql
.ToUpperInvariant()
.Split(new[] { " GO ", "\r\nGO ", "\n\nGO ", "\nGO\n", "\tGO ", "\rGO "}, StringSplitOptions.RemoveEmptyEntries);
var batches = new List<CommandDefinition>();
foreach(var batch in sqlBatches)
{
batches.Add(new CommandDefinition(batch, parameters, transaction, null, commandType, CommandFlags.Buffered, cancellationTokenSource.Token));
}
var resultSet = new List<List<T>>();
foreach (var commandDefinition in batches)
{
using (GridReader reader = await con.QueryMultipleAsync(commandDefinition))
{
while (!reader.IsConsumed)
{
try
{
var result = (await reader.ReadAsync<T>()).AsList();
if (result.FirstOrDefault() is IDynamicMetaObjectProvider)
{
(result as List<dynamic>).ConvertNullKeysToNoColumnName();
}
resultSet.Add(result);
}
catch(Exception e)
{
if(e.Message.Equals("No columns were selected"))
{
break;
}
else
{
throw;
}
}
}
}
}
try
{
transaction.Commit();
}
catch (Exception ex)
{
Trace.WriteLine(ex.ToString());
if (transaction != null)
{
transaction.Rollback();
}
}
return resultSet;
}
}
public static IEnumerable<dynamic> ConvertNullKeysToNoColumnName<dynamic>(this IEnumerable<dynamic> rows)
{
foreach (var row in rows)
{
if (row is IDictionary<string, object> rowDictionary)
{
if (rowDictionary == null) continue;
rowDictionary.Where(x => string.IsNullOrEmpty(x.Key)).ToList().ForEach(x =>
{
var val = rowDictionary[x.Key];
if (x.Value == val)
{
rowDictionary.Remove(x);
rowDictionary.Add("(No Column Name)", val);
}
else
{
Trace.WriteLine("Something went wrong");
}
});
}
}
return rows;
}
这适用于大多数查询(对于只有一个未命名结果列的查询),但当用户使用多个未命名列写入查询时,问题就会显现出来,如下所示:
select COUNT(*), MAX(create_date) from sys.databases
。
在本例中,Dapper返回一个类似于以下内容的DapperRow:
{DapperRow, = '9', = '2/14/2020 9:51:54 AM'}
因此,结果集正是用户所要求的(即没有名称或别名的值),但我需要为网格中的所有数据提供(非唯一的)键.
我的第一个想法是简单地将DapperRow
对象中的空键更改为默认值(比如‘(没有列名)’),因为它似乎是为存储而优化的,因此表键只在对象中存储一次(这很好,对于具有巨大结果集的查询提供了很好的性能奖励)。不过,DapperRow
类型是私有的。在搜索之后,我发现可以将DapperRow
转换为IDictionary<string, object>
来访问对象的键和值,甚至可以设置和删除值。这就是ConvertNullKeysToNoColumnName
扩展方法的来源。而且效果很好..。但只有一次。
为什么?那么,当您在一个DapperRow
中有多个空或空键并被转换为IDictionary<string,object>
,并且调用Remove(x)
函数时(其中x是整个项,或者仅仅是带有空键或空键的单个项的键),那么以后所有通过索引器item[key]
解析空键或空键的尝试都无法检索值--即使对象中仍然存在附加的键值对。
换句话说,在删除第一个键之后,我无法删除或替换后续的空键。
我漏掉了什么明显的东西吗?我是否只需要通过反射改变DapperRow
,并希望它没有任何奇怪的副作用,或者底层数据结构以后不会改变?还是将性能/内存命中,并将整个潜在的大结果集复制/映射到一个新的序列中,以便在运行时给空键一个默认值?
发布于 2020-02-18 23:24:14
我怀疑这是因为动态DapperRow对象实际上不是一个“正常”字典。它可以有几个具有相同键的条目。如果检查调试器中的对象,可以看到这一点。
当您引用rowDictionary[x.Key]
时,我怀疑您总是会得到第一个未命名的列。
如果调用rowDictionary.Remove(""); rowDictionary.Remove("");
,实际上只删除第一个条目--第二个条目仍然存在,尽管rowDictionary.ContainsKey("")
返回false。
您可以对整个字典进行Clear()
和重建。在这一点上,通过使用动态对象,您实际上并没有获得什么好处。
if (row is IDictionary<string, object>)
{
var rowDictionary = row as IDictionary<string, object>;
if (rowDictionary.ContainsKey(""))
{
var kvs = rowDictionary.ToList();
rowDictionary.Clear();
for (var i = 0; i < kvs.Count; ++i)
{
var kv = kvs[i];
var key = kv.Key == ""? $"(No Column <{i + 1}>)" : kv.Key;
rowDictionary.Add(key, kv.Value);
}
}
}
由于您使用的是未知的结果结构,并且只想将其传递给网格视图,所以我会考虑使用DataTable。
您仍然可以为参数处理保留Dapper:
foreach (var commandDefinition in batches)
{
using(var reader = await con.ExecuteReaderAsync(commandDefinition)) {
while(!reader.IsClosed) {
var table = new DataTable();
table.Load(reader);
resultSet.Add(table);
}
}
}
https://stackoverflow.com/questions/60290407
复制相似问题