首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >QueryMultiple为动态结果集提供默认名称

QueryMultiple为动态结果集提供默认名称
EN

Stack Overflow用户
提问于 2020-02-18 22:32:56
回答 1查看 1.4K关注 0票数 1

在没有提供列名的情况下,是否有一种方法(使用类型映射或其他解决方案)为dynamic结果集提供默认名称,例如“(无列名)”?

我正在编写一个查询编辑器,允许用户对数据库编写和运行用户提供的查询。我一直在使用Dapper作为我们所有的查询,它一直工作漂亮的99%的我们需要。不过,我遇到了麻烦,我希望有人能找到解决办法。

查询编辑器类似于SSMS。我不知道脚本会是什么样子,结果集的形状或类型是什么,甚至会返回多少个结果集。因此,我一直在批处理脚本,并使用Dapper的QueryMultipleGridReader读取dynamic结果。然后将结果发送到第三方UI数据网格(WPF)。数据网格知道如何使用动态数据,显示给定行所需的唯一条件是至少有一个键值对具有非空值,但不一定是唯一键和可空值。到现在为止还好。

Dapper调用的简化版本如下所示:

代码语言:javascript
运行
复制
        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,并希望它没有任何奇怪的副作用,或者底层数据结构以后不会改变?还是将性能/内存命中,并将整个潜在的大结果集复制/映射到一个新的序列中,以便在运行时给空键一个默认值?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-02-18 23:24:14

我怀疑这是因为动态DapperRow对象实际上不是一个“正常”字典。它可以有几个具有相同键的条目。如果检查调试器中的对象,可以看到这一点。

当您引用rowDictionary[x.Key]时,我怀疑您总是会得到第一个未命名的列。

如果调用rowDictionary.Remove(""); rowDictionary.Remove("");,实际上只删除第一个条目--第二个条目仍然存在,尽管rowDictionary.ContainsKey("")返回false。

您可以对整个字典进行Clear()和重建。在这一点上,通过使用动态对象,您实际上并没有获得什么好处。

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
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);
        }
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60290407

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档