|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace System.Data.Common
{
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public class DataAdapter : Component, IDataAdapter
{
private static readonly object s_eventFillError = new object();
private bool _acceptChangesDuringUpdate = true;
private bool _acceptChangesDuringUpdateAfterInsert = true;
private bool _continueUpdateOnError;
private bool _hasFillErrorHandler;
private bool _returnProviderSpecificTypes;
private bool _acceptChangesDuringFill = true;
private LoadOption _fillLoadOption;
private MissingMappingAction _missingMappingAction = System.Data.MissingMappingAction.Passthrough;
private MissingSchemaAction _missingSchemaAction = System.Data.MissingSchemaAction.Add;
private DataTableMappingCollection? _tableMappings;
private static int s_objectTypeCount; // Bid counter
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount);
[Conditional("DEBUG")]
private static void AssertReaderHandleFieldCount(DataReaderContainer readerHandler)
{
#if DEBUG
Debug.Assert(readerHandler.FieldCount > 0, "Scenario expects non-empty results but no fields reported by reader");
#endif
}
protected DataAdapter() : base()
{
GC.SuppressFinalize(this);
}
protected DataAdapter(DataAdapter from) : base()
{
CloneFrom(from);
}
[DefaultValue(true)]
public bool AcceptChangesDuringFill
{
get
{
return _acceptChangesDuringFill;
}
set
{
_acceptChangesDuringFill = value;
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool ShouldSerializeAcceptChangesDuringFill()
{
return (0 == _fillLoadOption);
}
[DefaultValue(true)]
public bool AcceptChangesDuringUpdate
{
get { return _acceptChangesDuringUpdate; }
set { _acceptChangesDuringUpdate = value; }
}
[DefaultValue(false)]
public bool ContinueUpdateOnError
{
get { return _continueUpdateOnError; }
set { _continueUpdateOnError = value; }
}
[RefreshProperties(RefreshProperties.All)]
public LoadOption FillLoadOption
{
get
{
LoadOption fillLoadOption = _fillLoadOption;
return ((0 != fillLoadOption) ? _fillLoadOption : LoadOption.OverwriteChanges);
}
[RequiresUnreferencedCode("Using LoadOption may cause members from types used in the expression column to be trimmed if not referenced directly.")] // See SchemaMapping.AddAdditionalPropertiesIfLoadOptionsSet
set
{
switch (value)
{
case 0: // to allow simple resetting
case LoadOption.OverwriteChanges:
case LoadOption.PreserveChanges:
case LoadOption.Upsert:
_fillLoadOption = value;
break;
default:
throw ADP.InvalidLoadOption(value);
}
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public void ResetFillLoadOption()
{
_fillLoadOption = 0;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual bool ShouldSerializeFillLoadOption() => 0 != _fillLoadOption;
[DefaultValue(System.Data.MissingMappingAction.Passthrough)]
public MissingMappingAction MissingMappingAction
{
get { return _missingMappingAction; }
set
{
switch (value)
{
case MissingMappingAction.Passthrough:
case MissingMappingAction.Ignore:
case MissingMappingAction.Error:
_missingMappingAction = value;
break;
default:
throw ADP.InvalidMissingMappingAction(value);
}
}
}
[DefaultValue(Data.MissingSchemaAction.Add)]
public MissingSchemaAction MissingSchemaAction
{
get { return _missingSchemaAction; }
set
{
switch (value)
{
case MissingSchemaAction.Add:
case MissingSchemaAction.Ignore:
case MissingSchemaAction.Error:
case MissingSchemaAction.AddWithKey:
_missingSchemaAction = value;
break;
default:
throw ADP.InvalidMissingSchemaAction(value);
}
}
}
internal int ObjectID => _objectID;
[DefaultValue(false)]
public virtual bool ReturnProviderSpecificTypes
{
get { return _returnProviderSpecificTypes; }
set { _returnProviderSpecificTypes = value; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public DataTableMappingCollection TableMappings
{
get
{
DataTableMappingCollection? mappings = _tableMappings;
if (null == mappings)
{
mappings = CreateTableMappings();
if (null == mappings)
{
mappings = new DataTableMappingCollection();
}
_tableMappings = mappings;
}
return mappings; // constructed by base class
}
}
ITableMappingCollection IDataAdapter.TableMappings => TableMappings;
protected virtual bool ShouldSerializeTableMappings() => true;
protected bool HasTableMappings() => ((null != _tableMappings) && (0 < TableMappings.Count));
public event FillErrorEventHandler? FillError
{
add
{
_hasFillErrorHandler = true;
Events.AddHandler(s_eventFillError, value);
}
remove
{
Events.RemoveHandler(s_eventFillError, value);
}
}
[Obsolete("CloneInternals() has been deprecated. Use the DataAdapter(DataAdapter from) constructor instead.")]
protected virtual DataAdapter CloneInternals()
{
DataAdapter clone = (DataAdapter)Activator.CreateInstance(GetType())!;
clone.CloneFrom(this);
return clone;
}
private void CloneFrom(DataAdapter from)
{
_acceptChangesDuringUpdate = from._acceptChangesDuringUpdate;
_acceptChangesDuringUpdateAfterInsert = from._acceptChangesDuringUpdateAfterInsert;
_continueUpdateOnError = from._continueUpdateOnError;
_returnProviderSpecificTypes = from._returnProviderSpecificTypes;
_acceptChangesDuringFill = from._acceptChangesDuringFill;
_fillLoadOption = from._fillLoadOption;
_missingMappingAction = from._missingMappingAction;
_missingSchemaAction = from._missingSchemaAction;
if ((null != from._tableMappings) && (0 < from.TableMappings.Count))
{
DataTableMappingCollection parameters = TableMappings;
foreach (object parameter in from.TableMappings)
{
parameters.Add((parameter is ICloneable) ? ((ICloneable)parameter).Clone() : parameter);
}
}
}
protected virtual DataTableMappingCollection CreateTableMappings()
{
DataCommonEventSource.Log.Trace("<comm.DataAdapter.CreateTableMappings|API> {0}", ObjectID);
return new DataTableMappingCollection();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// release mananged objects
_tableMappings = null;
}
// release unmanaged objects
base.Dispose(disposing); // notify base classes
}
[RequiresUnreferencedCode("IDataReader's (built from adapter commands) schema table types cannot be statically analyzed.")]
public virtual DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType)
{
throw ADP.NotSupported();
}
[RequiresUnreferencedCode("dataReader's schema table types cannot be statically analyzed.")]
protected virtual DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType, string srcTable, IDataReader dataReader)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DataAdapter.FillSchema|API> {0}, dataSet, schemaType={1}, srcTable, dataReader", ObjectID, schemaType);
try
{
if (null == dataSet)
{
throw ADP.ArgumentNull(nameof(dataSet));
}
if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType))
{
throw ADP.InvalidSchemaType(schemaType);
}
if (string.IsNullOrEmpty(srcTable))
{
throw ADP.FillSchemaRequiresSourceTableName(nameof(srcTable));
}
if ((null == dataReader) || dataReader.IsClosed)
{
throw ADP.FillRequires(nameof(dataReader));
}
// user must Close/Dispose of the dataReader
// Never returns null if dataSet is non-null
object value = FillSchemaFromReader(dataSet, null, schemaType, srcTable, dataReader)!;
return (DataTable[])value;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode("dataReader's schema table types cannot be statically analyzed.")]
protected virtual DataTable? FillSchema(DataTable dataTable, SchemaType schemaType, IDataReader dataReader)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DataAdapter.FillSchema|API> {0}, dataTable, schemaType, dataReader", ObjectID);
try
{
if (null == dataTable)
{
throw ADP.ArgumentNull(nameof(dataTable));
}
if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType))
{
throw ADP.InvalidSchemaType(schemaType);
}
if ((null == dataReader) || dataReader.IsClosed)
{
throw ADP.FillRequires(nameof(dataReader));
}
// user must Close/Dispose of the dataReader
// user will have to call NextResult to access remaining results
object? value = FillSchemaFromReader(null, dataTable, schemaType, null, dataReader);
return (DataTable?)value;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[RequiresUnreferencedCode("dataReader's schema table types cannot be statically analyzed.")]
internal object? FillSchemaFromReader(DataSet? dataset, DataTable? datatable, SchemaType schemaType, string? srcTable, IDataReader dataReader)
{
DataTable[]? dataTables = null;
int schemaCount = 0;
do
{
DataReaderContainer readerHandler = DataReaderContainer.Create(dataReader, ReturnProviderSpecificTypes);
AssertReaderHandleFieldCount(readerHandler);
if (0 >= readerHandler.FieldCount)
{
continue;
}
string? tmp = null;
if (null != dataset)
{
tmp = DataAdapter.GetSourceTableName(srcTable!, schemaCount);
schemaCount++; // don't increment if no SchemaTable ( a non-row returning result )
}
SchemaMapping mapping = new SchemaMapping(this, dataset, datatable, readerHandler, true, schemaType, tmp, false, null, null);
if (null != datatable)
{
// do not read remaining results in single DataTable case
return mapping.DataTable;
}
else if (null != mapping.DataTable)
{
if (null == dataTables)
{
dataTables = new DataTable[1] { mapping.DataTable };
}
else
{
dataTables = DataAdapter.AddDataTableToArray(dataTables, mapping.DataTable);
}
}
} while (dataReader.NextResult()); // FillSchema does not capture errors for FillError event
object? value = dataTables;
if ((null == value) && (null == datatable))
{
value = Array.Empty<DataTable>();
}
return value; // null if datatable had no results
}
public virtual int Fill(DataSet dataSet)
{
throw ADP.NotSupported();
}
protected virtual int Fill(DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DataAdapter.Fill|API> {0}, dataSet, srcTable, dataReader, startRecord, maxRecords", ObjectID);
try
{
if (null == dataSet)
{
throw ADP.FillRequires(nameof(dataSet));
}
if (string.IsNullOrEmpty(srcTable))
{
throw ADP.FillRequiresSourceTableName(nameof(srcTable));
}
if (null == dataReader)
{
throw ADP.FillRequires(nameof(dataReader));
}
if (startRecord < 0)
{
throw ADP.InvalidStartRecord(nameof(startRecord), startRecord);
}
if (maxRecords < 0)
{
throw ADP.InvalidMaxRecords(nameof(maxRecords), maxRecords);
}
if (dataReader.IsClosed)
{
return 0;
}
// user must Close/Dispose of the dataReader
DataReaderContainer readerHandler = DataReaderContainer.Create(dataReader, ReturnProviderSpecificTypes);
return FillFromReader(dataSet, null, srcTable, readerHandler, startRecord, maxRecords);
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
protected virtual int Fill(DataTable dataTable, IDataReader dataReader)
{
DataTable[] dataTables = new DataTable[] { dataTable };
return Fill(dataTables, dataReader, 0, 0);
}
protected virtual int Fill(DataTable[] dataTables, IDataReader dataReader, int startRecord, int maxRecords)
{
long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DataAdapter.Fill|API> {0}, dataTables[], dataReader, startRecord, maxRecords", ObjectID);
try
{
ADP.CheckArgumentLength(dataTables, nameof(dataTables));
if ((null == dataTables) || (0 == dataTables.Length) || (null == dataTables[0]))
{
throw ADP.FillRequires("dataTable");
}
if (null == dataReader)
{
throw ADP.FillRequires(nameof(dataReader));
}
if ((1 < dataTables.Length) && ((0 != startRecord) || (0 != maxRecords)))
{
throw ADP.NotSupported(); // FillChildren is not supported with FillPage
}
int result = 0;
bool enforceConstraints = false;
DataSet? commonDataSet = dataTables[0].DataSet;
try
{
if (null != commonDataSet)
{
enforceConstraints = commonDataSet.EnforceConstraints;
commonDataSet.EnforceConstraints = false;
}
for (int i = 0; i < dataTables.Length; ++i)
{
Debug.Assert(null != dataTables[i], "null DataTable Fill");
if (dataReader.IsClosed)
{
break;
}
DataReaderContainer readerHandler = DataReaderContainer.Create(dataReader, ReturnProviderSpecificTypes);
AssertReaderHandleFieldCount(readerHandler);
if (readerHandler.FieldCount <= 0)
{
if (i == 0)
{
bool lastFillNextResult;
do
{
lastFillNextResult = FillNextResult(readerHandler);
}
while (lastFillNextResult && readerHandler.FieldCount <= 0);
if (!lastFillNextResult)
{
break;
}
}
else
{
continue;
}
}
if ((0 < i) && !FillNextResult(readerHandler))
{
break;
}
// user must Close/Dispose of the dataReader
// user will have to call NextResult to access remaining results
int count = FillFromReader(null, dataTables[i], null, readerHandler, startRecord, maxRecords);
if (0 == i)
{
result = count;
}
}
}
catch (ConstraintException)
{
enforceConstraints = false;
throw;
}
finally
{
if (enforceConstraints)
{
commonDataSet!.EnforceConstraints = true;
}
}
return result;
}
finally
{
DataCommonEventSource.Log.ExitScope(logScopeId);
}
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "parentChapterValue is not used here")]
internal int FillFromReader(DataSet? dataset, DataTable? datatable, string? srcTable, DataReaderContainer dataReader, int startRecord, int maxRecords)
{
return FillFromReader(dataset, datatable, srcTable, dataReader, startRecord, maxRecords, null, null);
}
[RequiresUnreferencedCode("parentChapterValue's type cannot be statically analyzed")]
internal int FillFromReader(DataSet? dataset, DataTable? datatable, string? srcTable, DataReaderContainer dataReader, int startRecord, int maxRecords, DataColumn? parentChapterColumn, object? parentChapterValue)
{
int rowsAddedToDataSet = 0;
int schemaCount = 0;
do
{
AssertReaderHandleFieldCount(dataReader);
if (0 >= dataReader.FieldCount)
{
continue; // loop to next result
}
SchemaMapping? mapping = FillMapping(dataset, datatable, srcTable, dataReader, schemaCount, parentChapterColumn, parentChapterValue);
schemaCount++; // don't increment if no SchemaTable ( a non-row returning result )
if (null == mapping)
{
continue; // loop to next result
}
if (null == mapping.DataValues)
{
continue; // loop to next result
}
if (null == mapping.DataTable)
{
continue; // loop to next result
}
mapping.DataTable.BeginLoadData();
try
{
// startRecord and maxRecords only apply to the first resultset
if ((1 == schemaCount) && ((0 < startRecord) || (0 < maxRecords)))
{
rowsAddedToDataSet = FillLoadDataRowChunk(mapping, startRecord, maxRecords);
}
else
{
int count = FillLoadDataRow(mapping);
if (1 == schemaCount)
{
// only return LoadDataRow count for first resultset
// not secondary or chaptered results
rowsAddedToDataSet = count;
}
}
}
finally
{
mapping.DataTable.EndLoadData();
}
if (null != datatable)
{
break; // do not read remaining results in single DataTable case
}
} while (FillNextResult(dataReader));
return rowsAddedToDataSet;
}
[RequiresUnreferencedCode("Row chapter column types cannot be statically analyzed")]
private int FillLoadDataRowChunk(SchemaMapping mapping, int startRecord, int maxRecords)
{
DataReaderContainer dataReader = mapping.DataReader;
while (0 < startRecord)
{
if (!dataReader.Read())
{
// there are no more rows on first resultset
return 0;
}
--startRecord;
}
int rowsAddedToDataSet = 0;
if (0 < maxRecords)
{
while ((rowsAddedToDataSet < maxRecords) && dataReader.Read())
{
if (_hasFillErrorHandler)
{
try
{
mapping.LoadDataRowWithClear();
rowsAddedToDataSet++;
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
ADP.TraceExceptionForCapture(e);
OnFillErrorHandler(e, mapping.DataTable, mapping.DataValues);
}
}
else
{
mapping.LoadDataRow();
rowsAddedToDataSet++;
}
}
// skip remaining rows of the first resultset
}
else
{
rowsAddedToDataSet = FillLoadDataRow(mapping);
}
return rowsAddedToDataSet;
}
[RequiresUnreferencedCode("Row chapter column types cannot be statically analyzed")]
private int FillLoadDataRow(SchemaMapping mapping)
{
int rowsAddedToDataSet = 0;
DataReaderContainer dataReader = mapping.DataReader;
if (_hasFillErrorHandler)
{
while (dataReader.Read())
{ // read remaining rows of first and subsequent resultsets
try
{
// only try-catch if a FillErrorEventHandler is registered so that
// in the default case we get the full callstack from users
mapping.LoadDataRowWithClear();
rowsAddedToDataSet++;
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
ADP.TraceExceptionForCapture(e);
OnFillErrorHandler(e, mapping.DataTable, mapping.DataValues);
}
}
}
else
{
while (dataReader.Read())
{
// read remaining rows of first and subsequent resultset
mapping.LoadDataRow();
rowsAddedToDataSet++;
}
}
return rowsAddedToDataSet;
}
[RequiresUnreferencedCode("parentChapterValue's type cannot be statically analyzed")]
private SchemaMapping FillMappingInternal(DataSet? dataset, DataTable? datatable, string? srcTable, DataReaderContainer dataReader, int schemaCount, DataColumn? parentChapterColumn, object? parentChapterValue)
{
bool withKeyInfo = (Data.MissingSchemaAction.AddWithKey == MissingSchemaAction);
string? tmp = null;
if (null != dataset)
{
tmp = DataAdapter.GetSourceTableName(srcTable!, schemaCount);
}
return new SchemaMapping(this, dataset, datatable, dataReader, withKeyInfo, SchemaType.Mapped, tmp, true, parentChapterColumn, parentChapterValue);
}
[RequiresUnreferencedCode("parentChapterValue's type cannot be statically analyzed")]
private SchemaMapping? FillMapping(DataSet? dataset, DataTable? datatable, string? srcTable, DataReaderContainer dataReader, int schemaCount, DataColumn? parentChapterColumn, object? parentChapterValue)
{
SchemaMapping? mapping = null;
if (_hasFillErrorHandler)
{
try
{
// only try-catch if a FillErrorEventHandler is registered so that
// in the default case we get the full callstack from users
mapping = FillMappingInternal(dataset, datatable, srcTable, dataReader, schemaCount, parentChapterColumn, parentChapterValue);
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
ADP.TraceExceptionForCapture(e);
OnFillErrorHandler(e, null, null);
}
}
else
{
mapping = FillMappingInternal(dataset, datatable, srcTable, dataReader, schemaCount, parentChapterColumn, parentChapterValue);
}
return mapping;
}
private bool FillNextResult(DataReaderContainer dataReader)
{
bool result = true;
if (_hasFillErrorHandler)
{
try
{
// only try-catch if a FillErrorEventHandler is registered so that
// in the default case we get the full callstack from users
result = dataReader.NextResult();
}
catch (Exception e) when (ADP.IsCatchableExceptionType(e))
{
ADP.TraceExceptionForCapture(e);
OnFillErrorHandler(e, null, null);
}
}
else
{
result = dataReader.NextResult();
}
return result;
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public virtual IDataParameter[] GetFillParameters() => Array.Empty<IDataParameter>();
internal DataTableMapping? GetTableMappingBySchemaAction(string sourceTableName, string dataSetTableName, MissingMappingAction mappingAction)
{
return DataTableMappingCollection.GetTableMappingBySchemaAction(_tableMappings, sourceTableName, dataSetTableName, mappingAction);
}
internal int IndexOfDataSetTable(string dataSetTable)
{
if (null != _tableMappings)
{
return TableMappings.IndexOfDataSetTable(dataSetTable);
}
return -1;
}
protected virtual void OnFillError(FillErrorEventArgs value)
{
((FillErrorEventHandler?)Events[s_eventFillError])?.Invoke(this, value);
}
private void OnFillErrorHandler(Exception e, DataTable? dataTable, object?[]? dataValues)
{
FillErrorEventArgs fillErrorEvent = new FillErrorEventArgs(dataTable, dataValues);
fillErrorEvent.Errors = e;
OnFillError(fillErrorEvent);
if (!fillErrorEvent.Continue)
{
if (null != fillErrorEvent.Errors)
{
throw fillErrorEvent.Errors;
}
throw e;
}
}
[RequiresUnreferencedCode("IDataReader's (built from adapter commands) schema table types cannot be statically analyzed.")]
public virtual int Update(DataSet dataSet)
{
throw ADP.NotSupported();
}
// used by FillSchema which returns an array of datatables added to the dataset
private static DataTable[] AddDataTableToArray(DataTable[] tables, DataTable newTable)
{
for (int i = 0; i < tables.Length; ++i)
{ // search for duplicates
if (tables[i] == newTable)
{
return tables; // duplicate found
}
}
DataTable[] newTables = new DataTable[tables.Length + 1]; // add unique data table
for (int i = 0; i < tables.Length; ++i)
{
newTables[i] = tables[i];
}
newTables[tables.Length] = newTable;
return newTables;
}
// dynamically generate source table names
private static string GetSourceTableName(string srcTable, int index)
{
//if ((null != srcTable) && (0 <= index) && (index < srcTable.Length)) {
if (0 == index)
{
return srcTable; //[index];
}
return srcTable + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
}
internal sealed class LoadAdapter : DataAdapter
{
internal LoadAdapter() { }
internal int FillFromReader(DataTable[] dataTables, IDataReader dataReader, int startRecord, int maxRecords)
{
return Fill(dataTables, dataReader, startRecord, maxRecords);
}
}
}
|