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!
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;
}