public function save(question_engine_data_mapper $dm) {
        $dm->delete_steps(array_keys($this->stepsdeleted), $this->quba->get_owning_context());

        // Initially an array of array of question_attempt_step_objects.
        // Built as a nested array for efficiency, then flattened.
        $stepdata = array();

        foreach ($this->stepsmodified as $stepinfo) {
            list($step, $questionattemptid, $seq) = $stepinfo;
            $stepdata[] = $dm->update_question_attempt_step(
                    $step, $questionattemptid, $seq, $this->quba->get_owning_context());
        }

        foreach ($this->stepsadded as $stepinfo) {
            list($step, $questionattemptid, $seq) = $stepinfo;
            $stepdata[] = $dm->insert_question_attempt_step(
                    $step, $questionattemptid, $seq, $this->quba->get_owning_context());
        }

        foreach ($this->attemptsmodified as $qa) {
            $dm->update_question_attempt($qa);
        }

        foreach ($this->attemptsadded as $qa) {
            $stepdata[] = $dm->insert_question_attempt(
                    $qa, $this->quba->get_owning_context());
        }

        foreach ($this->metadataadded as $info) {
            $qa = reset($info);
            $stepdata[] = $dm->insert_question_attempt_metadata($qa, array_keys($info));
        }

        foreach ($this->metadatamodified as $info) {
            $qa = reset($info);
            $stepdata[] = $dm->update_question_attempt_metadata($qa, array_keys($info));
        }

        if ($this->modified) {
            $dm->update_questions_usage_by_activity($this->quba);
        }

        if ($stepdata) {
            $dm->insert_all_step_data(call_user_func_array('array_merge', $stepdata));
        }

        $this->stepsdeleted = array();
        $this->stepsmodified = array();
        $this->stepsadded = array();
        $this->attemptsdeleted = array();
        $this->attemptsadded = array();
        $this->attemptsmodified = array();
        $this->modified = false;
    }