tursodatabase/turso · error · InvalidCastException

Cannot convert remote {Type} value to Decimal.

Error message

Cannot convert remote {Type} value to Decimal.

What it means

GetDecimal on a reader column whose remote value type is not float, integer, or text. The remote protocol has no decimal type -- floats and text are converted -- but NULL and BLOB have no arm, so those cells throw InvalidCastException from the typed getter.

Source

Thrown at bindings/dotnet/src/Turso.Data/TursoRemoteClient.cs:698

    public double GetDouble()
    {
        return Type switch
        {
            "float" => ParseFloat(),
            "integer" => ParseInteger(),
            "text" => double.Parse(Value.GetString() ?? "", CultureInfo.InvariantCulture),
            _ => throw new InvalidCastException($"Cannot convert remote {Type} value to Double."),
        };
    }

    public decimal GetDecimal()
    {
        return Type switch
        {
            "float" => Convert.ToDecimal(ParseFloat(), CultureInfo.InvariantCulture),
            "integer" => ParseInteger(),
            "text" => decimal.Parse(Value.GetString() ?? "", CultureInfo.InvariantCulture),
            _ => throw new InvalidCastException($"Cannot convert remote {Type} value to Decimal."),
        };
    }

    private long ParseInteger()
    {
        return Value.ValueKind == JsonValueKind.String
            ? long.Parse(Value.GetString() ?? "", CultureInfo.InvariantCulture)
            : Value.GetInt64();
    }

    private double ParseFloat()
    {
        return Value.ValueKind == JsonValueKind.String
            ? double.Parse(Value.GetString() ?? "", CultureInfo.InvariantCulture)
            : Value.GetDouble();
    }

    private static byte[] DecodeBase64(string value)

View on GitHub (pinned to 6c72522679)

Solutions

  1. Guard with reader.IsDBNull(i) before GetDecimal and choose a default (0m or throwing a domain-specific error).
  2. Use COALESCE(col, 0) in SQL when a zero default is acceptable.
  3. Prefer storing money as integer minor units or TEXT and converting explicitly, since the wire protocol has no exact decimal type.
  4. Verify the column ordinal and declared type before using typed getters.

Example fix

// before
decimal total = reader.GetDecimal(2);

// after
decimal total = reader.IsDBNull(2) ? 0m : reader.GetDecimal(2);
Defensive patterns

Strategy: validation

Validate before calling

if (reader.IsDBNull(ordinal))
    return 0m;
return reader.GetDecimal(ordinal);

Type guard

static bool CanReadDecimal(System.Data.Common.DbDataReader reader, int i) =>
    !reader.IsDBNull(i)
    && reader.GetFieldType(i) is Type t
    && (t == typeof(decimal) || t == typeof(double) || t == typeof(long) || t == typeof(string));

Try / catch

try { value = reader.GetDecimal(i); }
catch (InvalidCastException) when (reader.IsDBNull(i))
{
    value = 0m; // explicit policy for NULL money columns
}

Prevention

When it happens

Trigger: reader.GetDecimal(i) on a remote connection where column i is NULL, a BLOB, or an unsupported type; text cells are parsed with decimal.Parse and throw FormatException for non-numeric or culture-incompatible strings.

Common situations: Money/amount columns stored as REAL or TEXT and occasionally NULL; LEFT JOIN or outer-aggregate queries producing NULL cells; schema changes after deployment.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/a11653ca99481da7. Report an issue: GitHub.