2 // System.Data.Common.DataAdapter
5 // Rodrigo Moya (rodrigo@ximian.com)
6 // Tim Coleman (tim@timcoleman.com)
9 // Copyright (C) Tim Coleman, 2002-2003
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.Collections;
38 using System.ComponentModel;
40 namespace System.Data.Common
43 /// Represents a set of data commands and a database connection that are used to fill the DataSet and update the data source.
49 class DataAdapter : Component, IDataAdapter
53 private bool acceptChangesDuringFill;
54 private bool continueUpdateOnError;
55 private MissingMappingAction missingMappingAction;
56 private MissingSchemaAction missingSchemaAction;
57 private DataTableMappingCollection tableMappings;
58 private const string DefaultSourceTableName = "Table";
59 private const string DefaultSourceColumnName = "Column";
62 private bool acceptChangesDuringUpdate;
63 private LoadOption fillLoadOption;
64 private bool returnProviderSpecificTypes;
70 protected DataAdapter ()
72 acceptChangesDuringFill = true;
73 continueUpdateOnError = false;
74 missingMappingAction = MissingMappingAction.Passthrough;
75 missingSchemaAction = MissingSchemaAction.Add;
76 tableMappings = new DataTableMappingCollection ();
78 acceptChangesDuringUpdate = true;
79 fillLoadOption = LoadOption.OverwriteChanges;
80 returnProviderSpecificTypes = false;
84 protected DataAdapter (DataAdapter from)
86 AcceptChangesDuringFill = from.AcceptChangesDuringFill;
87 ContinueUpdateOnError = from.ContinueUpdateOnError;
88 MissingMappingAction = from.MissingMappingAction;
89 MissingSchemaAction = from.MissingSchemaAction;
91 if (from.tableMappings != null)
92 foreach (ICloneable cloneable in from.TableMappings)
93 TableMappings.Add (cloneable.Clone ());
95 acceptChangesDuringUpdate = from.AcceptChangesDuringUpdate;
96 fillLoadOption = from.FillLoadOption;
97 returnProviderSpecificTypes = from.ReturnProviderSpecificTypes;
105 [DataCategory ("Fill")]
107 [DataSysDescription ("Whether or not Fill will call DataRow.AcceptChanges.")]
109 [DefaultValue (true)]
110 public bool AcceptChangesDuringFill {
111 get { return acceptChangesDuringFill; }
112 set { acceptChangesDuringFill = value; }
116 [DefaultValue (true)]
117 public bool AcceptChangesDuringUpdate {
118 get { return acceptChangesDuringUpdate; }
119 set { acceptChangesDuringUpdate = value; }
123 [DataCategory ("Update")]
125 [DataSysDescription ("Whether or not to continue to the next DataRow when the Update events, RowUpdating and RowUpdated, Status is UpdateStatus.ErrorsOccurred.")]
127 [DefaultValue (false)]
128 public bool ContinueUpdateOnError {
129 get { return continueUpdateOnError; }
130 set { continueUpdateOnError = value; }
134 [RefreshProperties (RefreshProperties.All)]
135 public LoadOption FillLoadOption {
136 get { return fillLoadOption; }
138 ExceptionHelper.CheckEnumValue (typeof (LoadOption), value);
139 fillLoadOption = value;
144 ITableMappingCollection IDataAdapter.TableMappings {
145 get { return TableMappings; }
148 [DataCategory ("Mapping")]
150 [DataSysDescription ("The action taken when a table or column in the TableMappings is missing.")]
152 [DefaultValue (MissingMappingAction.Passthrough)]
153 public MissingMappingAction MissingMappingAction {
154 get { return missingMappingAction; }
156 ExceptionHelper.CheckEnumValue (typeof (MissingMappingAction), value);
157 missingMappingAction = value;
161 [DataCategory ("Mapping")]
163 [DataSysDescription ("The action taken when a table or column in the DataSet is missing.")]
165 [DefaultValue (MissingSchemaAction.Add)]
166 public MissingSchemaAction MissingSchemaAction {
167 get { return missingSchemaAction; }
169 ExceptionHelper.CheckEnumValue (typeof (MissingSchemaAction), value);
170 missingSchemaAction = value;
175 [DefaultValue (false)]
176 public virtual bool ReturnProviderSpecificTypes {
177 get { return returnProviderSpecificTypes; }
178 set { returnProviderSpecificTypes = value; }
182 [DataCategory ("Mapping")]
184 [DataSysDescription ("How to map source table to DataSet table.")]
186 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
187 public DataTableMappingCollection TableMappings {
188 get { return tableMappings; }
196 public event FillErrorEventHandler FillError;
204 [Obsolete ("Use the protected constructor instead", false)]
207 protected virtual DataAdapter CloneInternals ()
209 throw new NotImplementedException ();
212 protected virtual DataTableMappingCollection CreateTableMappings ()
214 return new DataTableMappingCollection ();
218 protected override void Dispose (bool disposing)
220 throw new NotImplementedException ();
223 protected virtual bool ShouldSerializeTableMappings ()
229 internal int FillInternal (DataTable dataTable, IDataReader dataReader)
231 if (dataReader.FieldCount == 0) {
239 string tableName = SetupSchema (SchemaType.Mapped, dataTable.TableName);
240 if (tableName != null) {
241 dataTable.TableName = tableName;
242 FillTable (dataTable, dataReader, 0, 0, ref count);
251 // this method builds the schema for a given datatable. it returns a int array with
252 // "array[ordinal of datatable column] == index of source column in data reader".
253 // each column in the datatable has a mapping to a specific column in the datareader,
254 // the int array represents this match.
255 internal int[] BuildSchema (IDataReader reader, DataTable table, SchemaType schemaType)
257 return BuildSchema (reader, table, schemaType, MissingSchemaAction,
258 MissingMappingAction, TableMappings);
262 /// Creates or Modifies the schema of the given DataTable based on the schema of
263 /// the reader and the arguments passed.
265 internal static int[] BuildSchema (IDataReader reader, DataTable table,
266 SchemaType schemaType,
267 MissingSchemaAction missingSchAction,
268 MissingMappingAction missingMapAction,
269 DataTableMappingCollection dtMapping
273 // FIXME : this fails if query has fewer columns than a table
274 int[] mapping = new int[table.Columns.Count]; // mapping the reader indexes to the datatable indexes
276 for(int i=0; i < mapping.Length; i++) {
280 ArrayList primaryKey = new ArrayList ();
281 ArrayList sourceColumns = new ArrayList ();
282 bool createPrimaryKey = true;
284 DataTable schemaTable = reader.GetSchemaTable ();
286 DataColumn ColumnNameCol = schemaTable.Columns["ColumnName"];
287 DataColumn DataTypeCol = schemaTable.Columns["DataType"];
288 DataColumn IsAutoIncrementCol = schemaTable.Columns["IsAutoIncrement"];
289 DataColumn AllowDBNullCol = schemaTable.Columns["AllowDBNull"];
290 DataColumn IsReadOnlyCol = schemaTable.Columns["IsReadOnly"];
291 DataColumn IsKeyCol = schemaTable.Columns["IsKey"];
292 DataColumn IsUniqueCol = schemaTable.Columns["IsUnique"];
293 DataColumn ColumnSizeCol = schemaTable.Columns["ColumnSize"];
295 foreach (DataRow schemaRow in schemaTable.Rows) {
296 // generate a unique column name in the source table.
297 string sourceColumnName;
298 string realSourceColumnName ;
299 if (ColumnNameCol == null || schemaRow.IsNull(ColumnNameCol) ||
300 (string)schemaRow [ColumnNameCol] == String.Empty) {
301 sourceColumnName = DefaultSourceColumnName;
302 realSourceColumnName = DefaultSourceColumnName + "1";
304 sourceColumnName = (string) schemaRow [ColumnNameCol];
305 realSourceColumnName = sourceColumnName;
308 for (int i = 1; sourceColumns.Contains (realSourceColumnName); i += 1)
309 realSourceColumnName = String.Format ("{0}{1}", sourceColumnName, i);
310 sourceColumns.Add(realSourceColumnName);
312 // generate DataSetColumnName from DataTableMapping, if any
313 DataTableMapping tableMapping = null;
315 //FIXME : The sourcetable name shud get passed as a parameter..
316 int index = dtMapping.IndexOfDataSetTable (table.TableName);
317 string srcTable = (index != -1 ? dtMapping[index].SourceTable : table.TableName);
318 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (dtMapping, srcTable, table.TableName, missingMapAction);
319 if (tableMapping != null) {
320 table.TableName = tableMapping.DataSetTable;
321 // check to see if the column mapping exists
322 DataColumnMapping columnMapping = DataColumnMappingCollection.GetColumnMappingBySchemaAction(tableMapping.ColumnMappings, realSourceColumnName, missingMapAction);
323 if (columnMapping != null) {
324 Type columnType = schemaRow[DataTypeCol] as Type;
325 DataColumn col = columnType != null ? columnMapping.GetDataColumnBySchemaAction(
328 missingSchAction) : null;
331 // if the column is not in the table - add it.
332 if (table.Columns.IndexOf(col) == -1) {
333 if (missingSchAction == MissingSchemaAction.Add
334 || missingSchAction == MissingSchemaAction.AddWithKey)
335 table.Columns.Add(col);
337 int[] tmp = new int[mapping.Length + 1];
338 Array.Copy(mapping,0,tmp,0,col.Ordinal);
339 Array.Copy(mapping,col.Ordinal,tmp,col.Ordinal + 1,mapping.Length - col.Ordinal);
343 if (missingSchAction == MissingSchemaAction.AddWithKey) {
344 object value = (AllowDBNullCol != null) ? schemaRow[AllowDBNullCol] : null;
345 bool allowDBNull = value is bool ? (bool)value : true;
347 value = (IsKeyCol != null) ? schemaRow[IsKeyCol] : null;
348 bool isKey = value is bool ? (bool)value : false;
350 value = (IsAutoIncrementCol != null) ? schemaRow[IsAutoIncrementCol] : null;
351 bool isAutoIncrement = value is bool ? (bool)value : false;
353 value = (IsReadOnlyCol != null) ? schemaRow[IsReadOnlyCol] : null;
354 bool isReadOnly = value is bool ? (bool)value : false;
356 value = (IsUniqueCol != null) ? schemaRow[IsUniqueCol] : null;
357 bool isUnique = value is bool ? (bool)value : false;
359 col.AllowDBNull = allowDBNull;
360 // fill woth key info
361 if (isAutoIncrement && DataColumn.CanAutoIncrement(columnType)) {
362 col.AutoIncrement = true;
364 col.AllowDBNull = false;
367 if (columnType == DbTypes.TypeOfString) {
368 col.MaxLength = (ColumnSizeCol != null) ? (int)schemaRow[ColumnSizeCol] : 0;
374 if (!allowDBNull && (!isReadOnly || isKey))
375 col.AllowDBNull = false;
376 if (isUnique && !isKey && !columnType.IsArray) {
379 col.AllowDBNull = false;
382 // This might not be set by all DataProviders
383 bool isHidden = false;
384 if (schemaTable.Columns.Contains ("IsHidden")) {
385 value = schemaRow["IsHidden"];
386 isHidden = ((value is bool) ? (bool)value : false);
389 if (isKey && !isHidden) {
390 primaryKey.Add (col);
392 createPrimaryKey = false;
395 // add the ordinal of the column as a key and the index of the column in the datareader as a value.
396 mapping[col.Ordinal] = readerIndex++;
401 if (primaryKey.Count > 0) {
402 DataColumn[] colKey = (DataColumn[])(primaryKey.ToArray(typeof (DataColumn)));
403 if (createPrimaryKey)
404 table.PrimaryKey = colKey;
406 UniqueConstraint uConstraint = new UniqueConstraint(colKey);
407 for (int i = 0; i < table.Constraints.Count; i++) {
408 if (table.Constraints[i].Equals(uConstraint)) {
414 if (uConstraint != null)
415 table.Constraints.Add(uConstraint);
421 internal bool FillTable (DataTable dataTable, IDataReader dataReader, int startRecord, int maxRecords, ref int counter)
423 if (dataReader.FieldCount == 0)
426 int counterStart = counter;
428 int[] mapping = BuildSchema (dataReader, dataTable, SchemaType.Mapped);
430 int [] sortedMapping = new int [mapping.Length];
431 int length = sortedMapping.Length;
432 for (int i = 0; i < sortedMapping.Length; i++) {
433 if (mapping [i] >= 0)
434 sortedMapping [mapping [i]] = i;
436 sortedMapping [--length] = i;
439 for (int i = 0; i < startRecord; i++) {
443 dataTable.BeginLoadData ();
444 while (dataReader.Read () && (maxRecords == 0 || (counter - counterStart) < maxRecords)) {
446 dataTable.LoadDataRow (dataReader, sortedMapping, length, AcceptChangesDuringFill);
449 catch (Exception e) {
450 object[] readerArray = new object [dataReader.FieldCount];
451 object[] tableArray = new object [mapping.Length];
452 // we get the values from the datareader
453 dataReader.GetValues (readerArray);
454 // copy from datareader columns to table columns according to given mapping
455 for (int i = 0; i < mapping.Length; i++) {
456 if (mapping [i] >= 0) {
457 tableArray [i] = readerArray [mapping [i]];
460 FillErrorEventArgs args = CreateFillErrorEvent (dataTable, tableArray, e);
461 OnFillErrorInternal (args);
463 // if args.Continue is not set to true or if a handler is not set, rethrow the error..
468 dataTable.EndLoadData ();
472 internal virtual void OnFillErrorInternal (FillErrorEventArgs value)
479 internal FillErrorEventArgs CreateFillErrorEvent (DataTable dataTable, object[] values, Exception e)
481 FillErrorEventArgs args = new FillErrorEventArgs (dataTable, values);
483 args.Continue = false;
487 internal string SetupSchema (SchemaType schemaType, string sourceTableName)
489 DataTableMapping tableMapping = null;
491 if (schemaType == SchemaType.Mapped) {
492 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (TableMappings, sourceTableName, sourceTableName, MissingMappingAction);
493 if (tableMapping != null)
494 return tableMapping.DataSetTable;
497 return sourceTableName;
500 internal int FillInternal (DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords)
503 throw new ArgumentNullException ("DataSet");
506 throw new ArgumentException ("The startRecord parameter was less than 0.");
508 throw new ArgumentException ("The maxRecords parameter was less than 0.");
510 DataTable dataTable = null;
515 string tableName = srcTable;
517 // Non-resultset queries like insert, delete or update aren't processed.
518 if (dataReader.FieldCount != -1) {
519 tableName = SetupSchema (SchemaType.Mapped, tableName);
520 if (tableName != null) {
522 // check if the table exists in the dataset
523 if (dataSet.Tables.Contains (tableName))
524 // get the table from the dataset
525 dataTable = dataSet.Tables [tableName];
527 // Do not create schema if MissingSchemAction is set to Ignore
528 if (this.MissingSchemaAction == MissingSchemaAction.Ignore)
530 dataTable = dataSet.Tables.Add (tableName);
533 if (!FillTable (dataTable, dataReader, startRecord, maxRecords, ref count))
536 tableName = String.Format ("{0}{1}", srcTable, ++resultIndex);
542 } while (dataReader.NextResult ());
551 public virtual int Fill (DataSet dataSet)
553 throw new NotSupportedException();
556 protected virtual int Fill (DataTable dataTable, IDataReader dataReader)
558 return FillInternal (dataTable, dataReader);
561 protected virtual int Fill (DataTable[] dataTables, IDataReader dataReader, int startRecord, int maxRecords)
564 if (dataReader.IsClosed)
568 throw new ArgumentException ("The startRecord parameter was less than 0.");
570 throw new ArgumentException ("The maxRecords parameter was less than 0.");
573 foreach (DataTable dataTable in dataTables) {
574 string tableName = SetupSchema (SchemaType.Mapped, dataTable.TableName);
575 if (tableName != null) {
576 dataTable.TableName = tableName;
577 FillTable (dataTable, dataReader, 0, 0, ref count);
587 protected virtual int Fill (DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords)
589 return FillInternal (dataSet, srcTable, dataReader, startRecord, maxRecords);
593 protected virtual DataTable FillSchema (DataTable dataTable, SchemaType schemaType, IDataReader dataReader)
595 throw new NotImplementedException ();
599 protected virtual DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType, string srcTable, IDataReader dataReader)
601 throw new NotImplementedException ();
604 public virtual DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType)
606 throw new NotSupportedException ();
610 [EditorBrowsable (EditorBrowsableState.Advanced)]
611 public virtual IDataParameter[] GetFillParameters ()
613 throw new NotImplementedException ();
616 protected bool HasTableMappings ()
618 return (TableMappings.Count != 0);
621 protected virtual void OnFillError (FillErrorEventArgs value)
623 if (FillError != null)
624 FillError (this, value);
627 [EditorBrowsable (EditorBrowsableState.Never)]
628 public void ResetFillLoadOption ()
630 //FIXME: what else ??
631 FillLoadOption = LoadOption.OverwriteChanges;
634 [EditorBrowsable (EditorBrowsableState.Never)]
635 public virtual bool ShouldSerializeAcceptChangesDuringFill ()
640 [EditorBrowsable (EditorBrowsableState.Never)]
641 public virtual bool ShouldSerializeFillLoadOption ()
647 public virtual int Update (DataSet dataSet)
649 throw new NotImplementedException ();
652 public abstract int Fill (DataSet dataSet);
653 public abstract DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType);
654 public abstract IDataParameter[] GetFillParameters ();
655 public abstract int Update (DataSet dataSet);