在 C# 中使用 Dapper 查询数据并导出 Excel

1. 背景介绍

在项目中,我们经常需要查询数据库中的数据并导出为 Excel 进行分析或存档。本篇文章介绍如何在 C# 中使用 Dapper 查询 SQL Server 数据,并通过 EPPlus 生成 Excel 文件,支持 动态对象 (ExpandoObject) 和静态实体对象 的数据导出。

2. 代码结构概览

该实现包括以下部分:

  1. 数据库查询 (DapperHelper.Query) :执行 SQL 查询并返回 DynamicParameters 形式的结果。
  2. 数据转换 (ExpandoObject):查询结果转换为动态对象。
  3. 静态对象列表 (Person):示例数据。
  4. Excel 导出 (ExcelHelper.ExportToExcel) :将数据转换为 Excel 文件,支持 动态对象和静态对象,自动适配嵌套属性。

3. 代码实现

cs 复制代码
Dictionary<string, object> keyValuePairs = new();
  • 为了方便我们进行多个Sheet和多个数据查询,我这边会把多个数据存储到一个键值对里面,键值对的Key是Sheet的名字,Value是我们查出来的静态或者动态实体内容的集合

3.1 使用 Dapper 查询数据库

cs 复制代码
public static async Task<IEnumerable<DynamicParameters>> Query(string sql, object? @params = null, int? commandTimeout = null, CommandType? commandType = null)
{
    string ConnStr = AppConfig.SqlLinks;
    using (var con = new SqlConnection(ConnStr))
    {
        var results = await con.QueryAsync(sql, @params, commandTimeout: commandTimeout, commandType: commandType);
        return results.Select(r => new DynamicParameters(r));
    }
}
  • 该方法使用 Dapper 查询数据库,并返回 DynamicParameters
  • 连接字符串从 AppConfig.SqlLinks 获取。
  • 我们在不想创建实体对象的情况下直接选择把内容转化为动态实体类型

3.2 查询 orders 表并转换为动态对象

cs 复制代码
string sql = "Select * From [1booking].[dbo].[orders] With(NoLock)";
var results = await DapperHelper.Query(sql);
if (results.Count() > 0 && results != null)
{
    var objects = results.Select(r =>
    {
        var expando = new ExpandoObject();
        var dict = (IDictionary<string, object>)expando!;
        foreach (var key in r.ParameterNames)
        {
            dict[key] = r.Get<object>(key);
        }
        return expando;
    });
}
  • 查询 orders 表,并转换查询结果为 动态对象 (ExpandoObject)
  • 我们把他转换为动态对象之后,后续就可以使用它来直接展示
  • 把他直接存储键值对中,这个肯定都会存储,我这边就少些一行代码了

3.3 示例:静态对象列表 Person

cs 复制代码
var people = new List<Person>
{
    new Person
    {
        Id = 1,
        Name = "张三",
        BirthDate = new DateTime(1990, 1, 1),
        Salary = 5000.50m,
        Address = new Address { City = "北京", State = "北京" },
        Info = new Info {A = "2", B = "C"}
    },
    new Person
    {
        Id = 2,
        Name = "李四",
        BirthDate = new DateTime(1985, 5, 12),
        Salary = 8000.75m,
        Address = new Address { City = "上海", State = "上海" },
        Info = new Info {A = "1", B = "B"}
    }
};
  • 定义 静态对象 Person,这里都是我的示例,举例说明的,可以是任何静态对象多层嵌套也是没问题的
  • 把他直接存储键值对中,这个肯定都会存储,我这边就少些一行代码了

3.4 调用 Excel 导出方法

cs 复制代码
var excelData = ExcelHelper.ExportToExcel<Task>(keyValuePairs);
  • 通过 ExcelHelper.ExportToExcel<T> 方法,将 keyValuePairs 导出为 Excel。
  • 在这一步也就是调用我们主要的实现方法,这个方法里面主要是处理Excel和我们的实体区分处理。以实现全自动处理不区分类型

