Files
Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/BulkDataReader.cs
2022-01-18 15:23:53 +00:00

1508 lines
60 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.SqlClient;
namespace Umbraco.Cms.Infrastructure.Persistence
{
/// <summary>
/// A base implementation of <see cref="IDataReader"/> that is suitable for <see cref="SqlBulkCopy.WriteToServer(IDataReader)"/>.
/// </summary>
/// <remarks>
///
/// Borrowed from Microsoft:
/// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/
///
/// This implementation is designed to be very memory efficient requiring few memory resources and to support
/// rapid transfer of data to SQL Server.
///
/// Subclasses should implement <see cref="BulkDataReader.SchemaName"/>, <see cref="BulkDataReader.TableName"/>,
/// <see cref="BulkDataReader.AddSchemaTableRows()"/>, <see cref="BulkDataReader.Read()"/>, <see cref="BulkDataReader.GetValue(Int32)"/>.
/// If they contain disposable resources they should override <see cref="BulkDataReader.Dispose(Boolean)"/>.
///
/// SD: Alternatively, we could have used a LinqEntityDataReader which is nicer to use but it uses quite a lot of reflection and
/// I thought this would just be quicker.
/// Simple example of that: https://github.com/gridsum/DataflowEx/blob/master/Gridsum.DataflowEx/Databases/BulkDataReader.cs
/// Full example of that: https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs
/// So we know where to find that if we ever need it, these would convert any Linq data source to an IDataReader
///
/// </remarks>
internal abstract class BulkDataReader : IDataReader
{
#region Fields
/// <summary>
/// The <see cref="DataTable"/> containing the input row set's schema information <see cref="SqlBulkCopy.WriteToServer(IDataReader)"/>
/// requires to function correctly.
/// </summary>
private DataTable _schemaTable = new DataTable();
/// <summary>
/// The mapping from the row set input to the target table's columns.
/// </summary>
private List<SqlBulkCopyColumnMapping> _columnMappings = new List<SqlBulkCopyColumnMapping>();
#endregion
#region Subclass utility routines
/// <summary>
/// The mapping from the row set input to the target table's columns.
/// </summary>
/// <remarks>
/// If necessary, <see cref="BulkDataReader.AddSchemaTableRows()"/> will be called to initialize the mapping.
/// </remarks>
public ReadOnlyCollection<SqlBulkCopyColumnMapping> ColumnMappings
{
get
{
if (this._columnMappings.Count == 0)
{
// Need to add the column definitions and mappings.
AddSchemaTableRows();
if (this._columnMappings.Count == 0)
{
throw new InvalidOperationException("AddSchemaTableRows did not add rows.");
}
Debug.Assert(this._schemaTable.Rows.Count == FieldCount);
}
return new ReadOnlyCollection<SqlBulkCopyColumnMapping>(_columnMappings);
}
}
/// <summary>
/// The name of the input row set's schema.
/// </summary>
/// <remarks>
/// This may be different from the target schema but usually they are identical.
/// </remarks>
protected abstract string SchemaName
{
get;
}
/// <summary>
/// The name of the input row set's table.
/// </summary>
/// <remarks>
/// This may be different from the target table but usually they are identical.
/// </remarks>
protected abstract string TableName
{
get;
}
/// <summary>
/// Adds the input row set's schema to the object.
/// </summary>
/// <remarks>
/// Call <see cref="AddSchemaTableRow(String,Nullable{Int32},Nullable{Int16},Nullable{Int16},Boolean,Boolean,Boolean,SqlDbType,String,String,String,String,String)"/>
/// to do this for each row.
/// </remarks>
/// <seealso cref="AddSchemaTableRow(String,Nullable{Int32},Nullable{Int16},Nullable{Int16},Boolean,Boolean,Boolean,SqlDbType,String,String,String,String,String)"/>
protected abstract void AddSchemaTableRows();
/// <summary>
/// For each <see cref="SqlDbType"/>, the optional columns that may have values.
/// </summary>
/// <remarks>
/// This is used for checking the parameters of <see cref="AddSchemaTableRow(String,Nullable{Int32},Nullable{Int16},Nullable{Int16},Boolean,Boolean,Boolean,SqlDbType,String,String,String,String,String)"/>.
/// </remarks>
/// <seealso cref="AddSchemaTableRow(String,Nullable{Int32},Nullable{Int16},Nullable{Int16},Boolean,Boolean,Boolean,SqlDbType,String,String,String,String,String)"/>
private static readonly Dictionary<SqlDbType, List<string>> AllowedOptionalColumnCombinations = new Dictionary<SqlDbType, List<string>>
{
{ SqlDbType.BigInt, new List<string> { } },
{ SqlDbType.Binary, new List<string> { SchemaTableColumn.ColumnSize } },
{ SqlDbType.Bit, new List<string> { } },
{ SqlDbType.Char, new List<string> { SchemaTableColumn.ColumnSize } },
{ SqlDbType.Date, new List<string> { } },
{ SqlDbType.DateTime, new List<string> { } },
{ SqlDbType.DateTime2, new List<string> { SchemaTableColumn.NumericPrecision } },
{ SqlDbType.DateTimeOffset, new List<string> { SchemaTableColumn.NumericPrecision } },
{ SqlDbType.Decimal, new List<string> { SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale } },
{ SqlDbType.Float, new List<string> { SchemaTableColumn.NumericPrecision, SchemaTableColumn.NumericScale } },
{ SqlDbType.Image, new List<string> { } },
{ SqlDbType.Int, new List<string> { } },
{ SqlDbType.Money, new List<string> { } },
{ SqlDbType.NChar, new List<string> { SchemaTableColumn.ColumnSize } },
{ SqlDbType.NText, new List<string> { } },
{ SqlDbType.NVarChar, new List<string> { SchemaTableColumn.ColumnSize } },
{ SqlDbType.Real, new List<string> { } },
{ SqlDbType.SmallDateTime, new List<string> { } },
{ SqlDbType.SmallInt, new List<string> { } },
{ SqlDbType.SmallMoney, new List<string> { } },
{ SqlDbType.Structured, new List<string> { } },
{ SqlDbType.Text, new List<string> { } },
{ SqlDbType.Time, new List<string> { SchemaTableColumn.NumericPrecision } },
{ SqlDbType.Timestamp, new List<string> { } },
{ SqlDbType.TinyInt, new List<string> { } },
{ SqlDbType.Udt, new List<string> { BulkDataReader.DataTypeNameSchemaColumn } },
{ SqlDbType.UniqueIdentifier, new List<string> { } },
{ SqlDbType.VarBinary, new List<string> { SchemaTableColumn.ColumnSize } },
{ SqlDbType.VarChar, new List<string> { SchemaTableColumn.ColumnSize } },
{ SqlDbType.Variant, new List<string> { } },
{ SqlDbType.Xml, new List<string> { BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, BulkDataReader.XmlSchemaCollectionNameSchemaColumn } }
};
/// <summary>
/// A helper method to support <see cref="AddSchemaTableRows"/>.
/// </summary>
/// <remarks>
/// This methods does extensive argument checks. These errors will cause hard to diagnose exceptions in latter
/// processing so it is important to detect them when they can be easily associated with the code defect.
/// </remarks>
/// <exception cref="ArgumentException">
/// The combination of values for the parameters is not supported.
/// </exception>
/// <exception cref="ArgumentNullException">
/// A null value for the parameter is not supported.
/// </exception>
/// <param name="columnName">
/// The name of the column.
/// </param>
/// <param name="columnSize">
/// The size of the column which may be null if not applicable.
/// </param>
/// <param name="numericPrecision">
/// The precision of the column which may be null if not applicable.
/// </param>
/// <param name="numericScale">
/// The scale of the column which may be null if not applicable.
/// </param>
/// <param name="isUnique">
/// Are the column values unique (i.e. never duplicated)?
/// </param>
/// <param name="isKey">
/// Is the column part of the primary key?
/// </param>
/// <param name="allowDbNull">
/// Is the column nullable (i.e. optional)?
/// </param>
/// <param name="providerType">
/// The corresponding <see cref="SqlDbType"/>.
/// </param>
/// <param name="udtSchema">
/// The schema name of the UDT.
/// </param>
/// <param name="udtType">
/// The type name of the UDT.
/// </param>
/// <param name="xmlSchemaCollectionDatabase">
/// For XML columns the schema collection's database name. Otherwise, null.
/// </param>
/// <param name="xmlSchemaCollectionOwningSchema">
/// For XML columns the schema collection's schema name. Otherwise, null.
/// </param>
/// <param name="xmlSchemaCollectionName">
/// For XML columns the schema collection's name. Otherwise, null.
/// </param>
/// <seealso cref="AddSchemaTableRows"/>
protected void AddSchemaTableRow(string columnName,
int? columnSize,
short? numericPrecision,
short? numericScale,
bool isUnique,
bool isKey,
bool allowDbNull,
SqlDbType providerType,
string udtSchema,
string udtType,
string xmlSchemaCollectionDatabase,
string xmlSchemaCollectionOwningSchema,
string xmlSchemaCollectionName)
{
if (string.IsNullOrEmpty(columnName))
{
throw new ArgumentException("columnName must be a nonempty string.");
}
else if (columnSize.HasValue && columnSize.Value <= 0)
{
throw new ArgumentOutOfRangeException("columnSize");
}
else if (numericPrecision.HasValue && numericPrecision.Value <= 0)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
else if (numericScale.HasValue && numericScale.Value < 0)
{
throw new ArgumentOutOfRangeException("columnSize");
}
List<string> allowedOptionalColumnList;
if (BulkDataReader.AllowedOptionalColumnCombinations.TryGetValue(providerType, out allowedOptionalColumnList))
{
if ((columnSize.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.ColumnSize)) ||
(numericPrecision.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericPrecision)) ||
(numericScale.HasValue && !allowedOptionalColumnList.Contains(SchemaTableColumn.NumericScale)) ||
(udtSchema != null && !allowedOptionalColumnList.Contains(BulkDataReader.DataTypeNameSchemaColumn)) ||
(udtType != null && !allowedOptionalColumnList.Contains(BulkDataReader.DataTypeNameSchemaColumn)) ||
(xmlSchemaCollectionDatabase != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn)) ||
(xmlSchemaCollectionOwningSchema != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn)) ||
(xmlSchemaCollectionName != null && !allowedOptionalColumnList.Contains(BulkDataReader.XmlSchemaCollectionNameSchemaColumn)))
{
throw new ArgumentException("Columns are set that are incompatible with the value of providerType.");
}
}
else
{
throw new ArgumentException("providerType is unsupported.");
}
Type dataType; // Corresponding CLR type.
string dataTypeName; // Corresponding SQL Server type.
bool isLong = false; // Is the column a large value column (e.g. nvarchar(max))?
switch (providerType)
{
case SqlDbType.BigInt:
dataType = typeof(long);
dataTypeName = "bigint";
break;
case SqlDbType.Binary:
dataType = typeof(byte[]);
if (!columnSize.HasValue)
{
throw new ArgumentException("columnSize must be specified for \"binary\" type columns.");
}
else if (columnSize > 8000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"binary({0})",
columnSize.Value);
break;
case SqlDbType.Bit:
dataType = typeof(bool);
dataTypeName = "bit";
break;
case SqlDbType.Char:
dataType = typeof(string);
if (!columnSize.HasValue)
{
throw new ArgumentException("columnSize must be specified for \"char\" type columns.");
}
else if (columnSize > 8000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"char({0})",
columnSize.Value);
break;
case SqlDbType.Date:
dataType = typeof(DateTime);
dataTypeName = "date";
break;
case SqlDbType.DateTime:
dataType = typeof(DateTime);
dataTypeName = "datetime";
break;
case SqlDbType.DateTime2:
dataType = typeof(DateTime);
if (numericPrecision.HasValue)
{
if (numericPrecision.Value > 7)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"datetime2({0})",
numericPrecision.Value);
}
else
{
dataTypeName = "datetime2";
}
break;
case SqlDbType.DateTimeOffset:
dataType = typeof(DateTimeOffset);
if (numericPrecision.HasValue)
{
if (numericPrecision.Value > 7)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"datetimeoffset({0})",
numericPrecision.Value);
}
else
{
dataTypeName = "datetimeoffset";
}
break;
case SqlDbType.Decimal:
dataType = typeof(decimal);
if (!numericPrecision.HasValue || !numericScale.HasValue)
{
throw new ArgumentException("numericPrecision and numericScale must be specified for \"decimal\" type columns.");
}
else if (numericPrecision > 38)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
else if (numericScale.Value > numericPrecision.Value)
{
throw new ArgumentException("numericScale must not be larger than numericPrecision for \"decimal\" type columns.");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"decimal({0}, {1})",
numericPrecision.Value,
numericScale.Value);
break;
case SqlDbType.Float:
dataType = typeof(double);
if (!numericPrecision.HasValue)
{
throw new ArgumentException("numericPrecision must be specified for \"float\" type columns");
}
else if (numericPrecision > 53)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"float({0})",
numericPrecision.Value);
break;
case SqlDbType.Image:
dataType = typeof(byte[]);
dataTypeName = "image";
break;
case SqlDbType.Int:
dataType = typeof(int);
dataTypeName = "int";
break;
case SqlDbType.Money:
dataType = typeof(decimal);
dataTypeName = "money";
break;
case SqlDbType.NChar:
dataType = typeof(string);
if (!columnSize.HasValue)
{
throw new ArgumentException("columnSize must be specified for \"nchar\" type columns");
}
else if (columnSize > 4000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"nchar({0})",
columnSize.Value);
break;
case SqlDbType.NText:
dataType = typeof(string);
dataTypeName = "ntext";
break;
case SqlDbType.NVarChar:
dataType = typeof(string);
if (columnSize.HasValue)
{
if (columnSize > 4000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"nvarchar({0})",
columnSize.Value);
}
else
{
isLong = true;
dataTypeName = "nvarchar(max)";
}
break;
case SqlDbType.Real:
dataType = typeof(float);
dataTypeName = "real";
break;
case SqlDbType.SmallDateTime:
dataType = typeof(DateTime);
dataTypeName = "smalldatetime";
break;
case SqlDbType.SmallInt:
dataType = typeof(short);
dataTypeName = "smallint";
break;
case SqlDbType.SmallMoney:
dataType = typeof(decimal);
dataTypeName = "smallmoney";
break;
// SqlDbType.Structured not supported because it related to nested rowsets.
case SqlDbType.Text:
dataType = typeof(string);
dataTypeName = "text";
break;
case SqlDbType.Time:
dataType = typeof(TimeSpan);
if (numericPrecision.HasValue)
{
if (numericPrecision > 7)
{
throw new ArgumentOutOfRangeException("numericPrecision");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"time({0})",
numericPrecision.Value);
}
else
{
dataTypeName = "time";
}
break;
// SqlDbType.Timestamp not supported because rowversions are not settable.
case SqlDbType.TinyInt:
dataType = typeof(byte);
dataTypeName = "tinyint";
break;
case SqlDbType.Udt:
if (string.IsNullOrEmpty(udtSchema))
{
throw new ArgumentException("udtSchema must be nonnull and nonempty for \"UDT\" columns.");
}
else if (string.IsNullOrEmpty(udtType))
{
throw new ArgumentException("udtType must be nonnull and nonempty for \"UDT\" columns.");
}
dataType = typeof(object);
using (SqlCommandBuilder commandBuilder = new SqlCommandBuilder())
{
dataTypeName = commandBuilder.QuoteIdentifier(udtSchema) + "." + commandBuilder.QuoteIdentifier(udtType);
}
break;
case SqlDbType.UniqueIdentifier:
dataType = typeof(Guid);
dataTypeName = "uniqueidentifier";
break;
case SqlDbType.VarBinary:
dataType = typeof(byte[]);
if (columnSize.HasValue)
{
if (columnSize > 8000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"varbinary({0})",
columnSize.Value);
}
else
{
isLong = true;
dataTypeName = "varbinary(max)";
}
break;
case SqlDbType.VarChar:
dataType = typeof(string);
if (columnSize.HasValue)
{
if (columnSize > 8000)
{
throw new ArgumentOutOfRangeException("columnSize");
}
dataTypeName = string.Format(CultureInfo.InvariantCulture,
"varchar({0})",
columnSize.Value);
}
else
{
isLong = true;
dataTypeName = "varchar(max)";
}
break;
case SqlDbType.Variant:
dataType = typeof(object);
dataTypeName = "sql_variant";
break;
case SqlDbType.Xml:
dataType = typeof(string);
if (xmlSchemaCollectionName == null)
{
if (xmlSchemaCollectionDatabase != null || xmlSchemaCollectionOwningSchema != null)
{
throw new ArgumentException("xmlSchemaCollectionDatabase and xmlSchemaCollectionOwningSchema must be null if xmlSchemaCollectionName is null for \"xml\" columns.");
}
dataTypeName = "xml";
}
else
{
if (xmlSchemaCollectionName.Length == 0)
{
throw new ArgumentException("xmlSchemaCollectionName must be nonempty or null for \"xml\" columns.");
}
else if (xmlSchemaCollectionDatabase != null &&
xmlSchemaCollectionDatabase.Length == 0)
{
throw new ArgumentException("xmlSchemaCollectionDatabase must be null or nonempty for \"xml\" columns.");
}
else if (xmlSchemaCollectionOwningSchema != null &&
xmlSchemaCollectionOwningSchema.Length == 0)
{
throw new ArgumentException("xmlSchemaCollectionOwningSchema must be null or nonempty for \"xml\" columns.");
}
System.Text.StringBuilder schemaCollection = new System.Text.StringBuilder("xml(");
if (xmlSchemaCollectionDatabase != null)
{
schemaCollection.Append("[" + xmlSchemaCollectionDatabase + "]");
}
schemaCollection.Append("[" + (xmlSchemaCollectionOwningSchema == null ? SchemaName : xmlSchemaCollectionOwningSchema) + "]");
schemaCollection.Append("[" + xmlSchemaCollectionName + "]");
dataTypeName = schemaCollection.ToString();
}
break;
default:
throw new ArgumentOutOfRangeException("providerType");
}
this._schemaTable.Rows.Add(columnName,
_schemaTable.Rows.Count,
columnSize,
numericPrecision,
numericScale,
isUnique,
isKey,
"TraceServer",
"TraceWarehouse",
columnName,
SchemaName,
TableName,
dataType,
allowDbNull,
providerType,
false, // isAliased
false, // isExpression
false, // isIdentity,
false, // isAutoIncrement,
false, // isRowVersion,
false, // isHidden,
isLong,
true, // isReadOnly,
dataType,
dataTypeName,
xmlSchemaCollectionDatabase,
xmlSchemaCollectionOwningSchema,
xmlSchemaCollectionName);
this._columnMappings.Add(new SqlBulkCopyColumnMapping(columnName, columnName));
}
#endregion
#region Constructors
private const string IsIdentitySchemaColumn = "IsIdentity";
private const string DataTypeNameSchemaColumn = "DataTypeName";
private const string XmlSchemaCollectionDatabaseSchemaColumn = "XmlSchemaCollectionDatabase";
private const string XmlSchemaCollectionOwningSchemaSchemaColumn = "XmlSchemaCollectionOwningSchema";
private const string XmlSchemaCollectionNameSchemaColumn = "XmlSchemaCollectionName";
/// <summary>
/// Constructor.
/// </summary>
protected BulkDataReader()
{
this._schemaTable.Locale = System.Globalization.CultureInfo.InvariantCulture;
DataColumnCollection columns = _schemaTable.Columns;
columns.Add(SchemaTableColumn.ColumnName, typeof(string));
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
columns.Add(SchemaTableColumn.NumericScale, typeof(short));
columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
columns.Add(SchemaTableColumn.IsKey, typeof(bool));
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
columns.Add(SchemaTableColumn.DataType, typeof(Type));
columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
columns.Add(SchemaTableColumn.ProviderType, typeof(int));
columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
columns.Add(SchemaTableColumn.IsLong, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(string));
}
#endregion
#region IDataReader
/// <summary>
/// Gets a value indicating the depth of nesting for the current row. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <remarks>
/// <see cref="SqlBulkCopy"/> does not support nested result sets so this method always returns 0.
/// </remarks>
/// <seealso cref="IDataReader.Depth"/>
public int Depth
{
get { return 0; }
}
/// <summary>
/// Gets the number of columns in the current row. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <seealso cref="IDataRecord.FieldCount"/>
public int FieldCount
{
get { return GetSchemaTable().Rows.Count; }
}
/// <summary>
/// Is the bulk copy process open?
/// </summary>
bool _isOpen = true;
/// <summary>
/// Gets a value indicating whether the data reader is closed. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <seealso cref="IDataReader.IsClosed"/>
public bool IsClosed
{
get { return !_isOpen; }
}
/// <summary>
/// Gets the column located at the specified index. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// No column with the specified index was found.
/// </exception>
/// <param name="i">
/// The zero-based index of the column to get.
/// </param>
/// <returns>
/// The column located at the specified index as an <see cref="Object"/>.
/// </returns>
/// <seealso cref="P:IDataRecord.Item(Int32)"/>
public object this[int i]
{
get { return GetValue(i); }
}
/// <summary>
/// Gets the column with the specified name. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// No column with the specified name was found.
/// </exception>
/// <param name="name">
/// The name of the column to find.
/// </param>
/// <returns>
/// The column located at the specified name as an <see cref="Object"/>.
/// </returns>
/// <seealso cref="P:IDataRecord.Item(String)"/>
public object this[string name]
{
get { return GetValue(GetOrdinal(name)); }
}
/// <summary>
/// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <remarks>
/// Always returns -1 which is the expected behaviour for statements.
/// </remarks>
/// <seealso cref="IDataReader.RecordsAffected"/>
public virtual int RecordsAffected
{
get { return -1; }
}
/// <summary>
/// Closes the <see cref="IDataReader"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <seealso cref="IDataReader.Close"/>
public void Close()
{
this._isOpen = false;
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Boolean"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetBoolean(Int32)"/>
public bool GetBoolean(int i)
{
return (bool)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Byte"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetByte(Int32)"/>
public byte GetByte(int i)
{
return (byte)GetValue(i);
}
/// <summary>
/// Reads a stream of bytes from the specified column offset into the buffer as an array, starting at the given buffer offset.
/// (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <remarks>
/// If you pass a buffer that is null, <see cref="GetBytes"/> returns the length of the row in bytes.
/// </remarks>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <param name="fieldOffset">
/// The index within the field from which to start the read operation.
/// </param>
/// <param name="buffer">
/// The buffer into which to read the stream of bytes.
/// </param>
/// <param name="bufferoffset">
/// The index for buffer to start the read operation.
/// </param>
/// <param name="length">
/// The number of bytes to read.
/// </param>
/// <returns>
/// The actual number of bytes read.
/// </returns>
/// <seealso cref="IDataRecord.GetBytes(Int32,Int64,Byte[],Int32,Int32)"/>
public long GetBytes(int i,
long fieldOffset,
byte[] buffer,
int bufferoffset,
int length)
{
byte[] data = (byte[])GetValue(i);
if (buffer != null)
{
Array.Copy(data, fieldOffset, buffer, bufferoffset, length);
}
return data.LongLength;
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Char"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetChar(Int32)"/>
public char GetChar(int i)
{
char result;
object data = GetValue(i);
char? dataAsChar = data as char?;
char[] dataAsCharArray = data as char[];
string dataAsString = data as string;
if (dataAsChar.HasValue)
{
result = dataAsChar.Value;
}
else if (dataAsCharArray != null &&
dataAsCharArray.Length == 1)
{
result = dataAsCharArray[0];
}
else if (dataAsString != null &&
dataAsString.Length == 1)
{
result = dataAsString[0];
}
else
{
throw new InvalidOperationException("GetValue did not return a Char compatible type.");
}
return result;
}
/// <summary>
/// Reads a stream of characters from the specified column offset into the buffer as an array, starting at the given buffer offset.
/// (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <remarks>
/// If you pass a buffer that is null, <see cref="GetChars"/> returns the length of the row in bytes.
/// </remarks>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <param name="fieldoffset">
/// The index within the field from which to start the read operation.
/// </param>
/// <param name="buffer">
/// The buffer into which to read the stream of characters.
/// </param>
/// <param name="bufferoffset">
/// The index for buffer to start the read operation.
/// </param>
/// <param name="length">
/// The number of characters to read.
/// </param>
/// <returns>
/// The actual number of characters read.
/// </returns>
/// <seealso cref="IDataRecord.GetChars(Int32,Int64,Char[],Int32,Int32)"/>
public long GetChars(int i,
long fieldoffset,
char[] buffer,
int bufferoffset,
int length)
{
object data = GetValue(i);
string dataAsString = data as string;
char[] dataAsCharArray = data as char[];
if (dataAsString != null)
{
dataAsCharArray = dataAsString.ToCharArray((int)fieldoffset, length);
}
else if (dataAsCharArray == null)
{
throw new InvalidOperationException("GetValue did not return either a Char array or a String.");
}
if (buffer != null)
{
Array.Copy(dataAsCharArray, fieldoffset, buffer, bufferoffset, length);
}
return dataAsCharArray.LongLength;
}
/// <summary>
/// Returns an IDataReader for the specified column ordinal. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <remarks>
/// <see cref="SqlBulkCopy"/> does not support nested result sets so this method always returns null.
/// </remarks>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The <see cref="IDataReader"/> for the specified column ordinal (null).
/// </returns>
/// <seealso cref="IDataRecord.GetData(Int32)"/>
public IDataReader GetData(int i)
{
if (i < 0 || i >= this.FieldCount)
{
throw new ArgumentOutOfRangeException("i");
}
return null;
}
/// <summary>
/// The data type information for the specified field. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The data type information for the specified field.
/// </returns>
/// <seealso cref="IDataRecord.GetDataTypeName(Int32)"/>
public string GetDataTypeName(int i)
{
return GetFieldType(i).Name;
}
/// <summary>
/// Gets the value of the specified column as a <see cref="DateTime"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetDateTime(Int32)"/>
public DateTime GetDateTime(int i)
{
return (DateTime)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="DateTimeOffset"/>.
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
public DateTimeOffset GetDateTimeOffset(int i)
{
return (DateTimeOffset)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Decimal"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetDecimal(Int32)"/>
public decimal GetDecimal(int i)
{
return (decimal)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Double"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetDouble(Int32)"/>
public double GetDouble(int i)
{
return (double)GetValue(i);
}
/// <summary>
/// Gets the <see cref="Type"/> information corresponding to the type of <see cref="Object"/> that would be returned from <see cref="GetValue"/>.
/// (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The <see cref="Type"/> information corresponding to the type of <see cref="Object"/> that would be returned from <see cref="GetValue"/>.
/// </returns>
/// <seealso cref="IDataRecord.GetFieldType(Int32)"/>
public Type GetFieldType(int i)
{
return (Type)GetSchemaTable().Rows[i][SchemaTableColumn.DataType];
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Single"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetFloat(Int32)"/>
public float GetFloat(int i)
{
return (float)this[i];
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Guid"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetGuid(Int32)"/>
public Guid GetGuid(int i)
{
return (Guid)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Int16"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetInt16(Int32)"/>
public short GetInt16(int i)
{
return (short)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Int32"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetInt32(Int32)"/>
public int GetInt32(int i)
{
return (int)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Int64"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetInt64(Int32)"/>
public long GetInt64(int i)
{
return (long)GetValue(i);
}
/// <summary>
/// Gets the name for the field to find. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The name of the field or the empty string (""), if there is no value to return.
/// </returns>
/// <seealso cref="IDataRecord.GetName(Int32)"/>
public string GetName(int i)
{
return (string)GetSchemaTable().Rows[i][SchemaTableColumn.ColumnName];
}
/// <summary>
/// Return the index of the named field. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index of the named field was not found.
/// </exception>
/// <param name="name">
/// The name of the field to find.
/// </param>
/// <returns>
/// The index of the named field.
/// </returns>
/// <seealso cref="IDataRecord.GetOrdinal(String)"/>
public int GetOrdinal(string name)
{
if (name == null) // Empty strings are handled as a IndexOutOfRangeException.
{
throw new ArgumentNullException("name");
}
int result = -1;
int rowCount = FieldCount;
DataRowCollection schemaRows = GetSchemaTable().Rows;
// Case sensitive search
for (int ordinal = 0; ordinal < rowCount; ordinal++)
{
if (String.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.Ordinal))
{
result = ordinal;
}
}
if (result == -1)
{
// Case insensitive search.
for (int ordinal = 0; ordinal < rowCount; ordinal++)
{
if (String.Equals((string)schemaRows[ordinal][SchemaTableColumn.ColumnName], name, StringComparison.OrdinalIgnoreCase))
{
result = ordinal;
}
}
}
if (result == -1)
{
throw new IndexOutOfRangeException(name);
}
return result;
}
/// <summary>
/// Returns a <see cref="DataTable"/> that describes the column metadata of the <see cref="IDataReader"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="IDataReader"/> is closed.
/// </exception>
/// <returns>
/// A <see cref="DataTable"/> that describes the column metadata.
/// </returns>
/// <seealso cref="IDataReader.GetSchemaTable()"/>
public DataTable GetSchemaTable()
{
if (IsClosed)
{
throw new InvalidOperationException("The IDataReader is closed.");
}
if (_schemaTable.Rows.Count == 0)
{
// Need to add the column definitions and mappings
_schemaTable.TableName = TableName;
AddSchemaTableRows();
Debug.Assert(_schemaTable.Rows.Count == FieldCount);
}
return _schemaTable;
}
/// <summary>
/// Gets the value of the specified column as a <see cref="String"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetString(Int32)"/>
public string GetString(int i)
{
return (string)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="TimeSpan"/>.
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
public TimeSpan GetTimeSpan(int i)
{
return (TimeSpan)GetValue(i);
}
/// <summary>
/// Gets the value of the specified column as a <see cref="Object"/>. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// The value of the column.
/// </returns>
/// <seealso cref="IDataRecord.GetValue(Int32)"/>
public abstract object GetValue(int i);
/// <summary>
/// Populates an array of objects with the column values of the current record. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="ArgumentNullException">
/// <paramref name="values"/> was null.
/// </exception>
/// <param name="values">
/// An array of <see cref="Object"/> to copy the attribute fields into.
/// </param>
/// <returns>
/// The number of instances of <see cref="Object"/> in the array.
/// </returns>
/// <seealso cref="IDataRecord.GetValues(Object[])"/>
public int GetValues(object[] values)
{
if (values == null)
{
throw new ArgumentNullException("values");
}
int fieldCount = Math.Min(FieldCount, values.Length);
for (int i = 0; i < fieldCount; i++)
{
values[i] = GetValue(i);
}
return fieldCount;
}
/// <summary>
/// Return whether the specified field is set to null. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <exception cref="IndexOutOfRangeException">
/// The index passed was outside the range of 0 through <see cref="FieldCount"/>.
/// </exception>
/// <param name="i">
/// The zero-based column ordinal.
/// </param>
/// <returns>
/// True if the specified field is set to null; otherwise, false.
/// </returns>
/// <seealso cref="IDataRecord.IsDBNull(Int32)"/>
public bool IsDBNull(int i)
{
object data = GetValue(i);
return data == null || Convert.IsDBNull(data);
}
/// <summary>
/// Advances the data reader to the next result, when reading the results of batch SQL statements. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <remarks>
/// <see cref="IDataReader"/> for <see cref="SqlBulkCopy"/> returns a single result set so false is always returned.
/// </remarks>
/// <returns>
/// True if there are more rows; otherwise, false. <see cref="IDataReader"/> for <see cref="SqlBulkCopy"/> returns a single result set so false is always returned.
/// </returns>
/// <seealso cref="IDataReader.NextResult()"/>
public bool NextResult()
{
return false;
}
/// <summary>
/// Advances the <see cref="IDataReader"/> to the next record. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <returns>
/// True if there are more rows; otherwise, false.
/// </returns>
/// <seealso cref="IDataReader.Read()"/>
public abstract bool Read();
#endregion
#region IDisposable
/// <summary>
/// Has the object been disposed?
/// </summary>
bool _disposed = false;
/// <summary>
/// Dispose of any disposable and expensive resources.
/// </summary>
/// <param name="disposing">
/// Is this call the result of a <see cref="IDisposable.Dispose"/> call?
/// </param>
protected virtual void Dispose(bool disposing)
{
if (!this._disposed)
{
this._disposed = true;
if (disposing)
{
if (_schemaTable != null)
{
_schemaTable.Dispose();
this._schemaTable = null;
}
this._columnMappings = null;
this._isOpen = false;
GC.SuppressFinalize(this);
}
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. (Inherited from <see cref="IDataReader"/>.)
/// </summary>
/// <seealso cref="IDisposable.Dispose()"/>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Finalizer
/// </summary>
/// <remarks>
/// <see cref="BulkDataReader"/> has no unmanaged resources but a subclass may thus a finalizer is required.
/// </remarks>
~BulkDataReader()
{
Dispose(false);
}
#endregion
}
}