2 // System.Data.SqlClient.SqlCommandBuilder.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Veerapuram Varadhan (vvaradhan@novell.com)
8 // Copyright (C) Tim Coleman, 2002
12 // Copyright (C) 2004, 2009 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System
.Collections
;
36 using System
.ComponentModel
;
38 using System
.Data
.Common
;
40 using System
.Data
.SqlTypes
;
44 namespace System
.Data
.SqlClient
47 public sealed class SqlCommandBuilder
: DbCommandBuilder
49 public sealed class SqlCommandBuilder
: Component
57 DataTable dbSchemaTable
;
61 SqlDataAdapter adapter
;
62 SqlCommand insertCommand
;
63 SqlCommand deleteCommand
;
64 SqlCommand updateCommand
;
65 // Used to construct WHERE clauses
66 static readonly string clause1
= "({0} = 1 AND {1} IS NULL)";
67 static readonly string clause2
= "({0} = {1})";
70 readonly string _catalogSeparator
= ".";
71 readonly string _schemaSeparator
= ".";
72 readonly CatalogLocation _catalogLocation
= CatalogLocation
.Start
;
79 public SqlCommandBuilder ()
87 public SqlCommandBuilder (SqlDataAdapter adapter
)
90 DataAdapter
= adapter
;
93 #endregion // Constructors
98 [DataSysDescription ("The DataAdapter for which to automatically generate SqlCommands")]
100 [DefaultValue (null)]
101 public new SqlDataAdapter DataAdapter
{
106 return (SqlDataAdapter
)base.DataAdapter
;
111 adapter
.RowUpdating
-= new SqlRowUpdatingEventHandler (RowUpdatingHandler
);
115 adapter
.RowUpdating
+= new SqlRowUpdatingEventHandler (RowUpdatingHandler
);
117 base.DataAdapter
= value;
123 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
125 [DataSysDescription ("The character used in a text command as the opening quote for quoting identifiers that contain special characters.")]
127 [EditorBrowsable (EditorBrowsableState
.Never
)]
136 if (quotePrefix
== null)
140 return base.QuotePrefix
;
145 if (dbSchemaTable
!= null)
146 throw new InvalidOperationException (
147 "The QuotePrefix and QuoteSuffix " +
148 "properties cannot be changed once " +
149 "an Insert, Update, or Delete " +
150 "command has been generated.");
153 if (value != "[" && value != "\"")
154 throw new ArgumentException ("Only '[' " +
155 "and '\"' are allowed as value " +
156 "for the 'QuoteSuffix' property.");
157 base.QuotePrefix
= value;
163 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
165 [DataSysDescription ("The character used in a text command as the closing quote for quoting identifiers that contain special characters. ")]
167 [EditorBrowsable (EditorBrowsableState
.Never
)]
176 if (quoteSuffix
== null)
180 return base.QuoteSuffix
;
185 if (dbSchemaTable
!= null)
186 throw new InvalidOperationException (
187 "The QuotePrefix and QuoteSuffix " +
188 "properties cannot be changed once " +
189 "an Insert, Update, or Delete " +
190 "command has been generated.");
193 if (value != "]" && value != "\"")
194 throw new ArgumentException ("Only ']' " +
195 "and '\"' are allowed as value " +
196 "for the 'QuoteSuffix' property.");
197 base.QuoteSuffix
= value;
203 [EditorBrowsable (EditorBrowsableState
.Never
)]
205 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
209 public override string CatalogSeparator
{
210 get { return _catalogSeparator; }
212 if (value != _catalogSeparator
)
213 throw new ArgumentException ("Only " +
214 "'.' is allowed as value " +
215 "for the 'CatalogSeparator' " +
220 [EditorBrowsable (EditorBrowsableState
.Never
)]
222 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
226 public override string SchemaSeparator
{
227 get { return _schemaSeparator; }
229 if (value != _schemaSeparator
)
230 throw new ArgumentException ("Only " +
231 "'.' is allowed as value " +
232 "for the 'SchemaSeparator' " +
237 [EditorBrowsable (EditorBrowsableState
.Never
)]
239 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
241 [DefaultValue (CatalogLocation
.Start
)]
243 public override CatalogLocation CatalogLocation
{
244 get { return _catalogLocation; }
246 if (value != CatalogLocation
.Start
)
247 throw new ArgumentException ("Only " +
248 "'Start' is allowed as value " +
249 "for the 'CatalogLocation' " +
257 private SqlCommand SourceCommand
{
260 return adapter
.SelectCommand
;
266 #endregion // Properties
271 private void BuildCache (bool closeConnection
)
273 SqlCommand sourceCommand
= SourceCommand
;
274 if (sourceCommand
== null)
275 throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
276 SqlConnection connection
= sourceCommand
.Connection
;
277 if (connection
== null)
278 throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
280 if (dbSchemaTable
== null) {
281 if (connection
.State
== ConnectionState
.Open
)
282 closeConnection
= false;
286 SqlDataReader reader
= sourceCommand
.ExecuteReader (CommandBehavior
.SchemaOnly
| CommandBehavior
.KeyInfo
);
287 dbSchemaTable
= reader
.GetSchemaTable ();
291 BuildInformation (dbSchemaTable
);
295 private void BuildInformation (DataTable schemaTable
)
297 tableName
= String
.Empty
;
298 foreach (DataRow schemaRow
in schemaTable
.Rows
) {
299 if (schemaRow
.IsNull ("BaseTableName") ||
300 (string) schemaRow
["BaseTableName"] == String
.Empty
)
303 if (tableName
== String
.Empty
)
304 tableName
= (string) schemaRow
["BaseTableName"];
305 else if (tableName
!= (string) schemaRow
["BaseTableName"])
306 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
308 if (tableName
== String
.Empty
)
309 throw new InvalidOperationException ("Dynamic SQL generation is not supported with no base table.");
310 dbSchemaTable
= schemaTable
;
313 private SqlCommand
CreateDeleteCommand (bool useColumnsForParameterNames
)
315 // If no table was found, then we can't do an delete
316 if (QuotedTableName
== String
.Empty
)
319 CreateNewCommand (ref deleteCommand
);
321 string command
= String
.Format ("DELETE FROM {0}", QuotedTableName
);
322 StringBuilder whereClause
= new StringBuilder ();
323 bool keyFound
= false;
326 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
327 if ((bool)schemaRow
["IsExpression"] == true)
329 if (!IncludedInWhereClause (schemaRow
))
332 if (whereClause
.Length
> 0)
333 whereClause
.Append (" AND ");
335 bool isKey
= (bool) schemaRow
["IsKey"];
336 SqlParameter parameter
= null;
341 bool allowNull
= (bool) schemaRow
["AllowDBNull"];
343 string sourceColumnName
= (string) schemaRow
["BaseColumnName"];
344 if (useColumnsForParameterNames
) {
345 parameter
= deleteCommand
.Parameters
.Add (
346 GetNullCheckParameterName (sourceColumnName
),
349 parameter
= deleteCommand
.Parameters
.Add (
350 GetParameterName (parmIndex
++),
353 parameter
.IsNullable
= allowNull
;
354 parameter
.SourceVersion
= DataRowVersion
.Current
;
357 whereClause
.Append ("(");
358 whereClause
.Append (String
.Format (clause1
, parameter
.ParameterName
,
359 GetQuotedString (sourceColumnName
)));
360 whereClause
.Append (" OR ");
363 if (useColumnsForParameterNames
)
364 parameter
= CreateParameter (schemaRow
, true);
366 parameter
= CreateParameter (parmIndex
++, schemaRow
);
367 deleteCommand
.Parameters
.Add (parameter
);
368 ApplyParameterInfo (parameter
, schemaRow
, StatementType
.Delete
, true);
369 parameter
.IsNullable
= allowNull
;
370 parameter
.SourceVersion
= DataRowVersion
.Original
;
372 whereClause
.Append (String
.Format (clause2
, GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
375 whereClause
.Append (")");
378 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
380 // We're all done, so bring it on home
381 string sql
= String
.Format ("{0} WHERE ( {1} )", command
, whereClause
.ToString ());
382 deleteCommand
.CommandText
= sql
;
383 return deleteCommand
;
386 private SqlCommand
CreateInsertCommand (bool useColumnsForParameterNames
)
388 if (QuotedTableName
== String
.Empty
)
391 CreateNewCommand (ref insertCommand
);
393 string command
= String
.Format ("INSERT INTO {0}", QuotedTableName
);
395 StringBuilder columns
= new StringBuilder ();
396 StringBuilder values
= new StringBuilder ();
399 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
400 if (!IncludedInInsert (schemaRow
))
404 columns
.Append (" , ");
405 values
.Append (" , ");
408 SqlParameter parameter
= null;
409 if (useColumnsForParameterNames
) {
410 parameter
= CreateParameter (schemaRow
, false);
412 parameter
= CreateParameter (parmIndex
, schemaRow
);
415 insertCommand
.Parameters
.Add (parameter
);
416 ApplyParameterInfo (parameter
, schemaRow
, StatementType
.Insert
, false);
417 parameter
.SourceVersion
= DataRowVersion
.Current
;
418 parameter
.IsNullable
= (bool) schemaRow
["AllowDBNull"];
420 columns
.Append (GetQuotedString (parameter
.SourceColumn
));
421 values
.Append (parameter
.ParameterName
);
426 sql
= String
.Format ("{0}( {1} ) VALUES ( {2} )", command
, columns
.ToString (), values
.ToString ());
427 insertCommand
.CommandText
= sql
;
428 return insertCommand
;
431 private void CreateNewCommand (ref SqlCommand command
)
433 SqlCommand sourceCommand
= SourceCommand
;
434 if (command
== null) {
435 command
= sourceCommand
.Connection
.CreateCommand ();
436 command
.CommandTimeout
= sourceCommand
.CommandTimeout
;
437 command
.Transaction
= sourceCommand
.Transaction
;
439 command
.CommandType
= CommandType
.Text
;
440 command
.UpdatedRowSource
= UpdateRowSource
.None
;
441 command
.Parameters
.Clear ();
444 private SqlCommand
CreateUpdateCommand (bool useColumnsForParameterNames
)
446 // If no table was found, then we can't do an update
447 if (QuotedTableName
== String
.Empty
)
450 CreateNewCommand (ref updateCommand
);
452 string command
= String
.Format ("UPDATE {0} SET ", QuotedTableName
);
453 StringBuilder columns
= new StringBuilder ();
454 StringBuilder whereClause
= new StringBuilder ();
456 bool keyFound
= false;
458 // First, create the X=Y list for UPDATE
459 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
460 if (!IncludedInUpdate (schemaRow
))
462 if (columns
.Length
> 0)
463 columns
.Append (" , ");
465 SqlParameter parameter
= null;
466 if (useColumnsForParameterNames
) {
467 parameter
= CreateParameter (schemaRow
, false);
469 parameter
= CreateParameter (parmIndex
++, schemaRow
);
471 updateCommand
.Parameters
.Add (parameter
);
472 ApplyParameterInfo (parameter
, schemaRow
, StatementType
.Update
, false);
473 parameter
.IsNullable
= (bool) schemaRow
["AllowDBNull"];
474 parameter
.SourceVersion
= DataRowVersion
.Current
;
476 columns
.Append (String
.Format ("{0} = {1}", GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
479 // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
480 // into the loop above. "Premature optimization is the root of all evil." -- Knuth
481 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
482 if ((bool)schemaRow
["IsExpression"] == true)
485 if (!IncludedInWhereClause (schemaRow
))
488 if (whereClause
.Length
> 0)
489 whereClause
.Append (" AND ");
491 bool isKey
= (bool) schemaRow
["IsKey"];
492 SqlParameter parameter
= null;
497 bool allowNull
= (bool) schemaRow
["AllowDBNull"];
499 string sourceColumnName
= (string) schemaRow
["BaseColumnName"];
500 if (useColumnsForParameterNames
) {
501 parameter
= updateCommand
.Parameters
.Add (
502 GetNullCheckParameterName (sourceColumnName
),
505 parameter
= updateCommand
.Parameters
.Add (
506 GetParameterName (parmIndex
++),
509 parameter
.IsNullable
= allowNull
;
510 parameter
.SourceVersion
= DataRowVersion
.Current
;
513 whereClause
.Append ("(");
514 whereClause
.Append (String
.Format (clause1
, parameter
.ParameterName
,
515 GetQuotedString (sourceColumnName
)));
516 whereClause
.Append (" OR ");
520 if (useColumnsForParameterNames
) {
521 parameter
= CreateParameter (schemaRow
, true);
523 parameter
= CreateParameter (parmIndex
++, schemaRow
);
525 updateCommand
.Parameters
.Add (parameter
);
526 ApplyParameterInfo (parameter
, schemaRow
, StatementType
.Update
, true);
527 parameter
.IsNullable
= allowNull
;
528 parameter
.SourceVersion
= DataRowVersion
.Original
;
530 whereClause
.Append (String
.Format (clause2
, GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
533 whereClause
.Append (")");
536 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
538 // We're all done, so bring it on home
539 string sql
= String
.Format ("{0}{1} WHERE ( {2} )", command
, columns
.ToString (), whereClause
.ToString ());
540 updateCommand
.CommandText
= sql
;
541 return updateCommand
;
544 private SqlParameter
CreateParameter (DataRow schemaRow
, bool whereClause
)
546 string sourceColumn
= (string) schemaRow
["BaseColumnName"];
549 name
= GetParameterName ("Original_" + sourceColumn
);
551 name
= GetParameterName (sourceColumn
);
553 SqlParameter param
= new SqlParameter ();
554 param
.ParameterName
= name
;
555 param
.SourceColumn
= sourceColumn
;
559 private SqlParameter
CreateParameter (int paramIndex
, DataRow schemaRow
)
561 string sourceColumn
= (string) schemaRow
["BaseColumnName"];
562 string name
= GetParameterName (paramIndex
);
564 SqlParameter param
= new SqlParameter ();
565 param
.ParameterName
= name
;
566 param
.SourceColumn
= sourceColumn
;
571 public static void DeriveParameters (SqlCommand command
)
573 command
.DeriveParameters ();
577 protected override void Dispose (bool disposing
)
581 if (insertCommand
!= null)
582 insertCommand
.Dispose ();
583 if (deleteCommand
!= null)
584 deleteCommand
.Dispose ();
585 if (updateCommand
!= null)
586 updateCommand
.Dispose ();
587 if (dbSchemaTable
!= null)
588 dbSchemaTable
.Dispose ();
599 SqlCommand
GetDeleteCommand ()
602 return (SqlCommand
) base.GetDeleteCommand (false);
605 if (deleteCommand
== null)
606 return CreateDeleteCommand (false);
607 return deleteCommand
;
615 SqlCommand
GetInsertCommand ()
618 return (SqlCommand
) base.GetInsertCommand (false);
621 if (insertCommand
== null)
622 return CreateInsertCommand (false);
623 return insertCommand
;
631 SqlCommand
GetUpdateCommand ()
634 return (SqlCommand
) base.GetUpdateCommand (false);
637 if (updateCommand
== null)
638 return CreateUpdateCommand (false);
639 return updateCommand
;
644 public new SqlCommand
GetUpdateCommand (bool useColumnsForParameterNames
)
646 return (SqlCommand
) base.GetUpdateCommand (useColumnsForParameterNames
);
649 public new SqlCommand
GetDeleteCommand (bool useColumnsForParameterNames
)
651 return (SqlCommand
) base.GetDeleteCommand (useColumnsForParameterNames
);
654 public new SqlCommand
GetInsertCommand (bool useColumnsForParameterNames
)
656 return (SqlCommand
) base.GetInsertCommand (useColumnsForParameterNames
);
659 public override string QuoteIdentifier (string unquotedIdentifier
)
661 if (unquotedIdentifier
== null)
662 throw new ArgumentNullException ("unquotedIdentifier");
664 string prefix
= QuotePrefix
;
665 string suffix
= QuoteSuffix
;
667 if ((prefix
== "[" && suffix
!= "]") || (prefix
== "\"" && suffix
!= "\""))
668 throw new ArgumentException ("The QuotePrefix " +
669 "and QuoteSuffix properties do not match.");
671 string escaped
= unquotedIdentifier
.Replace (suffix
,
673 return string.Concat (prefix
, escaped
, suffix
);
676 public override string UnquoteIdentifier (string quotedIdentifier
)
678 return base.UnquoteIdentifier (quotedIdentifier
);
682 private bool IncludedInInsert (DataRow schemaRow
)
684 // If the parameter has one of these properties, then we don't include it in the insert:
685 // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
687 if (!schemaRow
.IsNull ("IsAutoIncrement") && (bool) schemaRow
["IsAutoIncrement"])
689 if (!schemaRow
.IsNull ("IsHidden") && (bool) schemaRow
["IsHidden"])
691 if (!schemaRow
.IsNull ("IsExpression") && (bool) schemaRow
["IsExpression"])
693 if (!schemaRow
.IsNull ("IsRowVersion") && (bool) schemaRow
["IsRowVersion"])
695 if (!schemaRow
.IsNull ("IsReadOnly") && (bool) schemaRow
["IsReadOnly"])
700 private bool IncludedInUpdate (DataRow schemaRow
)
702 // If the parameter has one of these properties, then we don't include it in the insert:
703 // AutoIncrement, Hidden, RowVersion
705 if (!schemaRow
.IsNull ("IsAutoIncrement") && (bool) schemaRow
["IsAutoIncrement"])
707 if (!schemaRow
.IsNull ("IsHidden") && (bool) schemaRow
["IsHidden"])
709 if (!schemaRow
.IsNull ("IsRowVersion") && (bool) schemaRow
["IsRowVersion"])
711 if (!schemaRow
.IsNull ("IsExpression") && (bool) schemaRow
["IsExpression"])
713 if (!schemaRow
.IsNull ("IsReadOnly") && (bool) schemaRow
["IsReadOnly"])
719 private bool IncludedInWhereClause (DataRow schemaRow
)
721 if ((bool) schemaRow
["IsLong"])
727 private string GetQuotedString (string value)
729 if (value == null || value.Length
== 0)
732 string prefix
= QuotePrefix
;
733 string suffix
= QuoteSuffix
;
735 if (prefix
.Length
== 0 && suffix
.Length
== 0)
737 return String
.Format ("{0}{1}{2}", prefix
, value, suffix
);
740 string GetNullCheckParameterName (string parameterName
)
742 return GetParameterName ("IsNull_" + parameterName
);
746 private string QuotedTableName
{
747 get { return GetQuotedString (tableName); }
750 public void RefreshSchema ()
752 // FIXME: "Figure out what else needs to be cleaned up when we refresh."
753 tableName
= String
.Empty
;
754 dbSchemaTable
= null;
755 deleteCommand
= null;
756 insertCommand
= null;
757 updateCommand
= null;
762 protected override void ApplyParameterInfo (DbParameter parameter
,
764 StatementType statementType
,
767 SqlParameter sqlParam
= (SqlParameter
) parameter
;
769 void ApplyParameterInfo (SqlParameter sqlParam
,
771 StatementType statementType
,
775 sqlParam
.SqlDbType
= (SqlDbType
) datarow
["ProviderType"];
777 object precision
= datarow
["NumericPrecision"];
778 if (precision
!= DBNull
.Value
) {
779 short val
= (short) precision
;
780 if (val
< byte.MaxValue
&& val
>= byte.MinValue
)
781 sqlParam
.Precision
= (byte) val
;
784 object scale
= datarow
["NumericScale"];
785 if (scale
!= DBNull
.Value
) {
786 short val
= ((short) scale
);
787 if (val
< byte.MaxValue
&& val
>= byte.MinValue
)
788 sqlParam
.Scale
= (byte) val
;
795 string GetParameterName (int parameterOrdinal
)
797 return String
.Format ("@p{0}", parameterOrdinal
);
803 string GetParameterName (string parameterName
)
805 return String
.Format ("@{0}", parameterName
);
809 protected override string GetParameterPlaceholder (int parameterOrdinal
)
811 return GetParameterName (parameterOrdinal
);
815 #endregion // Methods
817 #region Event Handlers
819 void RowUpdatingHandler (object sender
, SqlRowUpdatingEventArgs args
)
822 base.RowUpdatingHandler (args
);
824 if (args
.Command
!= null)
827 switch (args
.StatementType
) {
828 case StatementType
.Insert
:
829 args
.Command
= GetInsertCommand ();
831 case StatementType
.Update
:
832 args
.Command
= GetUpdateCommand ();
834 case StatementType
.Delete
:
835 args
.Command
= GetDeleteCommand ();
838 } catch (Exception e
) {
840 args
.Status
= UpdateStatus
.ErrorsOccurred
;
846 protected override void SetRowUpdatingHandler (DbDataAdapter adapter
)
848 SqlDataAdapter sda
= adapter
as SqlDataAdapter
;
850 throw new InvalidOperationException ("Adapter needs to be a SqlDataAdapter");
853 if (sda
!= base.DataAdapter
)
854 sda
.RowUpdating
+= new SqlRowUpdatingEventHandler (RowUpdatingHandler
);
856 sda
.RowUpdating
-= new SqlRowUpdatingEventHandler (RowUpdatingHandler
);;
859 protected override DataTable
GetSchemaTable (DbCommand srcCommand
)
861 using (SqlDataReader rdr
= (SqlDataReader
) srcCommand
.ExecuteReader (CommandBehavior
.KeyInfo
| CommandBehavior
.SchemaOnly
))
862 return rdr
.GetSchemaTable ();
865 protected override DbCommand
InitializeCommand (DbCommand command
)
867 if (command
== null) {
868 command
= new SqlCommand ();
870 command
.CommandTimeout
= 30;
871 command
.Transaction
= null;
872 command
.CommandType
= CommandType
.Text
;
873 command
.UpdatedRowSource
= UpdateRowSource
.None
;
879 #endregion // Event Handlers