3.5 Excel 导出实现 (ExcelHelper)

cs 复制代码
public static byte[] ExportToExcel<T>(Dictionary<string, object> dataSets)
{
    // 首先我们不能让我们的集合是空的
    if (dataSets == null || !dataSets.Any())
    {
        throw new ArgumentException("数据列表不能为空");
    }
    // 因为这个分商用版和个人版本,一个收费一个不收费,所以我选择个人使用
    ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
    // 创建一个处理Excel的生命周期
    using (var package = new ExcelPackage())
    {
        // 这一步也就是我上面所说的,如果有多个集合进行区分Sheet处理不同Sheet存放不同集合数据
        foreach (var dataSet in dataSets)
        {
            var sheetName = dataSet.Key;
            var data = dataSet.Value;
            var worksheet = package.Workbook.Worksheets.Add(sheetName);
            
            if (data is IEnumerable<ExpandoObject> dynamicList)
            {// 这一步主要是处理动态集合元素
                // 获取动态集合的属性,这里一般动态元素的类型我们是获取不到的都是null
                var properties = GetAllProperties(dynamicList.FirstOrDefault(), typeof(T));
                for (int i = 0; i < properties.Count; i++)
                {
                    // 写入第一行属性名称
                    worksheet.Cells[1, i + 1].Value = properties[i].DisplayName ?? properties[i].PropertyName;
                }
                for (int row = 0; row < dynamicList.Count(); row++)
                {
                    var item = dynamicList.ElementAt(row);
                    for (int col = 0; col < properties.Count; col++)
                    {
                        // 这一步用来处理嵌套属性
                        worksheet.Cells[row + 2, col + 1].Value = GetNestedPropertyValue(item, properties[col].PropertyName);
                    }
                }
            }
            else if (data is IEnumerable<object> list)
            {// 这一步主要是处理静态集合元素
                // 获取静态集合的属性,像是静态属性,这里是可以获取到的
                var properties = GetAllProperties(list.FirstOrDefault(), typeof(T));
                for (int i = 0; i < properties.Count; i++)
                {
                    // 写入第一行属性名称
                    worksheet.Cells[1, i + 1].Value = properties[i].DisplayName ?? properties[i].PropertyName;
                }
                int row = 2;
                foreach (var item in list)
                {
                    for (int col = 0; col < properties.Count; col++)
                    {
                        // 写入内容
                        worksheet.Cells[row, col + 1].Value = GetNestedPropertyValue(item, properties[col].PropertyName);
                    }
                    row++;
                }
            }
            // 自动调整列宽
            worksheet.Cells.AutoFitColumns();
        }
        // 返回Excel文件的字节数组
        return package.GetAsByteArray();
    }
}
cs 复制代码
private static List<PropertyMetadata> GetAllProperties(object obj, Type type = null, string prefix = "")
{
    // 处理 动态属性
    if (obj is IDictionary<string, object> dict)
    {
        // 遍历 ExpandoObject 的键值对
        foreach (var key in dict.Keys)
        {
            // 添加属性元数据(ExpandoObject 没有 PropertyInfo)
            properties.Add(new PropertyMetadata(null, key, key));
        }
    }
    // 处理静态属性
    else
    {
        // 获取类型的公共实例属性
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            // 构造属性名称(考虑前缀)
            var propertyName = string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}";

            // 处理基本类型属性
            if (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(string) || prop.PropertyType == typeof(DateTime) || prop.PropertyType == typeof(decimal))
            {
                // 获取显示名称(DisplayNameAttribute)
                var displayName = prop.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
                // 添加属性元数据
                properties.Add(new PropertyMetadata(prop, propertyName, displayName));
            }
            // 处理嵌套对象属性
            else if (prop.PropertyType.IsClass)
            {
                // 递归获取嵌套属性
                properties.AddRange(GetAllProperties(null, prop.PropertyType, propertyName));
            }
        }
    }
    // 返回属性元数据列表
    return properties;
}
cs 复制代码
/// <summary>
/// 属性名称(包括前缀)
/// </summary>
public string PropertyName { get; }

