|
|
@@ -1,128 +1,430 @@
|
|
|
//
|
|
|
-// System.Data.SqlClient.SqlCommandBuilder.cs
|
|
|
+// System.Data.Common.CommandBuilder.cs
|
|
|
//
|
|
|
// Author:
|
|
|
-// Rodrigo Moya ([email protected])
|
|
|
-// Daniel Morgan ([email protected])
|
|
|
// Tim Coleman ([email protected])
|
|
|
//
|
|
|
-// (C) Ximian, Inc 2002
|
|
|
// Copyright (C) Tim Coleman, 2002
|
|
|
//
|
|
|
|
|
|
using System;
|
|
|
-using System.Data;
|
|
|
+using System.Collections;
|
|
|
using System.ComponentModel;
|
|
|
+using System.Data;
|
|
|
+using System.Data.Common;
|
|
|
+using System.Text;
|
|
|
|
|
|
namespace System.Data.SqlClient {
|
|
|
- /// <summary>
|
|
|
- /// Builder of one command
|
|
|
- /// that will be used in manipulating a table for
|
|
|
- /// a DataSet that is assoicated with a database.
|
|
|
- /// </summary>
|
|
|
- public sealed class SqlCommandBuilder : Component
|
|
|
+ public sealed class SqlCommandBuilder : Component
|
|
|
{
|
|
|
#region Fields
|
|
|
|
|
|
+ DataTable dbSchemaTable;
|
|
|
+ SqlDataAdapter adapter;
|
|
|
string quotePrefix;
|
|
|
string quoteSuffix;
|
|
|
- SqlDataAdapter adapter;
|
|
|
+ string[] columnNames;
|
|
|
+ string tableName;
|
|
|
+
|
|
|
+ SqlCommand deleteCommand;
|
|
|
+ SqlCommand insertCommand;
|
|
|
+ SqlCommand updateCommand;
|
|
|
+
|
|
|
+ // Used to construct WHERE clauses
|
|
|
+ static readonly string clause1 = "({0} IS NULL AND {1} IS NULL)";
|
|
|
+ static readonly string clause2 = "({0} = {1})";
|
|
|
|
|
|
#endregion // Fields
|
|
|
|
|
|
#region Constructors
|
|
|
|
|
|
public SqlCommandBuilder ()
|
|
|
- : this (null)
|
|
|
{
|
|
|
+ dbSchemaTable = null;
|
|
|
+ adapter = null;
|
|
|
+ quoteSuffix = String.Empty;
|
|
|
+ quotePrefix = String.Empty;
|
|
|
}
|
|
|
|
|
|
- public SqlCommandBuilder (SqlDataAdapter adapter)
|
|
|
+ public SqlCommandBuilder (SqlDataAdapter adapter)
|
|
|
+ : this ()
|
|
|
{
|
|
|
- this.adapter = adapter;
|
|
|
- this.quotePrefix = String.Empty;
|
|
|
- this.quoteSuffix = String.Empty;
|
|
|
+ DataAdapter = adapter;
|
|
|
}
|
|
|
|
|
|
#endregion // Constructors
|
|
|
|
|
|
#region Properties
|
|
|
|
|
|
- [DataSysDescription ("The DataAdapter for which to automatically generator SqlCommands.")]
|
|
|
- [DefaultValue (null)]
|
|
|
public SqlDataAdapter DataAdapter {
|
|
|
get { return adapter; }
|
|
|
set {
|
|
|
+ adapter = value;
|
|
|
if (adapter != null)
|
|
|
- adapter.RowUpdating -= new SqlRowUpdatingEventHandler (RowUpdatingHandler);
|
|
|
- adapter = value;
|
|
|
- adapter.RowUpdating += new SqlRowUpdatingEventHandler (RowUpdatingHandler);
|
|
|
+ adapter.RowUpdating += new SqlRowUpdatingEventHandler (RowUpdatingHandler);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- [Browsable (false)]
|
|
|
- [DataSysDescription ("The character used in a text command as the opening quote for quoting identifiers that contain special characters.")]
|
|
|
- [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
|
+ private string QuotedTableName {
|
|
|
+ get { return GetQuotedString (tableName); }
|
|
|
+ }
|
|
|
+
|
|
|
public string QuotePrefix {
|
|
|
get { return quotePrefix; }
|
|
|
- set { quotePrefix = value; }
|
|
|
+ set {
|
|
|
+ if (dbSchemaTable != null)
|
|
|
+ throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
|
|
|
+ quotePrefix = value;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- [Browsable (false)]
|
|
|
- [DataSysDescription ("The character used in a text command as the closing quote for quoting identifiers that contain special characters.")]
|
|
|
- [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
|
public string QuoteSuffix {
|
|
|
get { return quoteSuffix; }
|
|
|
- set { quoteSuffix = value; }
|
|
|
+ set {
|
|
|
+ if (dbSchemaTable != null)
|
|
|
+ throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
|
|
|
+ quoteSuffix = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private SqlCommand SourceCommand {
|
|
|
+ get {
|
|
|
+ if (adapter != null)
|
|
|
+ return adapter.SelectCommand;
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#endregion // Properties
|
|
|
|
|
|
#region Methods
|
|
|
|
|
|
- [MonoTODO]
|
|
|
- public static void DeriveParameters (SqlCommand command)
|
|
|
+ private void BuildCache (bool closeConnection)
|
|
|
+ {
|
|
|
+ SqlCommand sourceCommand = SourceCommand;
|
|
|
+ if (sourceCommand == null)
|
|
|
+ throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
|
|
|
+ SqlConnection connection = sourceCommand.Connection;
|
|
|
+ if (connection == null)
|
|
|
+ throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
|
|
|
+
|
|
|
+ if (dbSchemaTable == null) {
|
|
|
+ if (connection.State == ConnectionState.Open)
|
|
|
+ closeConnection = false;
|
|
|
+ else
|
|
|
+ connection.Open ();
|
|
|
+
|
|
|
+ SqlDataReader reader = sourceCommand.ExecuteReader (CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
|
|
|
+ dbSchemaTable = reader.GetSchemaTable ();
|
|
|
+ if (closeConnection)
|
|
|
+ connection.Close ();
|
|
|
+ BuildInformation (dbSchemaTable);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void BuildInformation (DataTable schemaTable)
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ tableName = String.Empty;
|
|
|
+ foreach (DataRow schemaRow in schemaTable.Rows) {
|
|
|
+ if (tableName == String.Empty)
|
|
|
+ tableName = (string) schemaRow ["BaseTableName"];
|
|
|
+ if (tableName != (string) schemaRow["BaseTableName"])
|
|
|
+ throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
|
|
|
+ }
|
|
|
+ dbSchemaTable = schemaTable;
|
|
|
}
|
|
|
|
|
|
- [MonoTODO]
|
|
|
- public SqlCommand GetDeleteCommand ()
|
|
|
+ private SqlCommand CreateDeleteCommand (DataRow row, DataTableMapping tableMapping)
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ // If no table was found, then we can't do an delete
|
|
|
+ if (QuotedTableName == String.Empty)
|
|
|
+ return null;
|
|
|
+
|
|
|
+ CreateNewCommand (ref deleteCommand);
|
|
|
+
|
|
|
+ string command = String.Format ("DELETE FROM {0} ", QuotedTableName);
|
|
|
+ StringBuilder columns = new StringBuilder ();
|
|
|
+ StringBuilder where = new StringBuilder ();
|
|
|
+
|
|
|
+ int parmIndex = 1;
|
|
|
+
|
|
|
+ foreach (DataRow schemaRow in dbSchemaTable.Rows) {
|
|
|
+ if (!IncludedInWhereClause (schemaRow))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (where.Length > 0)
|
|
|
+ where.Append (" AND ");
|
|
|
+
|
|
|
+ bool isKey = (bool) schemaRow ["IsKey"];
|
|
|
+ SqlParameter parameter = null;
|
|
|
+
|
|
|
+ if (!isKey) {
|
|
|
+ parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
|
|
|
+ where.Append ("(");
|
|
|
+ where.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
|
|
|
+ where.Append (" OR ");
|
|
|
+ }
|
|
|
+
|
|
|
+ parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
|
|
|
+ if (row != null)
|
|
|
+ parameter.Value = row [parameter.SourceColumn, DataRowVersion.Current];
|
|
|
+ where.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
|
|
|
+
|
|
|
+ if (!isKey)
|
|
|
+ where.Append (")");
|
|
|
+ }
|
|
|
+
|
|
|
+ // We're all done, so bring it on home
|
|
|
+ string sql = String.Format ("{0} WHERE ( {1} )", command, where.ToString ());
|
|
|
+ deleteCommand.CommandText = sql;
|
|
|
+ return deleteCommand;
|
|
|
}
|
|
|
|
|
|
- [MonoTODO]
|
|
|
- public SqlCommand GetInsertCommand ()
|
|
|
+ private SqlCommand CreateInsertCommand (DataRow row, DataTableMapping tableMapping)
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ if (QuotedTableName == String.Empty)
|
|
|
+ return null;
|
|
|
+
|
|
|
+ CreateNewCommand (ref insertCommand);
|
|
|
+
|
|
|
+ string command = String.Format ("INSERT INTO {0}", QuotedTableName);
|
|
|
+ string sql;
|
|
|
+ StringBuilder columns = new StringBuilder ();
|
|
|
+ StringBuilder values = new StringBuilder ();
|
|
|
+
|
|
|
+ int parmIndex = 1;
|
|
|
+ foreach (DataRow schemaRow in dbSchemaTable.Rows) {
|
|
|
+ if (!IncludedInInsert (schemaRow))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (parmIndex > 1) {
|
|
|
+ columns.Append (" , ");
|
|
|
+ values.Append (" , ");
|
|
|
+ }
|
|
|
+
|
|
|
+ SqlParameter parameter = insertCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
|
|
|
+ if (row != null)
|
|
|
+ parameter.Value = row [parameter.SourceColumn, DataRowVersion.Proposed];
|
|
|
+ columns.Append (GetQuotedString (parameter.SourceColumn));
|
|
|
+ values.Append (parameter.ParameterName);
|
|
|
+ }
|
|
|
+
|
|
|
+ sql = String.Format ("{0}( {1} ) VALUES ( {2} )", command, columns.ToString (), values.ToString ());
|
|
|
+ insertCommand.CommandText = sql;
|
|
|
+ return insertCommand;
|
|
|
}
|
|
|
|
|
|
- [MonoTODO]
|
|
|
- public SqlCommand GetUpdateCommand ()
|
|
|
+ private void CreateNewCommand (ref SqlCommand command)
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ SqlCommand sourceCommand = SourceCommand;
|
|
|
+ if (command == null) {
|
|
|
+ command = sourceCommand.Connection.CreateCommand ();
|
|
|
+ command.CommandTimeout = sourceCommand.CommandTimeout;
|
|
|
+ command.Transaction = sourceCommand.Transaction;
|
|
|
+ }
|
|
|
+ command.CommandType = CommandType.Text;
|
|
|
+ command.UpdatedRowSource = UpdateRowSource.None;
|
|
|
}
|
|
|
|
|
|
- [MonoTODO]
|
|
|
- public void RefreshSchema ()
|
|
|
+ private SqlCommand CreateUpdateCommand (DataRow row, DataTableMapping tableMapping)
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ // If no table was found, then we can't do an update
|
|
|
+ if (QuotedTableName == String.Empty)
|
|
|
+ return null;
|
|
|
+
|
|
|
+ CreateNewCommand (ref updateCommand);
|
|
|
+
|
|
|
+ string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
|
|
|
+ StringBuilder columns = new StringBuilder ();
|
|
|
+ StringBuilder where = new StringBuilder ();
|
|
|
+
|
|
|
+ int parmIndex = 1;
|
|
|
+
|
|
|
+ // First, create the X=Y list for UPDATE
|
|
|
+ foreach (DataRow schemaRow in dbSchemaTable.Rows) {
|
|
|
+ if (columns.Length > 0)
|
|
|
+ columns.Append (" , ");
|
|
|
+
|
|
|
+ SqlParameter parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
|
|
|
+ if (row != null)
|
|
|
+ parameter.Value = row [parameter.SourceColumn, DataRowVersion.Proposed];
|
|
|
+ columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
|
|
|
+ // into the loop above. "Premature optimization is the root of all evil." -- Knuth
|
|
|
+ foreach (DataRow schemaRow in dbSchemaTable.Rows) {
|
|
|
+ if (!IncludedInWhereClause (schemaRow))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (where.Length > 0)
|
|
|
+ where.Append (" AND ");
|
|
|
+
|
|
|
+ bool isKey = (bool) schemaRow ["IsKey"];
|
|
|
+ SqlParameter parameter = null;
|
|
|
+
|
|
|
+ if (!isKey) {
|
|
|
+ parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
|
|
|
+ where.Append ("(");
|
|
|
+ where.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
|
|
|
+ where.Append (" OR ");
|
|
|
+ }
|
|
|
+
|
|
|
+ parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
|
|
|
+ if (row != null)
|
|
|
+ parameter.Value = row [parameter.SourceColumn, DataRowVersion.Current];
|
|
|
+ where.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
|
|
|
+
|
|
|
+ if (!isKey)
|
|
|
+ where.Append (")");
|
|
|
+ }
|
|
|
+
|
|
|
+ // We're all done, so bring it on home
|
|
|
+ string sql = String.Format ("{0}{1} WHERE ( {2} )", command, columns.ToString (), where.ToString ());
|
|
|
+ updateCommand.CommandText = sql;
|
|
|
+ return updateCommand;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SqlParameter CreateParameter (int parmIndex, DataRow schemaRow)
|
|
|
+ {
|
|
|
+ string name = String.Format ("@p{0}", parmIndex);
|
|
|
+ string sourceColumn = (string) schemaRow ["BaseColumnName"];
|
|
|
+ SqlDbType sqlDbType = (SqlDbType) schemaRow ["ProviderType"];
|
|
|
+ int size = (int) schemaRow ["ColumnSize"];
|
|
|
+
|
|
|
+ return new SqlParameter (name, sqlDbType, size, sourceColumn);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void DeriveParameters (SqlCommand command)
|
|
|
+ {
|
|
|
+ command.DeriveParameters ();
|
|
|
}
|
|
|
|
|
|
[MonoTODO]
|
|
|
- private void RowUpdatingHandler (object sender, SqlRowUpdatingEventArgs e)
|
|
|
+ protected override void Dispose (bool disposing)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ public SqlCommand GetDeleteCommand ()
|
|
|
+ {
|
|
|
+ BuildCache (true);
|
|
|
+ return CreateDeleteCommand (null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public SqlCommand GetInsertCommand ()
|
|
|
+ {
|
|
|
+ BuildCache (true);
|
|
|
+ return CreateInsertCommand (null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private string GetQuotedString (string value)
|
|
|
+ {
|
|
|
+ if (value == String.Empty || value == null)
|
|
|
+ return value;
|
|
|
+ if (quotePrefix == String.Empty && quoteSuffix == String.Empty)
|
|
|
+ return value;
|
|
|
+ return String.Format ("{0}{1}{2}", quotePrefix, value, quoteSuffix);
|
|
|
+ }
|
|
|
+
|
|
|
+ public SqlCommand GetUpdateCommand ()
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ BuildCache (true);
|
|
|
+ return CreateUpdateCommand (null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool IncludedInInsert (DataRow schemaRow)
|
|
|
+ {
|
|
|
+ // If the parameter has one of these properties, then we don't include it in the insert:
|
|
|
+ // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
|
|
|
+
|
|
|
+ if ((bool) schemaRow ["IsAutoIncrement"])
|
|
|
+ return false;
|
|
|
+ if ((bool) schemaRow ["IsHidden"])
|
|
|
+ return false;
|
|
|
+ if ((bool) schemaRow ["IsExpression"])
|
|
|
+ return false;
|
|
|
+ if ((bool) schemaRow ["IsRowVersion"])
|
|
|
+ return false;
|
|
|
+ if ((bool) schemaRow ["IsReadOnly"])
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool IncludedInUpdate (DataRow schemaRow)
|
|
|
+ {
|
|
|
+ // If the parameter has one of these properties, then we don't include it in the insert:
|
|
|
+ // AutoIncrement, Hidden, RowVersion
|
|
|
+
|
|
|
+ if ((bool) schemaRow ["IsAutoIncrement"])
|
|
|
+ return false;
|
|
|
+ if ((bool) schemaRow ["IsHidden"])
|
|
|
+ return false;
|
|
|
+ if ((bool) schemaRow ["IsRowVersion"])
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool IncludedInWhereClause (DataRow schemaRow)
|
|
|
+ {
|
|
|
+ if ((bool) schemaRow ["IsLong"])
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
[MonoTODO]
|
|
|
- protected override void Dispose (bool disposing)
|
|
|
+ public void RefreshSchema ()
|
|
|
{
|
|
|
- throw new NotImplementedException ();
|
|
|
+ tableName = String.Empty;
|
|
|
+ dbSchemaTable = null;
|
|
|
}
|
|
|
|
|
|
#endregion // Methods
|
|
|
+
|
|
|
+ #region Event Handlers
|
|
|
+
|
|
|
+ private void RowUpdatingHandler (object sender, SqlRowUpdatingEventArgs e)
|
|
|
+ {
|
|
|
+ if (e.Status != UpdateStatus.Continue)
|
|
|
+ return;
|
|
|
+
|
|
|
+ switch (e.StatementType) {
|
|
|
+ case StatementType.Delete:
|
|
|
+ deleteCommand = e.Command;
|
|
|
+ break;
|
|
|
+ case StatementType.Insert:
|
|
|
+ insertCommand = e.Command;
|
|
|
+ break;
|
|
|
+ case StatementType.Update:
|
|
|
+ updateCommand = e.Command;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ BuildCache (false);
|
|
|
+
|
|
|
+ switch (e.StatementType) {
|
|
|
+ case StatementType.Delete:
|
|
|
+ e.Command = CreateDeleteCommand (e.Row, e.TableMapping);
|
|
|
+ e.Status = UpdateStatus.Continue;
|
|
|
+ break;
|
|
|
+ case StatementType.Insert:
|
|
|
+ e.Command = CreateInsertCommand (e.Row, e.TableMapping);
|
|
|
+ e.Status = UpdateStatus.Continue;
|
|
|
+ break;
|
|
|
+ case StatementType.Update:
|
|
|
+ e.Command = CreateUpdateCommand (e.Row, e.TableMapping);
|
|
|
+ e.Status = UpdateStatus.Continue;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.Command != null && e.Row != null) {
|
|
|
+ e.Row.AcceptChanges ();
|
|
|
+ e.Status = UpdateStatus.SkipCurrentRow;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion // Event Handlers
|
|
|
}
|
|
|
}
|
|
|
|