如果使用 UNION ALL,MySqlDataReader returns 同一列的不同类型

MySqlDataReader returns different type for same column if UNION ALL is used

我有一个循环遍历从 MySql 命令生成的 DataReader,它的查询从一些字段中选择,包括 2 个 TINYINT(1) 字段映射到 C# 上的 bools 和这个正是我所期待的。

当我将查询更改为使用相同的 table 执行 UNION ALL 时出现了问题。 更改查询后,我开始收到无效转换错误。 TINYINT(1) 列现在 return SByte 而不是 Boolean.

这是 MySql 服务器问题吗? MySql Net/Connector 问题?这是预期的行为吗?

示例查询:

string sql = @"SELECT tinyint1column FROM mytable WHERE id = 1";

command.CommandText = sql;

using(var reader = command.ExecuteReader())
{
    while(reader.Read())
    {
        bool flag = (bool)reader["tinyint1column"]; // OK - No error
    }
}

sql = @"SELECT tinyint1column FROM mytable WHERE id = 1
        UNION ALL
        SELECT tinyint1column FROM mytable WHERE id = 2";

command.CommandText = sql;

using(var reader = command.ExecuteReader())
{
    while(reader.Read())
    {
        bool flag = (bool)reader["tinyint1column"]; // Invalid cast error???
    }
}

之前有人问:

我也可以通过 NET Connector 6.3 确认问题(?)。

然而,调用 reader.GetBoolean().
有一个简单的方法 MySql 连接器中基础 IDbDataReader 的覆盖在 reader 字段

内部调用 Convert.ToBoolean()
public bool GetBoolean(string name)
{
    return this.GetBoolean(this.GetOrdinal(name));
}

public override bool GetBoolean(int i)
{
    return Convert.ToBoolean(this.GetValue(i));
}

因此您可以轻松调整代码以适应这种情况(它也适用于单个 table 版本的查询)

using(var reader = command.ExecuteReader())
{
    while(reader.Read())
    {
        bool flag = reader.GetBoolean("tinyint1column"); 
        ....
    }
}

编辑 鉴于您在下面的评论,我认为您可以使用 DbDataRecord class 的扩展方法解决缺少 GetBoolean(fieldName) 的问题。
我已经用 LinqPad 对此进行了测试,它似乎可以正常工作(如果该字段为空,则 return 的一部分)

public bool GetBoolean(DbDataRecord rec, string fieldName)
{
    int pos = rec.GetOrdinal(fieldName);
    if(rec.IsDBNull(pos))
        return false; // ??

    object result = rec.GetValue(pos);   
    return Convert.ToBoolean(result);
}

我不确定这是 MySql 问题还是连接器问题,但这是个问题。

从@Steve 的回答中得到一些启发后,我想出了一个解决方案:

public static class DbDataRecordExtensions
{
    public static bool GetBoolean(this DbDataRecord rec, string fieldName)
    {
        var index = rec.GetOrdinal(fieldName);
        var value = rec.GetValue(index);

        if (value is bool || value is Boolean)
        {
            return (bool)value;
        }
        else if (value is SByte || value is sbyte)
        {
            return (sbyte)value != 0;
        }
        else
        {
            return rec.GetInt64(index) != 0;
        }
    }
}

我无法进行通用转换,因为当两个查询联合 return 行时它会更改类型,所以我写了一些条件来克服这个问题。

请注意,对于 MySqlDataReader,您必须更改扩展方法签名和方法名称:

GetBooleanEx(this MySqlDataReader rec, string fieldName)