How to format SQL Server DateTimeOffset and move t

2019-07-11 17:23发布

问题:

I'm trying to move SQL Server DateTimeOffset or Oracle using OracleBulkCopy.

For DateTimeOffset(1) the target data type is Timestamp(1) With Time Zone.

If I do a select of the DateTimeOffset(1) column on SQL Server I receive the following:

2007-05-08 12:35:29.1 +12:15

When I try to move this to Oracle I receive:

ORA-01843: not a valid month

It makes sense and I believe the month needs to first, but If I run INSERT into MyOracleTable values('2007-05-08 12:35:29.1 +12:15') I can insert just fine.

I've tried Converting the datetimeoffset to various formats on the SQL Server side. I receive various errors one being:

ORA-01855: AM/A.M. or PM/P.M. required

The NLS_TIMESTAMP_TZ_FORMAT parameter is:

YYYY-MM-DD HH24:MI:SSXFF TZR

Thank you for your help!

回答1:

My idea is to create a wrapper for SqlDataReader and do the conversion inside:

using (SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=test;Integrated Security=True;"))
{
    conn.Open();
    using (SqlCommand cmd = new SqlCommand("Select * from table_2", conn))
    {
        using (SqlDataReader sqlReader = cmd.ExecuteReader())
        {
            using (ReaderWrapper reader = new ReaderWrapper(sqlReader))
            {
                using (OracleBulkCopy bulkCopy = new OracleBulkCopy("data source=oracle;user id=user;password=secret", OracleBulkCopyOptions.Default))
                {
                    bulkCopy.DestinationTableName = "tb_date";
                    bulkCopy.WriteToServer(reader);
                }
            }
        }
    }
}

The wrapper itself is bulky, but very simple:

    internal sealed class ReaderWrapper : IDataReader
{
    #region Fields

    private bool _disposed;

    private IDataReader _reader;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="ReaderWrapper"/> class.
    /// </summary>
    /// <param name="reader">The wrapped reader.</param>
    public ReaderWrapper(IDataReader reader)
    {
        this._reader = reader;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets the number of columns in the current row.
    /// </summary>
    /// <value></value>
    /// <returns>
    /// When not positioned in a valid recordset, 0; otherwise, the number of columns in the current record. The default is -1.
    /// </returns>
    public int FieldCount
    {
        get { return this._reader.FieldCount; }
    }

    #endregion

    #region Indexers

    /// <summary>
    /// Gets the column located at the specified index.
    /// </summary>
    /// <param name="i">The zero-based index of the column to get.</param>
    /// <returns>The column located at the specified index.</returns>
    /// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="FieldCount"/>.</exception>
    public object this[int i]
    {
        get { return this.GetValue(i); }
    }

    /// <summary>
    /// Gets the column with the specified name.
    /// </summary>
    /// <param name="name">The name of the column to find.</param>
    /// <returns>The column with the specified name.</returns>
    /// <exception cref="System.IndexOutOfRangeException">No column with the specified name was found.</exception>
    public object this[string name]
    {
        get { return this._reader[name]; }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Advances the <see cref="T:System.Data.IDataReader"/> to the next record.
    /// </summary>
    /// <returns>
    /// <see langword="true"/> if there are more rows; otherwise, <see langword="false"/>.
    /// </returns>
    public bool Read()
    {
        return this._reader.Read();
    }

    /// <summary>
    /// Return the value of the specified field.
    /// </summary>
    /// <param name="i">The index of the field to find.</param>
    /// <returns>
    /// The <see cref="T:System.Object"/> which will contain the field value upon return.
    /// </returns>
    /// <exception cref="T:System.IndexOutOfRangeException">
    /// The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"/>.
    /// </exception>
    public object GetValue(int i)
    {
        object sqlValue = this._reader[i];
        DateTimeOffset? dateValue = sqlValue as DateTimeOffset?;
        if (dateValue == null)
        {
            return sqlValue;
        }

        // Ensure that DateTimeOffset can be converted to Oracle
        OracleTimeStampTZ oracleDate = new OracleTimeStampTZ(dateValue.Value.UtcDateTime, dateValue.Value.Offset.ToString());
        return oracleDate;
    }

    /// <summary>
    /// Gets the <see cref="T:System.Type" /> information corresponding to the type of <see cref="T:System.Object" /> that would be returned from <see cref="M:System.Data.IDataRecord.GetValue(System.Int32)" />.
    /// </summary>
    /// <param name="i">The index of the field to find.</param>
    /// <returns>
    /// The <see cref="T:System.Type" /> information corresponding to the type of <see cref="T:System.Object" /> that would be returned from <see cref="M:System.Data.IDataRecord.GetValue(System.Int32)" />.
    /// </returns>
    /// <exception cref="NotSupportedException"></exception>
    Type IDataRecord.GetFieldType(int i)
    {
        return this._reader.GetFieldType(i);
    }

    public int GetOrdinal(string name)
    {
        return this._reader.GetOrdinal(name);
    }

    public bool IsDBNull(int i)
    {
        return this.IsDBNull(i);
    }

    /// <summary>
    /// Closes the <see cref="T:System.Data.IDataReader"/> Object.
    /// </summary>
    public void Close()
    {
        this._reader.Dispose();
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, 
    /// releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        if (!this._disposed)
        {
            this._reader.Dispose();
            this._disposed = true;
        }
    }

    #endregion

    #region Not Implemented

    bool IDataReader.IsClosed
    {
        get { throw new NotSupportedException(); }
    }

    void IDataReader.Close()
    {
        throw new NotSupportedException();
    }

    int IDataReader.Depth
    {
        get { throw new NotSupportedException(); }
    }

    DataTable IDataReader.GetSchemaTable()
    {
        throw new NotSupportedException();
    }

    bool IDataReader.NextResult()
    {
        throw new NotSupportedException();
    }

    int IDataReader.RecordsAffected
    {
        get { throw new NotSupportedException(); }
    }

    bool IDataRecord.GetBoolean(int i)
    {
        throw new NotSupportedException();
    }

    byte IDataRecord.GetByte(int i)
    {
        throw new NotSupportedException();
    }

    long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
    {
        throw new NotSupportedException();
    }

    char IDataRecord.GetChar(int i)
    {
        throw new NotSupportedException();
    }

    long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
    {
        throw new NotSupportedException();
    }

    IDataReader IDataRecord.GetData(int i)
    {
        throw new NotSupportedException();
    }

    string IDataRecord.GetDataTypeName(int i)
    {
        throw new NotSupportedException();
    }

    DateTime IDataRecord.GetDateTime(int i)
    {
        throw new NotSupportedException();
    }

    decimal IDataRecord.GetDecimal(int i)
    {
        throw new NotSupportedException();
    }

    double IDataRecord.GetDouble(int i)
    {
        throw new NotSupportedException();
    }

    float IDataRecord.GetFloat(int i)
    {
        throw new NotSupportedException();
    }

    Guid IDataRecord.GetGuid(int i)
    {
        throw new NotSupportedException();
    }

    short IDataRecord.GetInt16(int i)
    {
        throw new NotSupportedException();
    }

    int IDataRecord.GetInt32(int i)
    {
        throw new NotSupportedException();
    }

    long IDataRecord.GetInt64(int i)
    {
        throw new NotSupportedException();
    }

    string IDataRecord.GetName(int i)
    {
        throw new NotSupportedException();
    }

    string IDataRecord.GetString(int i)
    {
        throw new NotSupportedException();
    }

    int IDataRecord.GetValues(object[] values)
    {
        throw new NotSupportedException();
    }

    #endregion

The actual conversion is done here:

public object GetValue(int i)
{
    object sqlValue = this._reader[i];
    DateTimeOffset? dateValue = sqlValue as DateTimeOffset?;
    if (dateValue == null)
    {
        return sqlValue;
    }

    // Ensure that DateTimeOffset can be converted to Oracle
    OracleTimeStampTZ oracleDate = new OracleTimeStampTZ(dateValue.Value.UtcDateTime, dateValue.Value.Offset.ToString());
    return oracleDate;
}