You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需新建类,如何从数据库返回关联表的通用对象而非指定类型?

问题描述

需求:实现GetTransactionBySaleId方法,关联查询Transactions和TransactionItems表的全部数据,尽可能不新建类。

现有实现代码:

public async Task<IEnumerable<object>> GetTransactionBySaleId(int id)
{
    var query = ("SELECT t.*, ti.* " +
       "FROM Transactions t " +
       "INNER JOIN TransactionItems ti ON t.SaleId = ti.SaleId " +
       "WHERE t.SaleId = @Id");

    // Parameterisation to prevent injection attacks
    var idParam = new SqlParameter("Id", id);

    return await DatabaseConnection.Instance.ExecuteQueryAsync<object>(query, idParam);
}

ExecuteQueryAsync方法实现:

public async Task<IEnumerable<T>> ExecuteQueryAsync<T>(string query, params SqlParameter[] parameters) where T : new()
{
    using var connection = new SqlConnection(connectionString);

    await connection.OpenAsync();

    var resultList = new List<T>();

    using var command = new SqlCommand(query, connection);

    // Add parameters to the command if any
    if (parameters != null)
    {
        command.Parameters.AddRange(parameters);
    }

    using var reader = await command.ExecuteReaderAsync();
    
    while (await reader.ReadAsync())
    {
        var obj = new T();
        MapData(reader, obj);
        resultList.Add(obj);
    }
    
    return resultList;
}

遇到的问题:

  • 返回类型设为object时,仅返回对应条目数量的空对象;
  • 返回类型设为Transactions时,无法获取TransactionItems表的数据。
解决方案

1. 核心问题分析

  • 使用object作为泛型参数时,MapData方法无法给无属性的object实例赋值,因此得到空对象;
  • 使用Transactions类时,该类仅包含Transactions表的属性,TransactionItems表的字段没有对应的属性,映射时会被忽略,所以拿不到关联表数据。

2. 无需新建类的最优方案:使用ExpandoObject

ExpandoObject支持动态添加属性,完美适配多表联合查询的动态字段需求,且不需要新建实体类。

步骤1:修改方法返回类型

将GetTransactionBySaleId的返回类型改为Task<IEnumerable<ExpandoObject>>,并调整泛型调用:

public async Task<IEnumerable<ExpandoObject>> GetTransactionBySaleId(int id)
{
    var query = ("SELECT t.*, ti.* " +
       "FROM Transactions t " +
       "INNER JOIN TransactionItems ti ON t.SaleId = ti.SaleId " +
       "WHERE t.SaleId = @Id");

    var idParam = new SqlParameter("Id", id);

    return await DatabaseConnection.Instance.ExecuteQueryAsync<ExpandoObject>(query, idParam);
}

步骤2:修改MapData方法支持动态对象

调整MapData逻辑,让它能遍历DataReader的所有列,将列名和对应值添加到ExpandoObject中:

private void MapData(IDataReader reader, object obj)
{
    if (obj is ExpandoObject expando)
    {
        var expandoDict = expando as IDictionary<string, object>;
        for (int i = 0; i < reader.FieldCount; i++)
        {
            string columnName = reader.GetName(i);
            object value = reader.IsDBNull(i) ? null : reader.GetValue(i);
            expandoDict[columnName] = value;
        }
        return;
    }

    // 保留原有的实体类映射逻辑(兼容其他实体类场景)
    PropertyInfo[] properties = obj.GetType().GetProperties();
    foreach (PropertyInfo prop in properties)
    {
        if (!reader.IsDBNull(reader.GetOrdinal(prop.Name)))
        {
            object value = reader.GetValue(reader.GetOrdinal(prop.Name));
            prop.SetValue(obj, value);
        }
    }
}

步骤3:确认泛型约束兼容性

ExpandoObject具备默认构造函数,原ExecuteQueryAsync的where T : new()约束可以满足,无需修改该方法定义。

3. 临时替代方案:手动构造匿名类型

如果不想用ExpandoObject,可以直接读取DataReader并手动构造匿名类型,但需要硬编码所有列名,灵活性较差:

public async Task<IEnumerable<object>> GetTransactionBySaleId(int id)
{
    var query = ("SELECT t.*, ti.* " +
       "FROM Transactions t " +
       "INNER JOIN TransactionItems ti ON t.SaleId = ti.SaleId " +
       "WHERE t.SaleId = @Id");

    var idParam = new SqlParameter("Id", id);
    var result = new List<object>();

    using var connection = new SqlConnection(DatabaseConnection.Instance.connectionString);
    await connection.OpenAsync();
    using var command = new SqlCommand(query, connection);
    command.Parameters.Add(idParam);
    using var reader = await command.ExecuteReaderAsync();

    while (await reader.ReadAsync())
    {
        var item = new
        {
            // Transactions表字段
            TransactionId = reader.GetInt32(reader.GetOrdinal("TransactionId")),
            SaleId = reader.GetInt32(reader.GetOrdinal("SaleId")),
            TransactionDate = reader.GetDateTime(reader.GetOrdinal("TransactionDate")),
            // ...补充其他Transactions表字段
            // TransactionItems表字段
            TransactionItemId = reader.GetInt32(reader.GetOrdinal("TransactionItemId")),
            ProductId = reader.GetInt32(reader.GetOrdinal("ProductId")),
            Quantity = reader.GetInt32(reader.GetOrdinal("Quantity")),
            // ...补充其他TransactionItems表字段
        };
        result.Add(item);
    }

    return result;
}

内容的提问来源于stack exchange,提问作者Luke 135

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 07:55:36