C#调用Kusto ToEnumerable转对象列表返回默认空值问题
执行Kusto查询获取表结构时,尝试将查询返回的响应转换为强类型对象列表,使用的Kusto查询语句如下:
tableName | getschema
该查询在Kusto Explorer中的返回结果(非完整版本)截图如下:
在C#代码中定义了如下DatasetColumn类用于接收查询结果:
public class DatasetColumn { /// <summary> /// 列名 /// </summary> public string? ColumnName { get; set; } /// <summary> /// 列在结构中的位置序号 /// </summary> public int ColumnOrdinal { get; set; } /// <summary> /// 列存储的数据的类型 /// </summary> public string? DataType { get; set; } /// <summary> /// 列存储的数据的类型 /// </summary> public string? ColumnType { get; set; } }
调用Kusto.Cloud.Data.ExtendedDataReader中提供的ToEnumerable扩展方法,从IDataReader读取结果并转为DatasetColumn对象列表,核心实现代码如下:
using (var client = storageClientFactory.CreateCslQueryProvider(new Kusto.Data.KustoConnectionStringBuilder(connectionDetails!.ClusterUrl))) { var schemaQuery = KustoSchemaQueryBuilder.GetKustoSchemaQuery(connectionDetails!.EntityName); var clientRequestProperties = new ClientRequestProperties() { ClientRequestId = Guid.NewGuid().ToString() }; var queryTask = client.ExecuteQueryAsync(connectionDetails.DatabaseName, schemaQuery, clientRequestProperties); using (var reader = await queryTask.ConfigureAwait(false)) { using (reader) { return reader.ToEnumerable<DatasetColumn>().ToArray(); } } }
实际运行时,返回的列表长度和预期结果行数完全匹配,但所有对象的属性均为类型默认值:字符串属性为null,整型属性为0。
以下是可复现问题的最小可运行示例:
using Kusto.Cloud.Platform.Data; using Kusto.Data; using Kusto.Data.Net.Client; using Newtonsoft.Json; namespace ConsoleApp1 { class DatasetColumn { public string? ColumnName { get; set; } public int ColumnOrdinal { get; set; } public string? DataType { get; set; } public string? ColumnType { get; set; } } internal class Program { static void Main(string[] args) { var sb = new KustoConnectionStringBuilder("https://help.kusto.windows.net/Samples; Fed=true; Accept=true"); using (var client = KustoClientFactory.CreateCslQueryProvider(sb)) { using (var result = client.ExecuteQuery("StormEvents | getschema")) { var cols = result.ToEnumerable<DatasetColumn>().ToArray(); Console.WriteLine(JsonConvert.SerializeObject(cols)); } } } } }
这个问题的核心是ToEnumerable<T>()扩展方法的默认映射规则和常规开发预期不一致:
该方法的完整签名为public static IEnumerable<T> ToEnumerable<T>(this IDataReader reader, bool disposeReader = true, bool useProperties = false) where T : new(),其中第三个参数useProperties默认值为false,即默认仅映射目标类的公共字段,完全忽略公共属性。
你定义的DatasetColumn类中,所有成员都是get;set形式的公共属性,方法逐行生成对象时,仅会new出默认实例,不会对属性做任何赋值操作,最终就出现了列表长度和返回行数一致、但所有属性都是类型默认值的现象。
你对该扩展方法的设计用途理解没有偏差,它本身就是用来将IDataReader结果逐行转换为强类型对象序列的,只是默认映射规则和多数ORM默认映射属性的行为不同,非常容易踩坑。
有两种简单方案可以解决这个问题,按需选择即可:
- 方案1:调用方法时开启属性映射
调用ToEnumerable时传入useProperties: true参数,让方法识别并映射公共属性即可,修改后的调用代码如下:
由于var cols = result.ToEnumerable<DatasetColumn>(useProperties: true).ToArray();getschema返回的列名(ColumnName、ColumnOrdinal、DataType、ColumnType)和你定义的属性名完全一致,开启参数后不需要额外调整类定义就能得到正确赋值的对象。 - 方案2:将类的公共属性改为公共字段
如果不想修改方法调用逻辑,也可以调整DatasetColumn的定义,把自动属性改为公共字段,适配默认的映射规则,修改后的类如下:class DatasetColumn { public string? ColumnName; public int ColumnOrdinal; public string? DataType; public string? ColumnType; }
补充说明:该方法做映射时,列名和字段/属性名的匹配是大小写不敏感的,不需要特意修改命名适配返回结果的列名大小写。如果存在列名和属性名不一致的场景,可以给对应属性/字段添加[System.ComponentModel.DataAnnotations.Schema.Column]特性指定映射的列名。
内容的提问来源于stack exchange,提问作者Liviu

