// Copyright (c) 2004, 2021, Oracle and/or its affiliates. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, as // published by the Free Software Foundation. // // This program is also distributed with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, // as designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an // additional permission to link the program and your derivative works // with the separately licensed software that they have included with // MySQL. // // Without limiting anything contained in the foregoing, this file, // which is part of MySQL Connector/NET, is also subject to the // Universal FOSS Exception, version 1.0, a copy of which can be found at // http://oss.oracle.com/licenses/universal-foss-exception. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA using MySql.Data.Types; using System; using System.Collections; using System.Data; using System.Data.Common; using System.Globalization; using System.Threading; using Sog.Properties; namespace MySql.Data.MySqlClient { /// public sealed partial class MySqlDataReader : DbDataReader, IDataReader, IDataRecord, IDisposable { // The DataReader should always be open when returned to the user. private bool _isOpen = true; internal long affectedRows; internal Driver driver; // Used in special circumstances with stored procs to avoid exceptions from DbDataAdapter // If set, AffectedRows returns -1 instead of 0. private readonly bool _disableZeroAffectedRows; /* * Keep track of the connection in order to implement the * CommandBehavior.CloseConnection flag. A null reference means * normal behavior (do not automatically close). */ private MySqlConnection _connection; /* * Because the user should not be able to directly create a * DataReader object, the constructors are * marked as internal. */ internal MySqlDataReader(MySqlCommand cmd, PreparableStatement statement, CommandBehavior behavior) { this.Command = cmd; this._connection = this.Command.Connection; this.CommandBehavior = behavior; this.driver = this._connection.driver; this.affectedRows = -1; this.Statement = statement; if (cmd.CommandType == CommandType.StoredProcedure && cmd.UpdatedRowSource == UpdateRowSource.FirstReturnedRecord ) { this._disableZeroAffectedRows = true; } } #region Properties internal PreparableStatement Statement { get; } internal MySqlCommand Command { get; private set; } internal ResultSet ResultSet { get; private set; } internal CommandBehavior CommandBehavior { get; private set; } /// /// Gets the number of columns in the current row. /// public override int FieldCount => this.ResultSet?.Size ?? 0; /// /// Gets a value indicating whether the MySqlDataReader contains one or more rows. /// public override bool HasRows => this.ResultSet?.HasRows ?? false; /// /// Gets a value indicating whether the data reader is closed. /// public override bool IsClosed => !this._isOpen; /// /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. /// public override int RecordsAffected { // RecordsAffected returns the number of rows affected in batch // statments from insert/delete/update statments. This property // is not completely accurate until .Close() has been called. get { if (!this._disableZeroAffectedRows) { return (int)this.affectedRows; } // In special case of updating stored procedure called from // within data adapter, we return -1 to avoid exceptions // (s. Bug#54895) if (this.affectedRows == 0) { return -1; } return (int)this.affectedRows; } } /// /// Overloaded. Gets the value of a column in its native format. /// In C#, this property is the indexer for the MySqlDataReader class. /// public override object this[int i] => this.GetValue(i); /// /// Gets the value of a column in its native format. /// [C#] In C#, this property is the indexer for the MySqlDataReader class. /// public override object this[String name] => this[this.GetOrdinal(name)]; /// /// Gets a value indicating the depth of nesting for the current row. This method is not /// supported currently and always returns 0. /// public override int Depth => 0; #endregion /// /// Closes the MySqlDataReader object. /// public override void Close() { if (!this._isOpen) { return; } bool shouldCloseConnection = (this.CommandBehavior & CommandBehavior.CloseConnection) != 0; CommandBehavior originalBehavior = this.CommandBehavior; // clear all remaining resultsets try { // Temporarily change to Default behavior to allow NextResult to finish properly. if (!originalBehavior.Equals(CommandBehavior.SchemaOnly)) { this.CommandBehavior = CommandBehavior.Default; } while (this.NextResult()) { } } catch (MySqlException ex) { // Ignore aborted queries if (!ex.IsQueryAborted) { // ignore IO exceptions. // We are closing or disposing reader, and do not // want exception to be propagated to used. If socket is // is closed on the server side, next query will run into // IO exception. If reader is closed by GC, we also would // like to avoid any exception here. bool isIOException = false; for (Exception exception = ex; exception != null; exception = exception.InnerException) { if (exception is System.IO.IOException) { isIOException = true; break; } } if (!isIOException) { // Ordinary exception (neither IO nor query aborted) throw; } } } catch (System.IO.IOException) { // eat, on the same reason we eat IO exceptions wrapped into // MySqlExceptions reasons, described above. } finally { // always ensure internal reader is null (Bug #55558) this._connection.Reader = null; this.CommandBehavior = originalBehavior; } // we now give the command a chance to terminate. In the case of // stored procedures it needs to update out and inout parameters this.Command.Close(this); this.CommandBehavior = CommandBehavior.Default; if (this.Command.Canceled && this._connection.driver.Version.isAtLeast(5, 1, 0)) { // Issue dummy command to clear kill flag this.ClearKillFlag(); } if (shouldCloseConnection) { this._connection.Close(); } this.Command = null; this._connection.IsInUse = false; this._connection = null; this._isOpen = false; } #region TypeSafe Accessors /// /// Gets the value of the specified column as a Boolean. /// /// /// public bool GetBoolean(string name) { return this.GetBoolean(this.GetOrdinal(name)); } /// /// Gets the value of the specified column as a Boolean. /// /// /// public override bool GetBoolean(int i) { var asValue = this.GetValue(i); int numericValue; if (int.TryParse(asValue as string, out numericValue)) { return Convert.ToBoolean(numericValue); } return Convert.ToBoolean(asValue); } /// /// Gets the value of the specified column as a byte. /// /// /// public byte GetByte(string name) { return this.GetByte(this.GetOrdinal(name)); } /// /// Gets the value of the specified column as a byte. /// /// /// public override byte GetByte(int i) { IMySqlValue v = this.GetFieldValue(i, false); if (v is MySqlUByte) { return ((MySqlUByte)v).Value; } else { return (byte)((MySqlByte)v).Value; } } /// /// Gets the value of the specified column as a sbyte. /// /// /// public sbyte GetSByte(string name) { return this.GetSByte(this.GetOrdinal(name)); } /// /// Gets the value of the specified column as a sbyte. /// /// /// public sbyte GetSByte(int i) { IMySqlValue v = this.GetFieldValue(i, false); if (v is MySqlByte) { return ((MySqlByte)v).Value; } else { return (sbyte)((MySqlByte)v).Value; } } /// /// Reads a stream of bytes from the specified column offset into the buffer an array starting at the given buffer offset. /// /// The zero-based column ordinal. /// The index within the field from which to begin the read operation. /// The buffer into which to read the stream of bytes. /// The index for buffer to begin the read operation. /// The maximum length to copy into the buffer. /// The actual number of bytes read. /// public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) { if (i >= this.FieldCount) { this.Throw(new IndexOutOfRangeException()); } IMySqlValue val = this.GetFieldValue(i, false); if (!(val is MySqlBinary) && !(val is MySqlGuid)) { this.Throw(new MySqlException("GetBytes can only be called on binary or guid columns")); } byte[] bytes = null; if (val is MySqlBinary) { bytes = ((MySqlBinary)val).Value; } else { bytes = ((MySqlGuid)val).Bytes; } if (buffer == null) { return bytes.Length; } if (bufferoffset >= buffer.Length || bufferoffset < 0) { this.Throw(new IndexOutOfRangeException("Buffer index must be a valid index in buffer")); } if (buffer.Length < (bufferoffset + length)) { this.Throw(new ArgumentException("Buffer is not large enough to hold the requested data")); } if (fieldOffset < 0 || ((ulong)fieldOffset >= (ulong)bytes.Length && (ulong)bytes.Length > 0)) { this.Throw(new IndexOutOfRangeException("Data index must be a valid index in the field")); } // adjust the length so we don't run off the end if ((ulong)bytes.Length < (ulong)(fieldOffset + length)) { length = (int)((ulong)bytes.Length - (ulong)fieldOffset); } Buffer.BlockCopy(bytes, (int)fieldOffset, buffer, (int)bufferoffset, (int)length); return length; } private object ChangeType(IMySqlValue value, int fieldIndex, Type newType) { this.ResultSet.Fields[fieldIndex].AddTypeConversion(newType); return Convert.ChangeType(value.Value, newType, CultureInfo.InvariantCulture); } /// /// Gets the value of the specified column as a single character. /// /// /// public char GetChar(string name) { return this.GetChar(this.GetOrdinal(name)); } /// /// Gets the value of the specified column as a single character. /// /// /// public override char GetChar(int i) { string s = this.GetString(i); return s[0]; } /// /// Reads a stream of characters from the specified column offset into the buffer as an array starting at the given buffer offset. /// /// /// /// /// /// /// public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) { if (i >= this.FieldCount) { this.Throw(new IndexOutOfRangeException()); } string valAsString = this.GetString(i); if (buffer == null) { return valAsString.Length; } if (bufferoffset >= buffer.Length || bufferoffset < 0) { this.Throw(new IndexOutOfRangeException("Buffer index must be a valid index in buffer")); } if (buffer.Length < (bufferoffset + length)) { this.Throw(new ArgumentException("Buffer is not large enough to hold the requested data")); } if (fieldoffset < 0 || fieldoffset >= valAsString.Length) { this.Throw(new IndexOutOfRangeException("Field offset must be a valid index in the field")); } if (valAsString.Length < length) { length = valAsString.Length; } valAsString.CopyTo((int)fieldoffset, buffer, bufferoffset, length); return length; } /// /// Gets the name of the source data type. /// /// /// public override String GetDataTypeName(int i) { if (!this._isOpen) { this.Throw(new Exception("No current query in data reader")); } if (i >= this.FieldCount) { this.Throw(new IndexOutOfRangeException()); } // return the name of the type used on the backend IMySqlValue v = this.ResultSet.Values[i]; return v.MySqlTypeName; } /// public MySqlDateTime GetMySqlDateTime(string column) { return this.GetMySqlDateTime(this.GetOrdinal(column)); } /// public MySqlDateTime GetMySqlDateTime(int column) { return (MySqlDateTime)this.GetFieldValue(column, true); } /// public DateTime GetDateTime(string column) { return this.GetDateTime(this.GetOrdinal(column)); } /// public override DateTime GetDateTime(int i) { IMySqlValue val = this.GetFieldValue(i, true); MySqlDateTime dt; if (val is MySqlDateTime) { dt = (MySqlDateTime)val; } else { // we need to do this because functions like date_add return string string s = this.GetString(i); dt = MySqlDateTime.Parse(s); } dt.TimezoneOffset = this.driver.timeZoneOffset; if (this._connection.Settings.ConvertZeroDateTime && !dt.IsValidDateTime) { return DateTime.MinValue; } else { return dt.GetDateTime(); } } /// /// Gets the value of the specified column as a . /// /// The name of the colum. /// The value of the specified column as a . public MySqlDecimal GetMySqlDecimal(string column) { return this.GetMySqlDecimal(this.GetOrdinal(column)); } /// /// Gets the value of the specified column as a . /// /// The index of the colum. /// The value of the specified column as a . public MySqlDecimal GetMySqlDecimal(int i) { return (MySqlDecimal)this.GetFieldValue(i, false); } /// public Decimal GetDecimal(string column) { return this.GetDecimal(this.GetOrdinal(column)); } /// public override Decimal GetDecimal(int i) { IMySqlValue v = this.GetFieldValue(i, true); if (v is MySqlDecimal) { return ((MySqlDecimal)v).Value; } return Convert.ToDecimal(v.Value); } /// public double GetDouble(string column) { return this.GetDouble(this.GetOrdinal(column)); } /// public override double GetDouble(int i) { IMySqlValue v = this.GetFieldValue(i, true); if (v is MySqlDouble) { return ((MySqlDouble)v).Value; } return Convert.ToDouble(v.Value); } public Type GetFieldType(string column) { return this.GetFieldType(this.GetOrdinal(column)); } /// /// Gets the Type that is the data type of the object. /// /// /// public override Type GetFieldType(int i) { if (!this._isOpen) { this.Throw(new Exception("No current query in data reader")); } if (i >= this.FieldCount) { this.Throw(new IndexOutOfRangeException()); } // we have to use the values array directly because we can't go through // GetValue IMySqlValue v = this.ResultSet.Values[i]; if (v is MySqlDateTime) { if (!this._connection.Settings.AllowZeroDateTime) { return typeof(DateTime); } return typeof(MySqlDateTime); } return v.SystemType; } /// public float GetFloat(string column) { return this.GetFloat(this.GetOrdinal(column)); } /// public override float GetFloat(int i) { IMySqlValue v = this.GetFieldValue(i, true); if (v is MySqlSingle) { return ((MySqlSingle)v).Value; } return Convert.ToSingle(v.Value); } public string GetBodyDefinition(string column) { var value = this.GetValue(this.GetOrdinal(column)); if (value.GetType().FullName.Equals("System.Byte[]")) { return this.GetString(column); } else { return this.GetValue(this.GetOrdinal(column)).ToString(); } } /// /// Gets the value of the specified column as a globally-unique identifier(GUID). /// /// The name of the column. /// public Guid GetGuid(string column) { return this.GetGuid(this.GetOrdinal(column)); } /// public override Guid GetGuid(int i) { object v = this.GetValue(i); if (v is Guid) { return (Guid)v; } if (v is string) { return new Guid(v as string); } if (v is byte[]) { byte[] bytes = (byte[])v; if (bytes.Length == 16) { return new Guid(bytes); } } this.Throw(new MySqlException(Resources.ValueNotSupportedForGuid)); return Guid.Empty; // just to silence compiler } /// public Int16 GetInt16(string column) { return this.GetInt16(this.GetOrdinal(column)); } /// public override Int16 GetInt16(int i) { IMySqlValue v = this.GetFieldValue(i, true); if (v is MySqlInt16) { return ((MySqlInt16)v).Value; } return (short)this.ChangeType(v, i, typeof(short)); } /// public Int32 GetInt32(string column) { return this.GetInt32(this.GetOrdinal(column)); } /// public override Int32 GetInt32(int i) { IMySqlValue v = this.GetFieldValue(i, true); if (v is MySqlInt32) { return ((MySqlInt32)v).Value; } return (Int32)this.ChangeType(v, i, typeof(Int32)); } /// public Int64 GetInt64(string column) { return this.GetInt64(this.GetOrdinal(column)); } /// public override Int64 GetInt64(int i) { IMySqlValue v = this.GetFieldValue(i, true); if (v is MySqlInt64) { return ((MySqlInt64)v).Value; } return (Int64)this.ChangeType(v, i, typeof(Int64)); } /// /// Gets the name of the specified column. /// /// /// public override String GetName(int i) { if (!this._isOpen) { this.Throw(new Exception("No current query in data reader")); } if (i >= this.FieldCount) { this.Throw(new IndexOutOfRangeException()); } return this.ResultSet.Fields[i].ColumnName; } /// /// Gets the column ordinal, given the name of the column. /// /// /// public override int GetOrdinal(string name) { if (!this._isOpen || this.ResultSet == null) { this.Throw(new Exception("No current query in data reader")); } return this.ResultSet.GetOrdinal(name); } /// public string GetString(string column) { return this.GetString(this.GetOrdinal(column)); } /// public override String GetString(int i) { IMySqlValue val = this.GetFieldValue(i, true); if (val is MySqlBinary) { byte[] v = ((MySqlBinary)val).Value; return this.ResultSet.Fields[i].Encoding.GetString(v, 0, v.Length); } return val.Value.ToString(); } /// public TimeSpan GetTimeSpan(string column) { return this.GetTimeSpan(this.GetOrdinal(column)); } /// public TimeSpan GetTimeSpan(int column) { IMySqlValue val = this.GetFieldValue(column, true); MySqlTimeSpan ts = (MySqlTimeSpan)val; return ts.Value; } /// /// Gets the value of the specified column in its native format. /// /// /// public override object GetValue(int i) { if (!this._isOpen) { this.Throw(new Exception("No current query in data reader")); } if (i >= this.FieldCount) { this.Throw(new IndexOutOfRangeException()); } IMySqlValue val = this.GetFieldValue(i, false); if (val.IsNull) { if (!(val.MySqlDbType == MySqlDbType.Time && val.Value.ToString() == "00:00:00")) { return DBNull.Value; } } // if the column is a date/time, then we return a MySqlDateTime // so .ToString() will print '0000-00-00' correctly if (val is MySqlDateTime) { MySqlDateTime dt = (MySqlDateTime)val; if (!dt.IsValidDateTime && this._connection.Settings.ConvertZeroDateTime) { return DateTime.MinValue; } else if (this._connection.Settings.AllowZeroDateTime) { return val; } else { return dt.GetDateTime(); } } return val.Value; } /// /// Gets all attribute columns in the collection for the current row. /// /// /// public override int GetValues(object[] values) { int numCols = Math.Min(values.Length, this.FieldCount); for (int i = 0; i < numCols; i++) { values[i] = this.GetValue(i); } return numCols; } /// public UInt16 GetUInt16(string column) { return this.GetUInt16(this.GetOrdinal(column)); } /// public UInt16 GetUInt16(int column) { IMySqlValue v = this.GetFieldValue(column, true); if (v is MySqlUInt16) { return ((MySqlUInt16)v).Value; } return (UInt16)this.ChangeType(v, column, typeof(UInt16)); } /// public UInt32 GetUInt32(string column) { return this.GetUInt32(this.GetOrdinal(column)); } /// public UInt32 GetUInt32(int column) { IMySqlValue v = this.GetFieldValue(column, true); if (v is MySqlUInt32) { return ((MySqlUInt32)v).Value; } return (uint)this.ChangeType(v, column, typeof(UInt32)); } /// public UInt64 GetUInt64(string column) { return this.GetUInt64(this.GetOrdinal(column)); } /// public UInt64 GetUInt64(int column) { IMySqlValue v = this.GetFieldValue(column, true); if (v is MySqlUInt64) { return ((MySqlUInt64)v).Value; } return (UInt64)this.ChangeType(v, column, typeof(UInt64)); } #endregion IDataReader IDataRecord.GetData(int i) { return base.GetData(i); } /// /// Gets a value indicating whether the column contains non-existent or missing values. /// /// /// public override bool IsDBNull(int i) { return DBNull.Value == this.GetValue(i); } /// /// Advances the data reader to the next result, when reading the results of batch SQL statements. /// /// public override bool NextResult() { if (!this._isOpen) { this.Throw(new MySqlException(Resources.NextResultIsClosed)); } bool isCaching = this.Command.CommandType == CommandType.TableDirect && this.Command.EnableCaching && (this.CommandBehavior & CommandBehavior.SequentialAccess) == 0; // this will clear out any unread data if (this.ResultSet != null) { this.ResultSet.Close(); if (isCaching) { TableCache.AddToCache(this.Command.CommandText, this.ResultSet); } } // single result means we only return a single resultset. If we have already // returned one, then we return false // TableDirect is basically a select * from a single table so it will generate // a single result also if (this.ResultSet != null && ((this.CommandBehavior & CommandBehavior.SingleResult) != 0 || isCaching)) { return false; } // next load up the next resultset if any try { do { this.ResultSet = null; // if we are table caching, then try to retrieve the resultSet from the cache if (isCaching) { this.ResultSet = TableCache.RetrieveFromCache( this.Command.CommandText, this.Command.CacheAge); } if (this.ResultSet == null) { this.ResultSet = this.driver.NextResult(this.Statement.StatementId, false); if (this.ResultSet == null) { return false; } if (this.ResultSet.IsOutputParameters && this.Command.CommandType == CommandType.StoredProcedure) { StoredProcedure sp = this.Statement as StoredProcedure; sp.ProcessOutputParameters(this); this.ResultSet.Close(); for (int i = 0; i < this.ResultSet.Fields.Length; i++) { if (this.ResultSet.Fields[i].ColumnName.StartsWith("@" + StoredProcedure.ParameterPrefix, StringComparison.OrdinalIgnoreCase)) { this.ResultSet = null; break; } } if (!sp.ServerProvidingOutputParameters) { return false; } // if we are using server side output parameters then we will get our ok packet // *after* the output parameters resultset this.ResultSet = this.driver.NextResult(this.Statement.StatementId, true); } this.ResultSet.Cached = isCaching; } if (this.ResultSet.Size == 0) { this.Command.LastInsertedId = this.ResultSet.InsertedId; if (this.affectedRows == -1) { this.affectedRows = this.ResultSet.AffectedRows; } else { this.affectedRows += this.ResultSet.AffectedRows; } } } while (this.ResultSet.Size == 0); return true; } catch (MySqlException ex) { if (ex.IsFatal) { this._connection.Abort(); } if (ex.Number == 0) { throw new MySqlException(Resources.FatalErrorReadingResult, ex); } if ((this.CommandBehavior & CommandBehavior.CloseConnection) != 0) { this.Close(); } throw; } } /// /// Advances the MySqlDataReader to the next record. /// /// public override bool Read() { if (!this._isOpen) { this.Throw(new MySqlException("Invalid attempt to Read when reader is closed.")); } if (this.ResultSet == null) { return false; } try { return this.ResultSet.NextRow(this.CommandBehavior); } catch (TimeoutException tex) { this._connection.HandleTimeoutOrThreadAbort(tex); throw; // unreached } catch (ThreadAbortException taex) { this._connection.HandleTimeoutOrThreadAbort(taex); throw; } catch (MySqlException ex) { if (ex.IsFatal) { this._connection.Abort(); } if (ex.IsQueryAborted) { throw; } throw new MySqlException(Resources.FatalErrorDuringRead, ex); } } /// /// Gets the value of the specified column as a . /// /// The index of the colum. /// The value of the specified column as a . public MySqlGeometry GetMySqlGeometry(int i) { try { IMySqlValue v = this.GetFieldValue(i, false); if (v is MySqlGeometry || v is MySqlBinary) { return new MySqlGeometry(MySqlDbType.Geometry, (Byte[])v.Value); } } catch { this.Throw(new Exception("Can't get MySqlGeometry from value")); } return new MySqlGeometry(true); } /// /// Gets the value of the specified column as a . /// /// The name of the colum. /// The value of the specified column as a . public MySqlGeometry GetMySqlGeometry(string column) { return this.GetMySqlGeometry(this.GetOrdinal(column)); } /// /// Returns an that iterates through the . /// public override IEnumerator GetEnumerator() { return new DbEnumerator(this, (this.CommandBehavior & CommandBehavior.CloseConnection) != 0); } private IMySqlValue GetFieldValue(int index, bool checkNull) { if (index < 0 || index >= this.FieldCount) { this.Throw(new ArgumentException(Resources.InvalidColumnOrdinal)); } IMySqlValue v = this.ResultSet[index]; if (!(v.MySqlDbType is MySqlDbType.Time && v.Value.ToString() == "00:00:00")) { if (checkNull && v.IsNull) { throw new System.Data.SqlTypes.SqlNullValueException(); } } return v; } private void ClearKillFlag() { // This query will silently crash because of the Kill call that happened before. string dummyStatement = "SELECT * FROM bogus_table LIMIT 0"; /* dummy query used to clear kill flag */ MySqlCommand dummyCommand = new MySqlCommand(dummyStatement, this._connection) { InternallyCreated = true }; try { dummyCommand.ExecuteReader(); // ExecuteReader catches the exception and returns null, which is expected. } catch (MySqlException ex) { int[] errors = { (int)MySqlErrorCode.NoSuchTable, (int)MySqlErrorCode.TableAccessDenied, (int)MySqlErrorCode.UnknownTable }; if (Array.IndexOf(errors, (int)ex.Number) < 0) { throw; } } } private void ProcessOutputParameters() { // if we are not 5.5 or later or we are not prepared then we are simulating output parameters // with user variables and they are also string so we have to work some magic with out // column types before we read the data if (!this.driver.SupportsOutputParameters || !this.Command.IsPrepared) { this.AdjustOutputTypes(); } // now read the output parameters data row if ((this.CommandBehavior & CommandBehavior.SchemaOnly) != 0) { return; } this.ResultSet.NextRow(this.CommandBehavior); string prefix = "@" + StoredProcedure.ParameterPrefix; for (int i = 0; i < this.FieldCount; i++) { string fieldName = this.GetName(i); if (fieldName.StartsWith(prefix)) { fieldName = fieldName.Remove(0, prefix.Length); } MySqlParameter parameter = this.Command.Parameters.GetParameterFlexible(fieldName, true); parameter.Value = this.GetValue(i); } } private void AdjustOutputTypes() { // since MySQL likes to return user variables as strings // we reset the types of the readers internal value objects // this will allow those value objects to parse the string based // return values for (int i = 0; i < this.FieldCount; i++) { string fieldName = this.GetName(i); fieldName = fieldName.Remove(0, StoredProcedure.ParameterPrefix.Length + 1); MySqlParameter parameter = this.Command.Parameters.GetParameterFlexible(fieldName, true); IMySqlValue v = MySqlField.GetIMySqlValue(parameter.MySqlDbType); if (v is MySqlBit) { MySqlBit bit = (MySqlBit)v; bit.ReadAsString = true; this.ResultSet.SetValueObject(i, bit); } else { this.ResultSet.SetValueObject(i, v); } } } public override T GetFieldValue(int ordinal) { if (typeof(T).Equals(typeof(DateTimeOffset))) { var dtValue = new DateTime(); var result = DateTime.TryParse(this.GetValue(ordinal).ToString(), out dtValue); DateTime datetime = result ? dtValue : DateTime.MinValue; return (T)Convert.ChangeType(new DateTimeOffset(datetime), typeof(T)); } else { return base.GetFieldValue(ordinal); } } private void Throw(Exception ex) { this._connection?.Throw(ex); throw ex; } public new void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } internal new void Dispose(bool disposing) { if (disposing) { this.Close(); } } #region Destructor ~MySqlDataReader() { this.Dispose(false); } #endregion } }