/// <summary>
/// 属性显示名称(DisplayNameAttribute)
/// </summary>
public string DisplayName { get; }

/// <summary>
/// 构造函数
/// </summary>
/// <param name="propertyInfo">PropertyInfo 对象(如果是基本类型,则为 null)</param>
/// <param name="propertyName">属性名称(包括前缀)</param>
/// <param name="displayName">属性显示名称(DisplayNameAttribute)</param>
public PropertyMetadata(PropertyInfo propertyInfo, string propertyName, string displayName)
{
    PropertyInfo = propertyInfo;
    PropertyName = propertyName;
    DisplayName = displayName;
}
cs 复制代码
private static object GetNestedPropertyValue(object obj, string propertyName)
{
    // 分割属性名称(支持嵌套属性)
    var props = propertyName.Split('.');

    // 初始化属性值
    object value = obj;

    // 遍历属性名称
    foreach (var prop in props)
    {
        // 检查属性值是否为空
        if (value == null)
        {
            return null; // 如果属性值为空,则返回 null
        }

        // 处理 动态属性,一般这个区分不了类型,后续可以在写入文档的时候区分类型
        if (value is IDictionary<string, object> dict)
        {
            // 尝试获取属性值
            dict.TryGetValue(prop, out value);
        }
        else // 处理静态属性
        {
            // 获取属性信息
            var propertyInfo = value.GetType().GetProperty(prop);

            // 检查属性是否存在
            if (propertyInfo == null)
            {
                return null; // 如果属性不存在,则返回 null
            }

            // 获取属性值
            value = propertyInfo.GetValue(value);
        }
    }

    // 返回属性值
    return value;
}
  • 自动解析动态对象和静态对象
  • 支持嵌套对象属性导出
  • 格式化 Excel,自动调整列宽

3.6 最后输出结果展示


4. 总结

📌 完整流程

  1. 查询数据库DapperHelper.Query)。
  2. 处理动态对象 (ExpandoObject) ,并存入 keyValuePairs
  3. 存储静态对象 (Person) ,并存入 keyValuePairs
  4. 调用 ExcelHelper.ExportToExcel 生成 Excel
  5. 支持静态 & 动态数据导出,自动解析嵌套对象。

🎯 适用场景

大批量数据导出

动态数据结构(ExpandoObject

支持嵌套对象

希望这篇文章能帮助你在 C# 项目中高效导出 Excel 数据!🚀

相关推荐
十年一梦实验室27 分钟前
C++ 中的 RTTI(Run-Time Type Information,运行时类型识别)
开发语言·c++
洛北辰南29 分钟前
系统架构设计师—案例分析—数据库篇—分布式缓存技术
数据库·分布式·系统架构·缓存技术
纽约恋情31 分钟前
C++——STL 常用的排序算法
开发语言·c++·排序算法
luckyext38 分钟前
Postman用JSON格式数据发送POST请求及注意事项
java·前端·后端·测试工具·c#·json·postman
qq_297908011 小时前
C#生产型企业ERP系统管理软件PCB行业ERP进销存MRP管理系统BOM管理
c#·asp.net·开源软件
千里码aicood1 小时前
【2025】基于python+django的驾校招生培训管理系统(源码、万字文档、图文修改、调试答疑)
开发语言·python·django
小李苦学C++1 小时前
C++模板特化与偏特化
开发语言·c++
星光璀璨山河无恙1 小时前
【MySQL】数据库简要介绍和简单应用
数据库·mysql
px52133441 小时前
Solder leakage problems and improvement strategies in electronics manufacturing
java·前端·数据库·pcb工艺
小王努力学编程1 小时前
元音辅音字符串计数leetcode3305,3306
开发语言·c++·学习·算法·leetcode