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;
203 [Obsolete ("Use the protected constructor instead")]
205 protected virtual DataAdapter CloneInternals ()
207 throw new NotImplementedException ();
210 protected virtual DataTableMappingCollection CreateTableMappings ()
212 return new DataTableMappingCollection ();
216 protected override void Dispose (bool disposing)
218 throw new NotImplementedException ();
221 protected virtual bool ShouldSerializeTableMappings ()
227 internal int FillInternal (DataTable dataTable, IDataReader dataReader)
229 if (dataReader.FieldCount == 0) {
237 string tableName = SetupSchema (SchemaType.Mapped, dataTable.TableName);
238 if (tableName != null) {
239 dataTable.TableName = tableName;
240 FillTable (dataTable, dataReader, 0, 0, ref count);
249 // this method builds the schema for a given datatable. it returns a int array with
250 // "array[ordinal of datatable column] == index of source column in data reader".
251 // each column in the datatable has a mapping to a specific column in the datareader,
252 // the int array represents this match.
253 internal int[] BuildSchema (IDataReader reader, DataTable table, SchemaType schemaType)
255 return BuildSchema (reader, table, schemaType, MissingSchemaAction,
256 MissingMappingAction, TableMappings);
260 /// Creates or Modifies the schema of the given DataTable based on the schema of
261 /// the reader and the arguments passed.
263 internal static int[] BuildSchema (IDataReader reader, DataTable table,
264 SchemaType schemaType,
265 MissingSchemaAction missingSchAction,
266 MissingMappingAction missingMapAction,
267 DataTableMappingCollection dtMapping
271 // FIXME : this fails if query has fewer columns than a table
272 int[] mapping = new int[table.Columns.Count]; // mapping the reader indexes to the datatable indexes
274 for(int i=0; i < mapping.Length; i++) {
278 ArrayList primaryKey = new ArrayList ();
279 ArrayList sourceColumns = new ArrayList ();
280 bool createPrimaryKey = true;
282 DataTable schemaTable = reader.GetSchemaTable ();
284 DataColumn ColumnNameCol = schemaTable.Columns["ColumnName"];
285 DataColumn DataTypeCol = schemaTable.Columns["DataType"];
286 DataColumn IsAutoIncrementCol = schemaTable.Columns["IsAutoIncrement"];
287 DataColumn AllowDBNullCol = schemaTable.Columns["AllowDBNull"];
288 DataColumn IsReadOnlyCol = schemaTable.Columns["IsReadOnly"];
289 DataColumn IsKeyCol = schemaTable.Columns["IsKey"];
290 DataColumn IsUniqueCol = schemaTable.Columns["IsUnique"];
291 DataColumn ColumnSizeCol = schemaTable.Columns["ColumnSize"];
293 foreach (DataRow schemaRow in schemaTable.Rows) {
294 // generate a unique column name in the source table.
295 string sourceColumnName;
296 string realSourceColumnName ;
297 if (ColumnNameCol == null || schemaRow.IsNull(ColumnNameCol) ||
298 (string)schemaRow [ColumnNameCol] == String.Empty) {
299 sourceColumnName = DefaultSourceColumnName;
300 realSourceColumnName = DefaultSourceColumnName + "1";
302 sourceColumnName = (string) schemaRow [ColumnNameCol];
303 realSourceColumnName = sourceColumnName;
306 for (int i = 1; sourceColumns.Contains (realSourceColumnName); i += 1)
307 realSourceColumnName = String.Format ("{0}{1}", sourceColumnName, i);
308 sourceColumns.Add(realSourceColumnName);
310 // generate DataSetColumnName from DataTableMapping, if any
311 DataTableMapping tableMapping = null;
313 //FIXME : The sourcetable name shud get passed as a parameter..
314 int index = dtMapping.IndexOfDataSetTable (table.TableName);
315 string srcTable = (index != -1 ? dtMapping[index].SourceTable : table.TableName);
316 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (dtMapping, srcTable, table.TableName, missingMapAction);
317 if (tableMapping != null) {
318 table.TableName = tableMapping.DataSetTable;
319 // check to see if the column mapping exists
320 DataColumnMapping columnMapping = DataColumnMappingCollection.GetColumnMappingBySchemaAction(tableMapping.ColumnMappings, realSourceColumnName, missingMapAction);
321 if (columnMapping != null) {
322 Type columnType = schemaRow[DataTypeCol] as Type;
323 DataColumn col = columnType != null ? columnMapping.GetDataColumnBySchemaAction(
326 missingSchAction) : null;
329 // if the column is not in the table - add it.
330 if (table.Columns.IndexOf(col) == -1) {
331 if (missingSchAction == MissingSchemaAction.Add
332 || missingSchAction == MissingSchemaAction.AddWithKey)
333 table.Columns.Add(col);
335 int[] tmp = new int[mapping.Length + 1];
336 Array.Copy(mapping,0,tmp,0,col.Ordinal);
337 Array.Copy(mapping,col.Ordinal,tmp,col.Ordinal + 1,mapping.Length - col.Ordinal);
341 if (missingSchAction == MissingSchemaAction.AddWithKey) {
342 object value = (AllowDBNullCol != null) ? schemaRow[AllowDBNullCol] : null;
343 bool allowDBNull = value is bool ? (bool)value : true;
345 value = (IsKeyCol != null) ? schemaRow[IsKeyCol] : null;
346 bool isKey = value is bool ? (bool)value : false;
348 value = (IsAutoIncrementCol != null) ? schemaRow[IsAutoIncrementCol] : null;
349 bool isAutoIncrement = value is bool ? (bool)value : false;
351 value = (IsReadOnlyCol != null) ? schemaRow[IsReadOnlyCol] : null;
352 bool isReadOnly = value is bool ? (bool)value : false;
354 value = (IsUniqueCol != null) ? schemaRow[IsUniqueCol] : null;
355 bool isUnique = value is bool ? (bool)value : false;
357 col.AllowDBNull = allowDBNull;
358 // fill woth key info
359 if (isAutoIncrement && DataColumn.CanAutoIncrement(columnType)) {
360 col.AutoIncrement = true;
362 col.AllowDBNull = false;
365 if (columnType == DbTypes.TypeOfString) {
366 col.MaxLength = (ColumnSizeCol != null) ? (int)schemaRow[ColumnSizeCol] : 0;
372 if (!allowDBNull && (!isReadOnly || isKey))
373 col.AllowDBNull = false;
374 if (isUnique && !isKey && !columnType.IsArray) {
377 col.AllowDBNull = false;
380 // This might not be set by all DataProviders
381 bool isHidden = false;
382 if (schemaTable.Columns.Contains ("IsHidden")) {
383 value = schemaRow["IsHidden"];
384 isHidden = ((value is bool) ? (bool)value : false);
387 if (isKey && !isHidden) {
388 primaryKey.Add (col);
390 createPrimaryKey = false;
393 // add the ordinal of the column as a key and the index of the column in the datareader as a value.
394 mapping[col.Ordinal] = readerIndex++;
399 if (primaryKey.Count > 0) {
400 DataColumn[] colKey = (DataColumn[])(primaryKey.ToArray(typeof (DataColumn)));
401 if (createPrimaryKey)
402 table.PrimaryKey = colKey;
404 UniqueConstraint uConstraint = new UniqueConstraint(colKey);
405 for (int i = 0; i < table.Constraints.Count; i++) {
406 if (table.Constraints[i].Equals(uConstraint)) {
412 if (uConstraint != null)
413 table.Constraints.Add(uConstraint);
419 internal bool FillTable (DataTable dataTable, IDataReader dataReader, int startRecord, int maxRecords, ref int counter)
421 if (dataReader.FieldCount == 0)
424 int counterStart = counter;
426 int[] mapping = BuildSchema (dataReader, dataTable, SchemaType.Mapped);
428 int [] sortedMapping = new int [mapping.Length];
429 int length = sortedMapping.Length;
430 for (int i = 0; i < sortedMapping.Length; i++) {
431 if (mapping [i] >= 0)
432 sortedMapping [mapping [i]] = i;
434 sortedMapping [--length] = i;
437 for (int i = 0; i < startRecord; i++) {
441 dataTable.BeginLoadData ();
442 while (dataReader.Read () && (maxRecords == 0 || (counter - counterStart) < maxRecords)) {
444 dataTable.LoadDataRow (dataReader, sortedMapping, length, AcceptChangesDuringFill);
447 catch (Exception e) {
448 object[] readerArray = new object [dataReader.FieldCount];
449 object[] tableArray = new object [mapping.Length];
450 // we get the values from the datareader
451 dataReader.GetValues (readerArray);
452 // copy from datareader columns to table columns according to given mapping
453 for (int i = 0; i < mapping.Length; i++) {
454 if (mapping [i] >= 0) {
455 tableArray [i] = readerArray [mapping [i]];
458 FillErrorEventArgs args = CreateFillErrorEvent (dataTable, tableArray, e);
459 OnFillErrorInternal (args);
461 // if args.Continue is not set to true or if a handler is not set, rethrow the error..
466 dataTable.EndLoadData ();
470 internal virtual void OnFillErrorInternal (FillErrorEventArgs value)
477 internal FillErrorEventArgs CreateFillErrorEvent (DataTable dataTable, object[] values, Exception e)
479 FillErrorEventArgs args = new FillErrorEventArgs (dataTable, values);
481 args.Continue = false;
485 internal string SetupSchema (SchemaType schemaType, string sourceTableName)
487 DataTableMapping tableMapping = null;
489 if (schemaType == SchemaType.Mapped) {
490 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (TableMappings, sourceTableName, sourceTableName, MissingMappingAction);
491 if (tableMapping != null)
492 return tableMapping.DataSetTable;
495 return sourceTableName;
498 internal int FillInternal (DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords)
501 throw new ArgumentNullException ("DataSet");
504 throw new ArgumentException ("The startRecord parameter was less than 0.");
506 throw new ArgumentException ("The maxRecords parameter was less than 0.");
508 DataTable dataTable = null;
513 string tableName = srcTable;
515 // Non-resultset queries like insert, delete or update aren't processed.
516 if (dataReader.FieldCount != -1) {
517 tableName = SetupSchema (SchemaType.Mapped, tableName);
518 if (tableName != null) {
520 // check if the table exists in the dataset
521 if (dataSet.Tables.Contains (tableName))
522 // get the table from the dataset
523 dataTable = dataSet.Tables [tableName];
525 // Do not create schema if MissingSchemAction is set to Ignore
526 if (this.MissingSchemaAction == MissingSchemaAction.Ignore)
528 dataTable = dataSet.Tables.Add (tableName);
531 if (!FillTable (dataTable, dataReader, startRecord, maxRecords, ref count))
534 tableName = String.Format ("{0}{1}", srcTable, ++resultIndex);
540 } while (dataReader.NextResult ());
549 public virtual int Fill (DataSet dataSet)
551 throw new NotSupportedException();
554 protected virtual int Fill (DataTable dataTable, IDataReader dataReader)
556 return FillInternal (dataTable, dataReader);
559 protected virtual int Fill (DataTable[] dataTables, IDataReader dataReader, int startRecord, int maxRecords)
562 if (dataReader.IsClosed)
566 throw new ArgumentException ("The startRecord parameter was less than 0.");
568 throw new ArgumentException ("The maxRecords parameter was less than 0.");
571 foreach (DataTable dataTable in dataTables) {
572 string tableName = SetupSchema (SchemaType.Mapped, dataTable.TableName);
573 if (tableName != null) {
574 dataTable.TableName = tableName;
575 FillTable (dataTable, dataReader, 0, 0, ref count);
585 protected virtual int Fill (DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords)
587 return FillInternal (dataSet, srcTable, dataReader, startRecord, maxRecords);
591 protected virtual DataTable FillSchema (DataTable dataTable, SchemaType schemaType, IDataReader dataReader)
593 throw new NotImplementedException ();
597 protected virtual DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType, string srcTable, IDataReader dataReader)
599 throw new NotImplementedException ();
602 public virtual DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType)
604 throw new NotSupportedException ();
608 [EditorBrowsable (EditorBrowsableState.Advanced)]
609 public virtual IDataParameter[] GetFillParameters ()
611 throw new NotImplementedException ();
614 protected bool HasTableMappings ()
616 return (TableMappings.Count != 0);
619 protected virtual void OnFillError (FillErrorEventArgs value)
621 if (FillError != null)
622 FillError (this, value);
625 [EditorBrowsable (EditorBrowsableState.Never)]
626 public void ResetFillLoadOption ()
628 //FIXME: what else ??
629 FillLoadOption = LoadOption.OverwriteChanges;
632 [EditorBrowsable (EditorBrowsableState.Never)]
633 public virtual bool ShouldSerializeAcceptChangesDuringFill ()
638 [EditorBrowsable (EditorBrowsableState.Never)]
639 public virtual bool ShouldSerializeFillLoadOption ()
645 public virtual int Update (DataSet dataSet)
647 throw new NotImplementedException ();
650 public abstract int Fill (DataSet dataSet);
651 public abstract DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType);
652 public abstract IDataParameter[] GetFillParameters ();
653 public abstract int Update (DataSet dataSet);