Search code examples
c#mysqlmysql.data

MySqlDataReader.GetStream() throws IndexOutOfRangeException


So I'm using the MySql.Data package for my .NET Core project and store password hashes and salts (of type byte[]) as varbinary() to the database. When selecting the password hash and salt from a user I need a way to convert the result back to a byte array. Given this sample code

Stream passwordHashStream = dbDataReader.GetStream(0);
byte[] passwordHash;
                        
using (MemoryStream memoryStream = new MemoryStream())
{
    await passwordHashStream.CopyToAsync(memoryStream);
    passwordHash = memoryStream.ToArray();
}

the first line will throw this exception

System.IndexOutOfRangeException: Data index must be a valid index in the field at MySql.Data.MySqlClient.Interceptors.ExceptionInterceptor.Throw(Exception exception) at MySql.Data.MySqlClient.MySqlConnection.Throw(Exception ex) at MySql.Data.MySqlClient.MySqlDataReader.Throw(Exception ex) at MySql.Data.MySqlClient.MySqlDataReader.GetBytes(Int32 i, Int64 fieldOffset, Byte[] buffer, Int32 bufferoffset, Int32 length) at System.Data.Common.DbDataReader.GetStream(Int32 ordinal) at Infrastructure.Persistence.Repositories.UsersRepository.<>c.<b__1_0>d.MoveNext() in /.../Infrastructure/Persistence/Repositories/UsersRepository.cs:line 60

although the reader contains the correct database results as you can see here down below (password is 32 bytes, hash is 16 bytes)

enter image description here

The error was reported in 2018

https://bugs.mysql.com/bug.php?id=93374

and has a verified status. Are there any good solutions / workarounds on how to solve it? I don't want to use the GetBytes method because it requires me to pass in the length of the byte array and I would like to stay independent from that.


Solution

  • Uninstall MySql.Data and replace it with MySqlConnector.

    (Disclosure: I'm the reporter of the MySql bug you found and the primary contributor to MySqlConnector.)

    As well as fixing that issue and many other bugs, MySqlConnector adds true async I/O support and performance improvements.

    I don't want to use the GetBytes method because it requires me to pass in the length of the byte array and I would like to stay independent from that.

    If you don't want to switch libraries, a little-known feature of GetBytes (supported by both MySql.Data and MySqlConnector) is that passing in a null buffer returns the needed length, so you don't need to hard-code it:

    // get the length of the column and allocate a buffer
    var length = dbDataReader.GetBytes(0, 0, null, 0, 0);
    var passwordHash = new byte[length];
    
    // fill the buffer from the column
    dbDataReader.GetBytes(0, 0, passwordHash, 0, passwordHash.Length);