//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] // [....] //------------------------------------------------------------------------------ namespace System.Data.Common { using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Globalization; using System.Text; using System.Text.RegularExpressions; public abstract class DbCommandBuilder : Component { // V1.2.3300 private class ParameterNames { private const string DefaultOriginalPrefix = "Original_"; private const string DefaultIsNullPrefix = "IsNull_"; // we use alternative prefix if the default prefix fails parametername validation private const string AlternativeOriginalPrefix = "original"; private const string AlternativeIsNullPrefix = "isnull"; private const string AlternativeOriginalPrefix2 = "ORIGINAL"; private const string AlternativeIsNullPrefix2 = "ISNULL"; private string _originalPrefix; private string _isNullPrefix; private Regex _parameterNameParser; private DbCommandBuilder _dbCommandBuilder; private string[] _baseParameterNames; private string[] _originalParameterNames; private string[] _nullParameterNames; private bool[] _isMutatedName; private int _count; private int _genericParameterCount; private int _adjustedParameterNameMaxLength; internal ParameterNames(DbCommandBuilder dbCommandBuilder, DbSchemaRow[] schemaRows) { _dbCommandBuilder = dbCommandBuilder; _baseParameterNames = new string[schemaRows.Length]; _originalParameterNames = new string[schemaRows.Length]; _nullParameterNames = new string[schemaRows.Length]; _isMutatedName = new bool[schemaRows.Length]; _count = schemaRows.Length; _parameterNameParser = new Regex(_dbCommandBuilder.ParameterNamePattern, RegexOptions.ExplicitCapture | RegexOptions.Singleline); SetAndValidateNamePrefixes(); _adjustedParameterNameMaxLength = GetAdjustedParameterNameMaxLength(); // Generate the baseparameter names and remove conflicting names // No names will be generated for any name that is rejected due to invalid prefix, regex violation or // name conflict after mutation. // All null values will be replaced with generic parameter names // for (int i = 0; i < schemaRows.Length; i++) { if (null == schemaRows[i]) { continue; } bool isMutatedName = false; string columnName = schemaRows[i].ColumnName; // all names that start with original- or isNullPrefix are invalid if (null != _originalPrefix) { if (columnName.StartsWith(_originalPrefix, StringComparison.OrdinalIgnoreCase)) { continue; } } if (null != _isNullPrefix) { if (columnName.StartsWith(_isNullPrefix, StringComparison.OrdinalIgnoreCase)) { continue; } } // Mutate name if it contains space(s) if (columnName.IndexOf(' ') >= 0) { columnName = columnName.Replace(' ', '_'); isMutatedName = true; } // Validate name against regular expression if (!_parameterNameParser.IsMatch(columnName)) { continue; } // Validate name against adjusted max parametername length if (columnName.Length > _adjustedParameterNameMaxLength) { continue; } _baseParameterNames[i] = columnName; _isMutatedName[i] = isMutatedName; } EliminateConflictingNames(); // Generate names for original- and isNullparameters // no names will be generated if the prefix failed parametername validation for (int i = 0; i < schemaRows.Length; i++) { if (null != _baseParameterNames[i]) { if (null != _originalPrefix) { _originalParameterNames[i] = _originalPrefix + _baseParameterNames[i]; } if (null != _isNullPrefix) { // don't bother generating an 'IsNull' name if it's not used if (schemaRows[i].AllowDBNull) { _nullParameterNames[i] = _isNullPrefix + _baseParameterNames[i]; } } } } ApplyProviderSpecificFormat(); GenerateMissingNames(schemaRows); } private void SetAndValidateNamePrefixes() { if (_parameterNameParser.IsMatch(DefaultIsNullPrefix)) { _isNullPrefix = DefaultIsNullPrefix; } else if (_parameterNameParser.IsMatch(AlternativeIsNullPrefix)) { _isNullPrefix = AlternativeIsNullPrefix; } else if (_parameterNameParser.IsMatch(AlternativeIsNullPrefix2)) { _isNullPrefix = AlternativeIsNullPrefix2; } else { _isNullPrefix = null; } if (_parameterNameParser.IsMatch(DefaultOriginalPrefix)) { _originalPrefix = DefaultOriginalPrefix; } else if (_parameterNameParser.IsMatch(AlternativeOriginalPrefix)) { _originalPrefix = AlternativeOriginalPrefix; } else if (_parameterNameParser.IsMatch(AlternativeOriginalPrefix2)) { _originalPrefix = AlternativeOriginalPrefix2; } else { _originalPrefix = null; } } private void ApplyProviderSpecificFormat() { for (int i = 0; i < _baseParameterNames.Length; i++) { if (null != _baseParameterNames[i]) { _baseParameterNames[i] = _dbCommandBuilder.GetParameterName(_baseParameterNames[i]); } if (null != _originalParameterNames[i]) { _originalParameterNames[i] = _dbCommandBuilder.GetParameterName(_originalParameterNames[i]); } if (null != _nullParameterNames[i]) { _nullParameterNames[i] = _dbCommandBuilder.GetParameterName(_nullParameterNames[i]); } } } private void EliminateConflictingNames() { // for (int i = 0; i < _count - 1; i++) { string name = _baseParameterNames[i]; if (null != name) { for (int j = i + 1; j < _count; j++) { if (ADP.CompareInsensitiveInvariant(name, _baseParameterNames[j])) { // found duplicate name // the name unchanged name wins int iMutatedName = _isMutatedName[j] ? j : i; Debug.Assert(_isMutatedName[iMutatedName], String.Format(CultureInfo.InvariantCulture, "{0} expected to be a mutated name", _baseParameterNames[iMutatedName])); _baseParameterNames[iMutatedName] = null; // null out the culprit } } } } } // Generates parameternames that couldn't be generated from columnname internal void GenerateMissingNames(DbSchemaRow[] schemaRows) { // foreach name in base names // if base name is null // for base, original and nullnames (null names only if nullable) // do // generate name based on current index // increment index // search name in base names // loop while name occures in base names // end for // end foreach string name; for (int i = 0; i < _baseParameterNames.Length; i++) { name = _baseParameterNames[i]; if (null == name) { _baseParameterNames[i] = GetNextGenericParameterName(); _originalParameterNames[i] = GetNextGenericParameterName(); // don't bother generating an 'IsNull' name if it's not used if ((null != schemaRows[i]) && schemaRows[i].AllowDBNull) { _nullParameterNames[i] = GetNextGenericParameterName(); } } } } private int GetAdjustedParameterNameMaxLength() { int maxPrefixLength = Math.Max( (null != _isNullPrefix ? _isNullPrefix.Length : 0), (null != _originalPrefix ? _originalPrefix.Length : 0) ) + _dbCommandBuilder.GetParameterName("").Length; return _dbCommandBuilder.ParameterNameMaxLength - maxPrefixLength; } private string GetNextGenericParameterName() { string name; bool nameExist; do { nameExist = false; _genericParameterCount++; name = _dbCommandBuilder.GetParameterName(_genericParameterCount); for (int i = 0; i < _baseParameterNames.Length; i++) { if (ADP.CompareInsensitiveInvariant(_baseParameterNames[i], name)) { nameExist = true; break; } } } while (nameExist); return name; } internal string GetBaseParameterName(int index) { return (_baseParameterNames[index]); } internal string GetOriginalParameterName(int index) { return (_originalParameterNames[index]); } internal string GetNullParameterName(int index) { return (_nullParameterNames[index]); } } private const string DeleteFrom = "DELETE FROM "; private const string InsertInto = "INSERT INTO "; private const string DefaultValues = " DEFAULT VALUES"; private const string Values = " VALUES "; private const string Update = "UPDATE "; private const string Set = " SET "; private const string Where = " WHERE "; private const string SpaceLeftParenthesis = " ("; private const string Comma = ", "; private const string Equal = " = "; private const string LeftParenthesis = "("; private const string RightParenthesis = ")"; private const string NameSeparator = "."; private const string IsNull = " IS NULL"; private const string EqualOne = " = 1"; private const string And = " AND "; private const string Or = " OR "; private DbDataAdapter _dataAdapter; private DbCommand _insertCommand; private DbCommand _updateCommand; private DbCommand _deleteCommand; private MissingMappingAction _missingMappingAction; private ConflictOption _conflictDetection = ConflictOption.CompareAllSearchableValues; private bool _setAllValues = false; private bool _hasPartialPrimaryKey = false; private DataTable _dbSchemaTable; private DbSchemaRow[] _dbSchemaRows; private string[] _sourceColumnNames; private ParameterNames _parameterNames = null; private string _quotedBaseTableName; // quote strings to use around SQL object names private CatalogLocation _catalogLocation = CatalogLocation.Start; private string _catalogSeparator = NameSeparator; private string _schemaSeparator = NameSeparator; private string _quotePrefix = ""; private string _quoteSuffix = ""; private string _parameterNamePattern = null; private string _parameterMarkerFormat = null; private int _parameterNameMaxLength = 0; protected DbCommandBuilder() : base() { // V1.2.3300 } [ DefaultValueAttribute(ConflictOption.CompareAllSearchableValues), ResCategoryAttribute(Res.DataCategory_Update), ResDescriptionAttribute(Res.DbCommandBuilder_ConflictOption), ] virtual public ConflictOption ConflictOption { // V1.2.3300 get { return _conflictDetection; } set { switch(value) { case ConflictOption.CompareAllSearchableValues: case ConflictOption.CompareRowVersion: case ConflictOption.OverwriteChanges: _conflictDetection = value; break; default: throw ADP.InvalidConflictOptions(value); } } } [ DefaultValueAttribute(CatalogLocation.Start), ResCategoryAttribute(Res.DataCategory_Schema), ResDescriptionAttribute(Res.DbCommandBuilder_CatalogLocation), ] virtual public CatalogLocation CatalogLocation { // V1.2.3300, MDAC 79449 get { return _catalogLocation; } set { if (null != _dbSchemaTable) { throw ADP.NoQuoteChange(); } switch(value) { case CatalogLocation.Start: case CatalogLocation.End: _catalogLocation = value; break; default: throw ADP.InvalidCatalogLocation(value); } } } [ DefaultValueAttribute(DbCommandBuilder.NameSeparator), ResCategoryAttribute(Res.DataCategory_Schema), ResDescriptionAttribute(Res.DbCommandBuilder_CatalogSeparator), ] virtual public string CatalogSeparator { // V1.2.3300, MDAC 79449 get { string catalogSeparator = _catalogSeparator; return (((null != catalogSeparator) && (0 < catalogSeparator.Length)) ? catalogSeparator : NameSeparator); } set { if (null != _dbSchemaTable) { throw ADP.NoQuoteChange(); } _catalogSeparator = value; } } [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.DbCommandBuilder_DataAdapter), ] public DbDataAdapter DataAdapter { // V1.2.3300 get { return _dataAdapter; } set { if (_dataAdapter != value) { RefreshSchema(); if (null != _dataAdapter) { // derived should remove event handler from old adapter SetRowUpdatingHandler(_dataAdapter); _dataAdapter = null; } if (null != value) { // derived should add event handler to new adapter SetRowUpdatingHandler(value); _dataAdapter = value; } } } } internal int ParameterNameMaxLength { get { return _parameterNameMaxLength; } } internal string ParameterNamePattern { get { return _parameterNamePattern; } } private string QuotedBaseTableName { get { return _quotedBaseTableName; } } [ DefaultValueAttribute(""), ResCategoryAttribute(Res.DataCategory_Schema), ResDescriptionAttribute(Res.DbCommandBuilder_QuotePrefix), ] virtual public string QuotePrefix { // V1.2.3300, XXXCommandBuilder V1.0.3300 get { string quotePrefix = _quotePrefix; return ((null != quotePrefix) ? quotePrefix : ADP.StrEmpty); } set { if (null != _dbSchemaTable) { throw ADP.NoQuoteChange(); } _quotePrefix = value; } } [ DefaultValueAttribute(""), ResCategoryAttribute(Res.DataCategory_Schema), ResDescriptionAttribute(Res.DbCommandBuilder_QuoteSuffix), ] virtual public string QuoteSuffix { // V1.2.3300, XXXCommandBuilder V1.0.3300 get { string quoteSuffix = _quoteSuffix; return ((null != quoteSuffix) ? quoteSuffix : ADP.StrEmpty); } set { if (null != _dbSchemaTable) { throw ADP.NoQuoteChange(); } _quoteSuffix = value; } } [ DefaultValueAttribute(DbCommandBuilder.NameSeparator), ResCategoryAttribute(Res.DataCategory_Schema), ResDescriptionAttribute(Res.DbCommandBuilder_SchemaSeparator), ] virtual public string SchemaSeparator { // V1.2.3300, MDAC 79449 get { string schemaSeparator = _schemaSeparator; return (((null != schemaSeparator) && (0 < schemaSeparator.Length)) ? schemaSeparator : NameSeparator); } set { if (null != _dbSchemaTable) { throw ADP.NoQuoteChange(); } _schemaSeparator = value; } } [ DefaultValueAttribute(false), ResCategoryAttribute(Res.DataCategory_Schema), ResDescriptionAttribute(Res.DbCommandBuilder_SetAllValues), ] public bool SetAllValues { get { return _setAllValues; } set { _setAllValues = value; } } private DbCommand InsertCommand { get { return _insertCommand; } set { _insertCommand = value; } } private DbCommand UpdateCommand { get { return _updateCommand; } set { _updateCommand = value; } } private DbCommand DeleteCommand { get { return _deleteCommand; } set { _deleteCommand = value; } } private void BuildCache(bool closeConnection, DataRow dataRow, bool useColumnsForParameterNames) { // V1.2.3300 // Don't bother building the cache if it's done already; wait for // the user to call RefreshSchema first. if ((null != _dbSchemaTable) && (!useColumnsForParameterNames || (null != _parameterNames))) { return; } DataTable schemaTable = null; DbCommand srcCommand = GetSelectCommand(); DbConnection connection = srcCommand.Connection; if (null == connection) { throw ADP.MissingSourceCommandConnection(); } try { if (0 == (ConnectionState.Open & connection.State)) { connection.Open(); } else { closeConnection = false; } if (useColumnsForParameterNames) { DataTable dataTable = connection.GetSchema(DbMetaDataCollectionNames.DataSourceInformation); if (dataTable.Rows.Count == 1) { _parameterNamePattern = dataTable.Rows[0][DbMetaDataColumnNames.ParameterNamePattern] as string; _parameterMarkerFormat = dataTable.Rows[0][DbMetaDataColumnNames.ParameterMarkerFormat] as string; object oParameterNameMaxLength = dataTable.Rows[0][DbMetaDataColumnNames.ParameterNameMaxLength]; _parameterNameMaxLength = (oParameterNameMaxLength is int) ? (int)oParameterNameMaxLength : 0; // note that we protect against errors in the xml file! if (0 == _parameterNameMaxLength || null == _parameterNamePattern || null == _parameterMarkerFormat) { useColumnsForParameterNames = false; } } else { Debug.Assert(false, "Rowcount expected to be 1"); useColumnsForParameterNames = false; } } schemaTable = GetSchemaTable(srcCommand); } finally { if (closeConnection) { connection.Close(); } } if (null == schemaTable) { throw ADP.DynamicSQLNoTableInfo(); } #if DEBUG //if (AdapterSwitches.DbCommandBuilder.TraceVerbose) { // ADP.TraceDataTable("DbCommandBuilder", schemaTable); //} #endif BuildInformation(schemaTable); _dbSchemaTable = schemaTable; DbSchemaRow[] schemaRows = _dbSchemaRows; string[] srcColumnNames = new string[schemaRows.Length]; for (int i = 0; i < schemaRows.Length; ++i) { if (null != schemaRows[i]) { srcColumnNames[i] = schemaRows[i].ColumnName; } } _sourceColumnNames = srcColumnNames; if (useColumnsForParameterNames) { _parameterNames = new ParameterNames(this, schemaRows); } ADP.BuildSchemaTableInfoTableNames(srcColumnNames); } virtual protected DataTable GetSchemaTable (DbCommand sourceCommand) { using (IDataReader dataReader = sourceCommand.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)){ return dataReader.GetSchemaTable(); } } private void BuildInformation(DataTable schemaTable) { DbSchemaRow[] rows = DbSchemaRow.GetSortedSchemaRows(schemaTable, false); // MDAC 60609 if ((null == rows) || (0 == rows.Length)) { throw ADP.DynamicSQLNoTableInfo(); } string baseServerName = ""; // MDAC 72721, 73599 string baseCatalogName = ""; string baseSchemaName = ""; string baseTableName = null; for (int i = 0; i < rows.Length; ++i) { DbSchemaRow row = rows[i]; string tableName = row.BaseTableName; if ((null == tableName) || (0 == tableName.Length)) { rows[i] = null; continue; } string serverName = row.BaseServerName; string catalogName = row.BaseCatalogName; string schemaName = row.BaseSchemaName; if (null == serverName) { serverName = ""; } if (null == catalogName) { catalogName = ""; } if (null == schemaName) { schemaName = ""; } if (null == baseTableName) { baseServerName = serverName; baseCatalogName = catalogName; baseSchemaName = schemaName; baseTableName = tableName; } else if ( (0 != ADP.SrcCompare(baseTableName, tableName)) || (0 != ADP.SrcCompare(baseSchemaName, schemaName)) || (0 != ADP.SrcCompare(baseCatalogName, catalogName)) || (0 != ADP.SrcCompare(baseServerName, serverName))) { throw ADP.DynamicSQLJoinUnsupported(); } } if (0 == baseServerName.Length) { baseServerName = null; } if (0 == baseCatalogName.Length) { baseServerName = null; baseCatalogName = null; } if (0 == baseSchemaName.Length) { baseServerName = null; baseCatalogName = null; baseSchemaName = null; } if ((null == baseTableName) || (0 == baseTableName.Length)) { throw ADP.DynamicSQLNoTableInfo(); } CatalogLocation location = CatalogLocation; string catalogSeparator = CatalogSeparator; string schemaSeparator = SchemaSeparator; string quotePrefix = QuotePrefix; string quoteSuffix = QuoteSuffix; if (!ADP.IsEmpty(quotePrefix) && (-1 != baseTableName.IndexOf(quotePrefix, StringComparison.Ordinal))) { throw ADP.DynamicSQLNestedQuote(baseTableName, quotePrefix); } if (!ADP.IsEmpty(quoteSuffix) && (-1 != baseTableName.IndexOf(quoteSuffix, StringComparison.Ordinal))) { throw ADP.DynamicSQLNestedQuote(baseTableName, quoteSuffix); } System.Text.StringBuilder builder = new System.Text.StringBuilder(); if (CatalogLocation.Start == location) { // MDAC 79449 if (null != baseServerName) { builder.Append(ADP.BuildQuotedString(quotePrefix, quoteSuffix, baseServerName)); builder.Append(catalogSeparator); } if (null != baseCatalogName) { builder.Append(ADP.BuildQuotedString(quotePrefix, quoteSuffix, baseCatalogName)); builder.Append(catalogSeparator); } // } if (null != baseSchemaName) { builder.Append(ADP.BuildQuotedString(quotePrefix, quoteSuffix, baseSchemaName)); builder.Append(schemaSeparator); } // builder.Append(ADP.BuildQuotedString(quotePrefix, quoteSuffix, baseTableName)); if (CatalogLocation.End == location) { // MDAC 79449 if (null != baseServerName) { builder.Append(catalogSeparator); builder.Append(ADP.BuildQuotedString(quotePrefix, quoteSuffix, baseServerName)); } if (null != baseCatalogName) { builder.Append(catalogSeparator); builder.Append(ADP.BuildQuotedString(quotePrefix, quoteSuffix, baseCatalogName)); } } _quotedBaseTableName = builder.ToString(); _hasPartialPrimaryKey = false; foreach(DbSchemaRow row in rows) { if ((null != row) && (row.IsKey || row.IsUnique) && !row.IsLong && !row.IsRowVersion && row.IsHidden) { _hasPartialPrimaryKey = true; break; } } _dbSchemaRows = rows; } private DbCommand BuildDeleteCommand(DataTableMapping mappings, DataRow dataRow) { DbCommand command = InitializeCommand(DeleteCommand); StringBuilder builder = new StringBuilder(); int parameterCount = 0; Debug.Assert (!ADP.IsEmpty(_quotedBaseTableName), "no table name"); builder.Append(DeleteFrom); builder.Append(QuotedBaseTableName); parameterCount = BuildWhereClause(mappings, dataRow, builder, command, parameterCount, false); command.CommandText = builder.ToString(); RemoveExtraParameters(command, parameterCount); DeleteCommand = command; return command; } private DbCommand BuildInsertCommand(DataTableMapping mappings, DataRow dataRow) { DbCommand command = InitializeCommand(InsertCommand); StringBuilder builder = new StringBuilder(); int parameterCount = 0; string nextSeparator = SpaceLeftParenthesis; Debug.Assert (!ADP.IsEmpty(_quotedBaseTableName), "no table name"); builder.Append(InsertInto); builder.Append(QuotedBaseTableName); // search for the columns in that base table, to be the column clause DbSchemaRow[] schemaRows = _dbSchemaRows; string[] parameterName = new string[schemaRows.Length]; for (int i = 0; i < schemaRows.Length; ++i) { DbSchemaRow row = schemaRows[i]; if ( (null == row) || (0 == row.BaseColumnName.Length) || !IncludeInInsertValues(row) ) continue; object currentValue = null; string sourceColumn = _sourceColumnNames[i]; // If we're building a statement for a specific row, then check the // values to see whether the column should be included in the insert // statement or not if ((null != mappings) && (null != dataRow)) { DataColumn dataColumn = GetDataColumn(sourceColumn, mappings, dataRow); if (null == dataColumn) continue; // Don't bother inserting if the column is readonly in both the data // set and the back end. if (row.IsReadOnly && dataColumn.ReadOnly) continue; currentValue = GetColumnValue(dataRow, dataColumn, DataRowVersion.Current); // If the value is null, and the column doesn't support nulls, then // the user is requesting the server-specified default value, so don't // include it in the set-list. if ( !row.AllowDBNull && (null == currentValue || Convert.IsDBNull(currentValue)) ) continue; } builder.Append(nextSeparator); nextSeparator = Comma; builder.Append(QuotedColumn(row.BaseColumnName)); parameterName[parameterCount] = CreateParameterForValue( command, GetBaseParameterName(i), sourceColumn, DataRowVersion.Current, parameterCount, currentValue, row, StatementType.Insert, false ); parameterCount++; } if (0 == parameterCount) builder.Append(DefaultValues); else { builder.Append(RightParenthesis); builder.Append(Values); builder.Append(LeftParenthesis); builder.Append(parameterName[0]); for (int i = 1; i < parameterCount; ++i) { builder.Append(Comma); builder.Append(parameterName[i]); } builder.Append(RightParenthesis); } command.CommandText = builder.ToString(); RemoveExtraParameters(command, parameterCount); InsertCommand = command; return command; } private DbCommand BuildUpdateCommand(DataTableMapping mappings, DataRow dataRow) { DbCommand command = InitializeCommand(UpdateCommand); StringBuilder builder = new StringBuilder(); string nextSeparator = Set; int parameterCount = 0; Debug.Assert (!ADP.IsEmpty(_quotedBaseTableName), "no table name"); builder.Append(Update); builder.Append(QuotedBaseTableName); // search for the columns in that base table, to build the set clause DbSchemaRow[] schemaRows = _dbSchemaRows; for (int i = 0; i < schemaRows.Length; ++i) { DbSchemaRow row = schemaRows[i]; if ((null == row) || (0 == row.BaseColumnName.Length) || !IncludeInUpdateSet(row)) continue; object currentValue = null; string sourceColumn = _sourceColumnNames[i]; // If we're building a statement for a specific row, then check the // values to see whether the column should be included in the update // statement or not if ((null != mappings) && (null != dataRow)) { DataColumn dataColumn = GetDataColumn(sourceColumn, mappings, dataRow); if (null == dataColumn) continue; // Don't bother updating if the column is readonly in both the data // set and the back end. if (row.IsReadOnly && dataColumn.ReadOnly) continue; // Unless specifically directed to do so, we will not automatically update // a column with it's original value, which means that we must determine // whether the value has changed locally, before we send it up. currentValue = GetColumnValue(dataRow, dataColumn, DataRowVersion.Current); if (!SetAllValues) { object originalValue = GetColumnValue(dataRow, dataColumn, DataRowVersion.Original); if ((originalValue == currentValue) || ((null != originalValue) && originalValue.Equals(currentValue))) { continue; } } } builder.Append(nextSeparator); nextSeparator = Comma; builder.Append(QuotedColumn(row.BaseColumnName)); builder.Append(Equal); builder.Append( CreateParameterForValue( command, GetBaseParameterName(i), sourceColumn, DataRowVersion.Current, parameterCount, currentValue, row, StatementType.Update, false ) ); parameterCount++; } // It is an error to attempt an update when there's nothing to update; bool skipRow = (0 == parameterCount); parameterCount = BuildWhereClause(mappings, dataRow, builder, command, parameterCount, true); command.CommandText = builder.ToString(); RemoveExtraParameters(command, parameterCount); UpdateCommand = command; return (skipRow) ? null : command; } private int BuildWhereClause( DataTableMapping mappings, DataRow dataRow, StringBuilder builder, DbCommand command, int parameterCount, bool isUpdate ) { string beginNewCondition = string.Empty; int whereCount = 0; builder.Append(Where); builder.Append(LeftParenthesis); DbSchemaRow[] schemaRows = _dbSchemaRows; for (int i = 0; i < schemaRows.Length; ++i) { DbSchemaRow row = schemaRows[i]; if ((null == row) || (0 == row.BaseColumnName.Length) || !IncludeInWhereClause(row, isUpdate)) { continue; } builder.Append(beginNewCondition); beginNewCondition = And; object value = null; string sourceColumn = _sourceColumnNames[i]; string baseColumnName = QuotedColumn(row.BaseColumnName); if ((null != mappings) && (null != dataRow)) value = GetColumnValue(dataRow, sourceColumn, mappings, DataRowVersion.Original); if (!row.AllowDBNull) { // ( = ?) builder.Append(LeftParenthesis); builder.Append(baseColumnName); builder.Append(Equal); builder.Append( CreateParameterForValue( command, GetOriginalParameterName(i), sourceColumn, DataRowVersion.Original, parameterCount, value, row, (isUpdate ? StatementType.Update : StatementType.Delete), true ) ); parameterCount++; builder.Append(RightParenthesis); } else { // ((? = 1 AND IS NULL) OR ( = ?)) builder.Append(LeftParenthesis); builder.Append(LeftParenthesis); builder.Append( CreateParameterForNullTest( command, GetNullParameterName(i), sourceColumn, DataRowVersion.Original, parameterCount, value, row, (isUpdate ? StatementType.Update : StatementType.Delete), true ) ); parameterCount++; builder.Append(EqualOne); builder.Append(And); builder.Append(baseColumnName); builder.Append(IsNull); builder.Append(RightParenthesis); builder.Append(Or); builder.Append(LeftParenthesis); builder.Append(baseColumnName); builder.Append(Equal); builder.Append( CreateParameterForValue( command, GetOriginalParameterName(i), sourceColumn, DataRowVersion.Original, parameterCount, value, row, (isUpdate ? StatementType.Update : StatementType.Delete), true ) ); parameterCount++; builder.Append(RightParenthesis); builder.Append(RightParenthesis); } if (IncrementWhereCount(row)) { whereCount++; } } builder.Append(RightParenthesis); if (0 == whereCount) { if (isUpdate) { if (ConflictOption.CompareRowVersion == ConflictOption) { throw ADP.DynamicSQLNoKeyInfoRowVersionUpdate(); } throw ADP.DynamicSQLNoKeyInfoUpdate(); } else { if (ConflictOption.CompareRowVersion == ConflictOption) { throw ADP.DynamicSQLNoKeyInfoRowVersionDelete(); } throw ADP.DynamicSQLNoKeyInfoDelete(); } } return parameterCount; } private string CreateParameterForNullTest( DbCommand command, string parameterName, string sourceColumn, DataRowVersion version, int parameterCount, object value, DbSchemaRow row, StatementType statementType, bool whereClause ) { DbParameter p = GetNextParameter(command, parameterCount); Debug.Assert(!ADP.IsEmpty(sourceColumn), "empty source column"); if (null == parameterName) { p.ParameterName = GetParameterName(1 + parameterCount); } else { p.ParameterName = parameterName; } p.Direction = ParameterDirection.Input; p.SourceColumn = sourceColumn; p.SourceVersion = version; p.SourceColumnNullMapping = true; p.Value = value; p.Size = 0; // don't specify parameter.Size so that we don't silently truncate to the metadata size ApplyParameterInfo(p, row.DataRow, statementType, whereClause); p.DbType = DbType.Int32; p.Value = ADP.IsNull(value) ? DbDataAdapter.ParameterValueNullValue : DbDataAdapter.ParameterValueNonNullValue; if (!command.Parameters.Contains(p)) { command.Parameters.Add(p); } if (null == parameterName) { return GetParameterPlaceholder(1 + parameterCount); } else { Debug.Assert(null != _parameterNames, "How can we have a parameterName without a _parameterNames collection?"); Debug.Assert(null != _parameterMarkerFormat, "How can we have a _parameterNames collection but no _parameterMarkerFormat?"); return String.Format(CultureInfo.InvariantCulture, _parameterMarkerFormat, parameterName); } } private string CreateParameterForValue( DbCommand command, string parameterName, string sourceColumn, DataRowVersion version, int parameterCount, object value, DbSchemaRow row, StatementType statementType, bool whereClause ) { DbParameter p = GetNextParameter(command, parameterCount); if (null == parameterName) { p.ParameterName = GetParameterName(1 + parameterCount); } else { p.ParameterName = parameterName; } p.Direction = ParameterDirection.Input; p.SourceColumn = sourceColumn; p.SourceVersion = version; p.SourceColumnNullMapping = false; p.Value = value; p.Size = 0; // don't specify parameter.Size so that we don't silently truncate to the metadata size ApplyParameterInfo(p, row.DataRow, statementType, whereClause); if (!command.Parameters.Contains(p)) { command.Parameters.Add(p); } if (null == parameterName) { return GetParameterPlaceholder(1 + parameterCount); } else { Debug.Assert(null != _parameterNames, "How can we have a parameterName without a _parameterNames collection?"); Debug.Assert(null != _parameterMarkerFormat, "How can we have a _parameterNames collection but no _parameterMarkerFormat?"); return String.Format(CultureInfo.InvariantCulture, _parameterMarkerFormat, parameterName); } } override protected void Dispose(bool disposing) { // V1.2.3300, XXXCommandBuilder V1.0.3300 // MDAC 65459 if (disposing) { // release mananged objects DataAdapter = null; } //release unmanaged objects base.Dispose(disposing); // notify base classes } private DataTableMapping GetTableMapping(DataRow dataRow ) { DataTableMapping tableMapping = null; if (null != dataRow) { DataTable dataTable = dataRow.Table; if (null != dataTable) { DbDataAdapter adapter = DataAdapter; if (null != adapter) { tableMapping = adapter.GetTableMapping(dataTable); } else { string tableName = dataTable.TableName; tableMapping = new DataTableMapping(tableName, tableName); } } } return tableMapping; } private string GetBaseParameterName(int index) { if (null != _parameterNames) { return (_parameterNames.GetBaseParameterName(index)); } else { return null; } } private string GetOriginalParameterName(int index) { if (null != _parameterNames) { return (_parameterNames.GetOriginalParameterName(index)); } else { return null; } } private string GetNullParameterName(int index) { if (null != _parameterNames) { return (_parameterNames.GetNullParameterName(index)); } else { return null; } } private DbCommand GetSelectCommand() { // V1.2.3300 DbCommand select = null; DbDataAdapter adapter = DataAdapter; if (null != adapter) { if (0 == _missingMappingAction) { _missingMappingAction = adapter.MissingMappingAction; } select = (DbCommand)adapter.SelectCommand; } if (null == select) { throw ADP.MissingSourceCommand(); } return select; } // open connection is required by OleDb/OdbcCommandBuilder.QuoteIdentifier and UnquoteIdentifier // to get literals quotes from the driver internal DbConnection GetConnection() { DbDataAdapter adapter = DataAdapter; if (adapter != null) { DbCommand select = (DbCommand)adapter.SelectCommand; if (select != null) { return select.Connection; } } return null; } public DbCommand GetInsertCommand() { // V1.2.3300, XXXCommandBuilder V1.0.3300 return GetInsertCommand((DataRow)null, false); } public DbCommand GetInsertCommand(bool useColumnsForParameterNames) { return GetInsertCommand((DataRow)null, useColumnsForParameterNames); } internal DbCommand GetInsertCommand(DataRow dataRow, bool useColumnsForParameterNames) { BuildCache(true, dataRow, useColumnsForParameterNames); BuildInsertCommand(GetTableMapping(dataRow), dataRow); return InsertCommand; } public DbCommand GetUpdateCommand() { // V1.2.3300, XXXCommandBuilder V1.0.3300 return GetUpdateCommand((DataRow)null, false); } public DbCommand GetUpdateCommand(bool useColumnsForParameterNames) { return GetUpdateCommand((DataRow)null, useColumnsForParameterNames); } internal DbCommand GetUpdateCommand(DataRow dataRow, bool useColumnsForParameterNames) { BuildCache(true, dataRow, useColumnsForParameterNames); BuildUpdateCommand(GetTableMapping(dataRow), dataRow); return UpdateCommand; } public DbCommand GetDeleteCommand() { // V1.2.3300, XXXCommandBuilder V1.0.3300 return GetDeleteCommand((DataRow)null, false); } public DbCommand GetDeleteCommand(bool useColumnsForParameterNames) { return GetDeleteCommand((DataRow)null, useColumnsForParameterNames); } internal DbCommand GetDeleteCommand(DataRow dataRow, bool useColumnsForParameterNames) { BuildCache(true, dataRow, useColumnsForParameterNames); BuildDeleteCommand(GetTableMapping(dataRow), dataRow); return DeleteCommand; } private object GetColumnValue(DataRow row, String columnName, DataTableMapping mappings, DataRowVersion version) { return GetColumnValue(row, GetDataColumn(columnName, mappings, row), version); } private object GetColumnValue(DataRow row, DataColumn column, DataRowVersion version) { object value = null; if (null != column) { value = row[column, version]; } return value; } private DataColumn GetDataColumn(string columnName, DataTableMapping tablemapping, DataRow row) { DataColumn column = null; if (!ADP.IsEmpty(columnName)) { column = tablemapping.GetDataColumn(columnName, null, row.Table, _missingMappingAction, MissingSchemaAction.Error); } return column; } static private DbParameter GetNextParameter(DbCommand command, int pcount) { DbParameter p; if (pcount < command.Parameters.Count) { p = command.Parameters[pcount]; } else { p = command.CreateParameter(); /*if (null == p) { // */ } Debug.Assert(null != p, "null CreateParameter"); return p; } private bool IncludeInInsertValues(DbSchemaRow row) { // return (!row.IsAutoIncrement && !row.IsHidden && !row.IsExpression && !row.IsRowVersion && !row.IsReadOnly); } private bool IncludeInUpdateSet(DbSchemaRow row) { // return (!row.IsAutoIncrement && !row.IsRowVersion && !row.IsHidden && !row.IsReadOnly); } private bool IncludeInWhereClause(DbSchemaRow row, bool isUpdate) { bool flag = IncrementWhereCount(row); if (flag && row.IsHidden) { // MDAC 52564 if (ConflictOption.CompareRowVersion == ConflictOption) { throw ADP.DynamicSQLNoKeyInfoRowVersionUpdate(); } throw ADP.DynamicSQLNoKeyInfoUpdate(); } if (!flag && (ConflictOption.CompareAllSearchableValues == ConflictOption)) { // include other searchable values flag = !row.IsLong && !row.IsRowVersion && !row.IsHidden; } return flag; } private bool IncrementWhereCount(DbSchemaRow row) { ConflictOption value = ConflictOption; switch(value) { case ConflictOption.CompareAllSearchableValues: case ConflictOption.OverwriteChanges: // find the primary key return (row.IsKey || row.IsUnique) && !row.IsLong && !row.IsRowVersion; case ConflictOption.CompareRowVersion: // or the row version return (((row.IsKey || row.IsUnique) && !_hasPartialPrimaryKey) || row.IsRowVersion) && !row.IsLong; default: throw ADP.InvalidConflictOptions(value); } } virtual protected DbCommand InitializeCommand(DbCommand command) { // V1.2.3300 if (null == command) { DbCommand select = GetSelectCommand(); command = select.Connection.CreateCommand(); /*if (null == command) { // */ // the following properties are only initialized when the object is created // all other properites are reinitialized on every row /*command.Connection = select.Connection;*/ // initialized by CreateCommand command.CommandTimeout = select.CommandTimeout; command.Transaction = select.Transaction; } command.CommandType = CommandType.Text; command.UpdatedRowSource = UpdateRowSource.None; // no select or output parameters expected return command; } private string QuotedColumn(string column) { return ADP.BuildQuotedString(QuotePrefix, QuoteSuffix, column); } public virtual string QuoteIdentifier(string unquotedIdentifier ) { throw ADP.NotSupported(); } virtual public void RefreshSchema() { // V1.2.3300, XXXCommandBuilder V1.0.3300 _dbSchemaTable = null; _dbSchemaRows = null; _sourceColumnNames = null; _quotedBaseTableName = null; DbDataAdapter adapter = DataAdapter; if (null != adapter) { // MDAC 66016 if (InsertCommand == adapter.InsertCommand) { adapter.InsertCommand = null; } if (UpdateCommand == adapter.UpdateCommand) { adapter.UpdateCommand = null; } if (DeleteCommand == adapter.DeleteCommand) { adapter.DeleteCommand = null; } } DbCommand command; if (null != (command = InsertCommand)) { command.Dispose(); } if (null != (command = UpdateCommand)) { command.Dispose(); } if (null != (command = DeleteCommand)) { command.Dispose(); } InsertCommand = null; UpdateCommand = null; DeleteCommand = null; } static private void RemoveExtraParameters(DbCommand command, int usedParameterCount) { for (int i = command.Parameters.Count-1; i >= usedParameterCount; --i) { command.Parameters.RemoveAt(i); } } protected void RowUpdatingHandler(RowUpdatingEventArgs rowUpdatingEvent) { if (null == rowUpdatingEvent) { throw ADP.ArgumentNull("rowUpdatingEvent"); } try { if (UpdateStatus.Continue == rowUpdatingEvent.Status) { StatementType stmtType = rowUpdatingEvent.StatementType; DbCommand command = (DbCommand)rowUpdatingEvent.Command; if (null != command) { switch(stmtType) { case StatementType.Select: Debug.Assert(false, "how did we get here?"); return; // don't mess with it case StatementType.Insert: command = InsertCommand; break; case StatementType.Update: command = UpdateCommand; break; case StatementType.Delete: command = DeleteCommand; break; default: throw ADP.InvalidStatementType(stmtType); } if (command != rowUpdatingEvent.Command) { command = (DbCommand)rowUpdatingEvent.Command; if ((null != command) && (null == command.Connection)) { // MDAC 87649 DbDataAdapter adapter = DataAdapter; DbCommand select = ((null != adapter) ? ((DbCommand)adapter.SelectCommand) : null); if (null != select) { command.Connection = (DbConnection)select.Connection; } } // user command, not a command builder command } else command = null; } if (null == command) { RowUpdatingHandlerBuilder(rowUpdatingEvent); } } } catch(Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } ADP.TraceExceptionForCapture(e); rowUpdatingEvent.Status = UpdateStatus.ErrorsOccurred; rowUpdatingEvent.Errors = e; } } private void RowUpdatingHandlerBuilder(RowUpdatingEventArgs rowUpdatingEvent) { // MDAC 58710 - unable to tell Update method that Event opened connection and Update needs to close when done // HackFix - the Update method will close the connection if command was null and returned command.Connection is same as SelectCommand.Connection DataRow datarow = rowUpdatingEvent.Row; BuildCache(false, datarow, false); DbCommand command; switch(rowUpdatingEvent.StatementType) { case StatementType.Insert: command = BuildInsertCommand(rowUpdatingEvent.TableMapping, datarow); break; case StatementType.Update: command = BuildUpdateCommand(rowUpdatingEvent.TableMapping, datarow); break; case StatementType.Delete: command = BuildDeleteCommand(rowUpdatingEvent.TableMapping, datarow); break; #if DEBUG case StatementType.Select: Debug.Assert(false, "how did we get here?"); goto default; #endif default: throw ADP.InvalidStatementType(rowUpdatingEvent.StatementType); } if (null == command) { if (null != datarow) { datarow.AcceptChanges(); } rowUpdatingEvent.Status = UpdateStatus.SkipCurrentRow; } rowUpdatingEvent.Command = command; } public virtual string UnquoteIdentifier(string quotedIdentifier ) { throw ADP.NotSupported(); } abstract protected void ApplyParameterInfo(DbParameter parameter, DataRow row, StatementType statementType, bool whereClause); // V1.2.3300 abstract protected string GetParameterName(int parameterOrdinal); // V1.2.3300 abstract protected string GetParameterName(string parameterName); abstract protected string GetParameterPlaceholder(int parameterOrdinal); // V1.2.3300 abstract protected void SetRowUpdatingHandler(DbDataAdapter adapter); // V1.2.3300 // static internal string[] ParseProcedureName(string name, string quotePrefix, string quoteSuffix) { // Procedure may consist of up to four parts: // 0) Server // 1) Catalog // 2) Schema // 3) ProcedureName // // Parse the string into four parts, allowing the last part to contain '.'s. // If less than four period delimited parts, use the parts from procedure backwards. // const string Separator = "."; string[] qualifiers = new string[4]; if (!ADP.IsEmpty(name)) { bool useQuotes = !ADP.IsEmpty(quotePrefix) && !ADP.IsEmpty(quoteSuffix); int currentPos = 0, parts; for(parts = 0; (parts < qualifiers.Length) && (currentPos < name.Length); ++parts) { int startPos = currentPos; // does the part begin with a quotePrefix? if (useQuotes && (name.IndexOf(quotePrefix, currentPos, quotePrefix.Length, StringComparison.Ordinal) == currentPos)) { currentPos += quotePrefix.Length; // move past the quotePrefix // search for the quoteSuffix (or end of string) while (currentPos < name.Length) { currentPos = name.IndexOf(quoteSuffix, currentPos, StringComparison.Ordinal); if (currentPos < 0) { // error condition, no quoteSuffix currentPos = name.Length; break; } else { currentPos += quoteSuffix.Length; // move past the quoteSuffix // is this a double quoteSuffix? if ((currentPos < name.Length) && (name.IndexOf(quoteSuffix, currentPos, quoteSuffix.Length, StringComparison.Ordinal) == currentPos)) { // a second quoteSuffix, continue search for terminating quoteSuffix currentPos += quoteSuffix.Length; // move past the second quoteSuffix } else { // found the terminating quoteSuffix break; } } } } // search for separator (either no quotePrefix or already past quoteSuffix) if (currentPos < name.Length) { currentPos = name.IndexOf(Separator, currentPos, StringComparison.Ordinal); if ((currentPos < 0) || (parts == qualifiers.Length-1)) { // last part that can be found currentPos = name.Length; } } qualifiers[parts] = name.Substring(startPos, currentPos-startPos); currentPos += Separator.Length; } // allign the qualifiers if we had less than MaxQualifiers for(int j = qualifiers.Length-1; 0 <= j; --j) { qualifiers[j] = ((0 < parts) ? qualifiers[--parts] : null); } } return qualifiers; } } }