/// <summary> /// Executes the command, using the DataObjectFactory<T> class and serializes the result /// to the stream provided. /// </summary> /// <param name="stream">The stream the result will be serialized to.</param> /// <param name="command">The command to execute.</param> public static void ProcessCommand(Stream stream, FactoryTransferObject <T> command) { if (command == null) { throw new ArgumentNullException("command"); } if (stream == null) { throw new ArgumentNullException("stream"); } Type factoryType = typeof(DataObjectFactory <T>); Type returnType = GetReturnType(typeof(T), command.Command); MethodInfo method = factoryType.GetMethod(command.Command, BindingFlags.Public | BindingFlags.Static, null, command.GetAllParameterTypes(), null); object value; switch (command.Command) { case "CreateObjects": value = method.Invoke(null, command.GetAllParameters()); break; case "CreateObject": value = method.Invoke(null, command.GetAllParameters()); break; case "WriteObject": method.Invoke(null, command.GetAllParameters()); value = new DtoObjectInsertedAck(DataObjectInfo <T> .GetPrimaryKeys(command.DataObject)); break; case "DeleteObject": method.Invoke(null, command.GetAllParameters()); value = new DtoServerAck(); break; default: throw new ArgumentOutOfRangeException("command"); } XmlSerializer serializer = new XmlSerializer(returnType); serializer.Serialize(stream, value); }
/// <summary> /// <para> /// Saves an object to the database. If the IsNew propery of the object is set to false, an existing /// row will be updated. If the IsNew property is set to true, a new row will be created. /// </para> /// <para> /// If the object contains a single PrimaryKey field, with AutoNumber set to true, a new primary key /// will be generated. Depending on the current settings, this may be a new, random key or an incremental /// key. /// </summary> /// <remarks> /// <para> /// When an existing object is saved to the database, only modified values are being sent to the database. /// This is to avoid concurrency issues as much as possible. /// </para> /// <para> /// This method is not thread safe. /// </para> /// </remarks> /// <param name="value">The object to be saved.</param> /// <param name="overrideAutoNumber"> /// Sometimes a new object will be added to the database, but the primary key will be set beforehand. /// To prevent the code (or database) from auto-assigning a new key, this parameter should be set. /// </param> public static void WriteObject(T value, bool overrideAutoNumber) { if (value == null) { throw new ArgumentNullException("value"); } if (value.IsDeleted) { throw new InvalidOperationException(Resources.CannotSaveDeletedObject); } // If the value is not new, and none of the values has changed, there is nothing we // should do. if (!value.IsNew && !value.IsDirty) { return; } // Should we update the primary key? Yes, if the value is new and the data object has a primary key // which is auto-numbered (IDENTITY in SQL). bool updatePrimaryKey = value.IsNew && DataObjectInfo <T> .HasPrimaryKeyWithAutoNumber(); // Make sure the overrideAutoNumber parameter is set only if the code would attempt // to update the primary key. if (overrideAutoNumber && !updatePrimaryKey) { throw new InvalidOperationException(Resources.CannotOverridePrimaryKey); } // If overrideAutoNumber is set, we don't update the primary key -- obviously! if (overrideAutoNumber) { updatePrimaryKey = false; } // Should we generate a new, random key? bool generateRandomKey = updatePrimaryKey && PrefB.RandomKeys; if (generateRandomKey) { int key = MiscData.GetKey(DataObjectInfo <T> .GetTableName(), DataObjectInfo <T> .GetPrimaryKeyFieldName()); DataObjectInfo <T> .SetPrimaryKey(value, key); // The primary key as already been updated. No need to retrieve it from the database. updatePrimaryKey = false; } if (!RemotingClient.OpenDentBusinessIsLocal) { DtoObjectInsertedAck ack = (DtoObjectInsertedAck)FactoryClient <T> .SendRequest("WriteObject", value, new object[] { overrideAutoNumber }); DataObjectInfo <T> .SetPrimaryKeys(value, ack.PrimaryKeys); value.OnSaved(EventArgs.Empty); return; } Collection <DataFieldInfo> dataFields = DataObjectInfo <T> .GetDataFields(DataFieldMask.Data); Collection <DataFieldInfo> primaryKeyFields = DataObjectInfo <T> .GetDataFields(DataFieldMask.PrimaryKey); Collection <DataFieldInfo> allFields = DataObjectInfo <T> .GetDataFields(); if (allFields.Count == 0) { throw new InvalidOperationException(Resources.NoFields); } // In queries, the first field always is special (because of the use of commas). This helper variable // helps us generate correct queries. bool isFirstField = true; using (IDbConnection connection = DataSettings.GetConnection()) using (IDbCommand command = connection.CreateCommand()) { if (useParameters) { // For each field, create a parameter foreach (DataFieldInfo dataField in allFields) { IDbDataParameter parameter = command.CreateParameter(); parameter.ParameterName = ParameterPrefix + dataField.DatabaseName; // Get the value of the field object fieldValue = dataField.Field.GetValue(value); // If the value is of type string and the value is null, we replace it // by an empty string. if (fieldValue == null && dataField.Field.FieldType == typeof(string)) { fieldValue = string.Empty; } parameter.Value = fieldValue; command.Parameters.Add(parameter); } } // Create the SQL query. If it is a new field, create an "INSERT" statement, else // an "UPDATE" statement. StringBuilder commandTextBuilder = new StringBuilder(); if (value.IsNew) { // Create a new row, using an INSERT statement. // The values to set always include the data values (not part of the PK) commandTextBuilder.Append(string.Format("INSERT INTO {0} (", DataObjectInfo <T> .GetTableName())); foreach (DataFieldInfo field in dataFields) { if (isFirstField) { isFirstField = false; } else { commandTextBuilder.Append(','); } commandTextBuilder.Append(field.DatabaseName); } // If the PK is auto-generated, it it shouldn't be included. If the PK is generated by the code // (be it that is some external variable or that the PK is a random number generated previously); // it should be included if (!updatePrimaryKey) { foreach (DataFieldInfo primaryKeyField in primaryKeyFields) { if (isFirstField) { isFirstField = false; } else { commandTextBuilder.Append(','); } commandTextBuilder.Append(primaryKeyField.DatabaseName); } } commandTextBuilder.Append(") VALUES ("); isFirstField = true; foreach (DataFieldInfo field in dataFields) { if (isFirstField) { isFirstField = false; } else { commandTextBuilder.Append(','); } if (useParameters) { commandTextBuilder.Append(ParameterPrefix + field.DatabaseName); } else { commandTextBuilder.AppendFormat("{0}", POut.PObject(field.Field.GetValue(value))); } } if (!updatePrimaryKey) { foreach (DataFieldInfo primaryKeyField in primaryKeyFields) { if (isFirstField) { isFirstField = false; } else { commandTextBuilder.Append(','); } if (useParameters) { commandTextBuilder.Append(ParameterPrefix + primaryKeyField.DatabaseName); } else { commandTextBuilder.AppendFormat("{0}", POut.PObject(primaryKeyField.Field.GetValue(value))); } } } commandTextBuilder.Append(')'); } else { // Update an existing row, using the UPDATE statement. // The WHERE clause contains all PK fields, other data fields go directly into // the SET clause. commandTextBuilder.Append(string.Format("UPDATE {0} SET ", DataObjectInfo <T> .GetTableName())); foreach (DataFieldInfo field in dataFields) { // If the data field has not changed, we don't need to update it -- obviously if (!DataObjectInfo <T> .HasChanged(field, value)) { continue; } if (isFirstField) { isFirstField = false; } else { commandTextBuilder.Append(','); } if (useParameters) { commandTextBuilder.Append(string.Format("{0} = {1}{0}", field.DatabaseName, ParameterPrefix)); } else { commandTextBuilder.Append(string.Format("{0} = {1}", field.DatabaseName, POut.PObject(field.Field.GetValue(value)))); } } commandTextBuilder.Append(" WHERE "); isFirstField = true; foreach (DataFieldInfo field in primaryKeyFields) { if (isFirstField) { isFirstField = false; } else { commandTextBuilder.Append(','); } if (useParameters) { commandTextBuilder.Append(string.Format("{0} = {1}{0}", field.DatabaseName, ParameterPrefix)); } else { commandTextBuilder.Append(string.Format("{0} = {1}", field.DatabaseName, POut.PObject(field.Field.GetValue(value)))); } } } command.CommandText = commandTextBuilder.ToString(); connection.Open(); // This executes the UPDATE/INSERT command. command.ExecuteNonQuery(); // Update the PK if required if (updatePrimaryKey) { // This is currently not implemented for Oracle. Open Dental uses a special mechanism, OracleInsertId, // but using a SEQUENCE might be better. See http://coldfusion.sys-con.com/read/43794.htm . // // For MS SQL (not implemented, either), this would be SCOPE_IDENTITY() if (DataSettings.DbType != DatabaseType.MySql) { throw new NotImplementedException(); } command.CommandText = "SELECT LAST_INSERT_ID()"; // The type returned by command.ExecuteScalar() is System.Int64. // We need to cast it to System.Int32, that's what we use here. int key = Convert.ToInt32(command.ExecuteScalar()); DataObjectInfo <T> .SetPrimaryKey(value, key); } connection.Close(); // The object has been saved value.OnSaved(EventArgs.Empty); } }