/// <summary>
        /// Loads an objects of type <typeparamref name="T"/> from the database, using the
        /// given <paramref name="query"/>.
        /// </summary>
        /// <param name="query">The query to execute on the server.</param>
        /// <returns>An objects of type <typeparamref name="T"/>.</returns>
        public static T CreateObject(string query)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            if (!RemotingClient.OpenDentBusinessIsLocal)
            {
                return((T)FactoryClient <T> .SendRequest("CreateObject", default(T), new object[] { query }));
            }

            T value;

            using (IDbConnection connection = DataSettings.GetConnection())
                using (IDbCommand command = connection.CreateCommand()) {
                    command.CommandText = query;

                    connection.Open();
                    using (IDataReader reader = command.ExecuteReader()) {
                        reader.Read();
                        value = CreateObject(reader);
                    }
                    connection.Close();
                }

            return(value);
        }
        public static void DeleteObject(int id)
        {
            if (!RemotingClient.OpenDentBusinessIsLocal)
            {
                FactoryClient <T> .SendRequest("DeleteObject", default(T), new object[] { id });

                return;
            }

            string primaryKeyFieldName = DataObjectInfo <T> .GetPrimaryKeyFieldName();

            string tableName = DataObjectInfo <T> .GetTableName();

            string query;

            if (useParameters)
            {
                query = string.Format("DELETE FROM {0} WHERE {1} = {2}{1}", tableName, primaryKeyFieldName, ParameterPrefix);
            }
            else
            {
                query = string.Format("DELETE FROM {0} WHERE {1} = '{2}'", tableName, primaryKeyFieldName, POut.PInt(id));
            }

            using (IDbConnection connection = DataSettings.GetConnection())
                using (IDbCommand command = connection.CreateCommand()) {
                    if (useParameters)
                    {
                        IDataParameter parameter = command.CreateParameter();
                        parameter.ParameterName = ParameterPrefix + primaryKeyFieldName;
                        parameter.Value         = id;
                        command.Parameters.Add(parameter);
                    }

                    connection.Open();
                    command.ExecuteNonQuery();
                    connection.Close();
                }
        }
        public static void DeleteObject(T value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            if (value.IsDeleted)
            {
                throw new InvalidOperationException(Resources.ObjectAlreadyDeleted);
            }

            if (value.IsNew)
            {
                throw new InvalidOperationException(Resources.ObjectNotSaved);
            }

            if (!RemotingClient.OpenDentBusinessIsLocal)
            {
                FactoryClient <T> .SendRequest("DeleteObject", value, new object[] {});

                value.OnDeleted(EventArgs.Empty);
                return;
            }

            Collection <DataFieldInfo> identityFields = DataObjectInfo <T> .GetDataFields(DataFieldMask.PrimaryKey);

            using (IDbConnection connection = DataSettings.GetConnection())
                using (IDbCommand command = connection.CreateCommand()) {
                    StringBuilder commandTextBuilder = new StringBuilder();

                    commandTextBuilder.Append(string.Format("DELETE FROM {0} WHERE ", DataObjectInfo <T> .GetTableName()));

                    if (useParameters)
                    {
                        // For each field, create a parameter
                        foreach (DataFieldInfo dataField in identityFields)
                        {
                            IDbDataParameter parameter = command.CreateParameter();
                            parameter.ParameterName = ParameterPrefix + dataField.DatabaseName;
                            parameter.Value         = dataField.Field.GetValue(value);
                            command.Parameters.Add(parameter);
                        }
                    }

                    for (int i = 0; i < identityFields.Count; i++)
                    {
                        if (useParameters)
                        {
                            commandTextBuilder.Append(string.Format("{0} = {1}{0}", identityFields[i].DatabaseName, ParameterPrefix));
                        }
                        else
                        {
                            commandTextBuilder.Append(string.Format("{0} = '{1}'", identityFields[i].DatabaseName, POut.PObject(identityFields[i].Field.GetValue(value))));
                        }

                        if (i != identityFields.Count - 1)
                        {
                            commandTextBuilder.Append(" AND ");
                        }
                    }

                    command.CommandText = commandTextBuilder.ToString();

                    connection.Open();
                    command.ExecuteNonQuery();
                    connection.Close();
                }

            // The object has been deleted
            value.OnDeleted(EventArgs.Empty);
        }
        /// <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);
                }
        }