1. 技术栈选择与准备

我们选用C# 10.0 + MySqlConnector 2.3.8 + MySQL 8.0组合。MySqlConnector相比官方MySQL Connector/NET有更活跃的维护,支持async/await特性,且性能提升约30%(根据官方基准测试)。通过NuGet安装:

Install-Package MySqlConnector -Version 2.3.8

2. 基础调用流程(带完整示例)

2.1 创建测试存储过程

先创建一个包含输入/输出参数和结果集的存储过程:

DELIMITER $$
CREATE PROCEDURE GetEmployeeDetails(
    IN deptId INT,
    OUT totalCount INT,
    INOUT bonusRate DECIMAL(5,2)
)
BEGIN
    -- 查询结果集
    SELECT * FROM employees WHERE department_id = deptId;
    
    -- 设置输出参数
    SELECT COUNT(*) INTO totalCount FROM employees;
    
    -- 修改INOUT参数
    SET bonusRate = bonusRate * 1.1;
END$$
DELIMITER ;

2.2 C#调用实现

完整控制台应用示例:

using MySqlConnector;
using System.Data;

public class Program
{
    const string connStr = "Server=localhost;Database=testdb;User=root;Password=123456;";

    public static async Task Main()
    {
        await using var connection = new MySqlConnection(connStr);
        await connection.OpenAsync();

        // 创建命令对象(重点:必须指定CommandType)
        await using var cmd = new MySqlCommand("GetEmployeeDetails", connection)
        {
            CommandType = CommandType.StoredProcedure
        };

        // 输入参数
        cmd.Parameters.Add(new MySqlParameter("deptId", 5));
        
        // 输出参数(必须指定方向)
        cmd.Parameters.Add(new MySqlParameter("totalCount", MySqlDbType.Int32)
        {
            Direction = ParameterDirection.Output
        });
        
        // INOUT参数(初始值+方向)
        cmd.Parameters.Add(new MySqlParameter("bonusRate", MySqlDbType.Decimal)
        {
            Value = 1.5m,
            Direction = ParameterDirection.InputOutput,
            Precision = 5,
            Scale = 2
        });

        // 执行并读取结果集
        await using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            Console.WriteLine($"员工ID: {reader["id"]}, 姓名: {reader["name"]}");
        }

        // 必须显式关闭Reader才能获取输出参数
        await reader.CloseAsync();

        // 获取参数值(注意类型转换)
        int total = (int)cmd.Parameters["totalCount"].Value;
        decimal rate = (decimal)cmd.Parameters["bonusRate"].Value;
        
        Console.WriteLine($"总人数: {total}, 调整后奖金系数: {rate}");
    }
}

3. 高级应用场景

3.1 事务处理

存储过程与事务的完美组合:

await using var transaction = await connection.BeginTransactionAsync();
try
{
    cmd.Transaction = transaction;
    // 执行多个存储过程...
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

3.2 批量参数处理

使用MySqlBatch提升批量执行效率:

var batch = new MySqlBatch(connection)
{
    BatchCommands =
    {
        new MySqlBatchCommand("Call UpdateInventory(@productId, @qty)")
        {
            Parameters =
            {
                new MySqlParameter("@productId", 101),
                new MySqlParameter("@qty", 10)
            }
        },
        new MySqlBatchCommand("Call LogOperation(@userId, @action)")
        {
            Parameters =
            {
                new MySqlParameter("@userId", "admin"),
                new MySqlParameter("@action", "库存更新")
            }
        }
    }
};
await batch.ExecuteNonQueryAsync();

4. 技术优缺点分析

4.1 优势亮点

  • 性能卓越:二进制协议传输比普通SQL快约40%
  • 参数安全:天然防止SQL注入
  • 复用性强:统一业务逻辑入口
  • 事务简化:复杂操作原子性保证

4.2 潜在局限

  • 调试复杂度:需要跨语言调试
  • 版本管理:需同步维护SQL脚本
  • 移植成本:不同数据库语法差异

5. 关键注意事项

5.1 参数顺序陷阱

MySQL存储过程参数必须按照创建顺序添加,否则会引发错误。建议使用参数名称显式指定:

// 正确做法(指定参数名称)
cmd.Parameters.Add(new MySqlParameter("deptId", MySqlDbType.Int32)
{
    Value = 5,
    Direction = ParameterDirection.Input
});

5.2 类型映射要点

常见类型对应关系:

MySQL类型 C#类型 MySqlDbType枚举
INT int Int32
DECIMAL(10,2) decimal Decimal
VARCHAR(255) string VarChar
DATETIME DateTime DateTime
BLOB byte[] Blob

5.3 连接池优化

在连接字符串中配置:

Pooling=true; 
MinimumPoolSize=5; 
MaximumPoolSize=100; 
ConnectionIdleTimeout=300;

6. 性能优化实践

6.1 预处理命令

使用Prepare提升重复执行效率:

if (!cmd.IsPrepared)
{
    await cmd.PrepareAsync();
}
// 循环执行已预处理的命令...

6.2 异步编程

充分利用async/await避免线程阻塞:

public async Task<decimal> CalculateSalaryAsync(int userId)
{
    await using var cmd = new MySqlCommand("CalcSalary", connection)
    {
        CommandType = CommandType.StoredProcedure
    };
    cmd.Parameters.AddWithValue("userId", userId);
    return (decimal)await cmd.ExecuteScalarAsync();
}

7. 典型错误排查

7.1 参数方向缺失

未设置ParameterDirection导致输出参数获取失败:

// 错误示例:缺少Direction设置
cmd.Parameters.Add(new MySqlParameter("totalCount", MySqlDbType.Int32));

// 正确做法
cmd.Parameters.Add(new MySqlParameter("totalCount", MySqlDbType.Int32)
{
    Direction = ParameterDirection.Output
});

7.2 结果集未关闭

过早访问输出参数导致异常:

// 错误顺序
var total = cmd.Parameters["totalCount"].Value; // 此处会抛出异常
await reader.CloseAsync();

// 正确流程
await reader.CloseAsync();
var total = cmd.Parameters["totalCount"].Value;

8. 总结与选择建议

在以下场景优先选用存储过程:

  • 复杂计算逻辑(如财务核算)
  • 高频批量操作(如库存扣减)
  • 敏感数据操作(如权限分级)

对于简单CRUD操作,建议直接使用Dapper等轻量级ORM。建议将存储过程版本纳入CI/CD流程管理,使用Flyway等数据库迁移工具保持版本一致性。