Guardar Varios Registros En Transacción

Hola foro,

Tengo un código que carga un XLS y va guardando cada línea; el punto es que si durante el proceso una línea no se puede guardar todas las anteriores no se deben guardar.

Estoy haciendo un FOR dentro de una transacción pero no se hace el roolBack




..

$transaction = Yii::app()->db->beginTransaction();

try

{

  for($i=1, $i...    <<-- recorre el XLS línea por línea

  {

    ..

    if ($modelo1->validate())

      $modelo1->save();

    else

      break;      <<-- para no recorrer todo el XLS

    ..

    if ($model2->validate())

      $modelo2->save();

    else

      break;      <<-- para no recorrer todo el XLS

  }

  $transaction->commit();

  Yii::app()->getUser()->setFlash('success','The excel file was successfully imported.');

  $this->refresh();

}

catch(Exception $e)

{

  $transaction->rollBack();

  Yii::app()->user->setFlash('error', "{$e->getMessage()}");

  $this->refresh();

}

..



$modelo1 es otra tabla que se llena con algunos datos del XLS si es necesario.

Aun cuando suceda algún error los datos ya guardados no se deshacen.

Ya verifiqué que mis tablas sean InnoDB ($modelo1 y 2).

Que puede estar pasando ?

Pongo el código tal cual.




public function actionUpload()

{

  $uploaded = false;

  $dir = Yii::getPathOfAlias('webroot')."/protected/uploads/";

  $model=new Upload;

  

  Yii::import('ext.phpexcelreader.JPhpExcelReader');

  $separator=array('$',',','*');

  

  if(isset($_POST['Upload']))

  {

    $model->attributes=$_POST['Upload'];

    

    $file=CUploadedFile::getInstance($model,'file');

    if($model->validate())

    {

      $uploaded = $file->saveAs($dir.$file->getName());

      $data=new JPhpExcelReader($dir.$file->getName());


      $transaction = Yii::app()->db->beginTransaction();

      try

      {

        // Se recorre de nuevo el xls para ir sacando datos e ir insertando

        for ($i = 1; $i <= $data->sheets[0]['numRows']; $i++)

        {

          $wbs_code = substr($data->sheets[0]['cells'][$i][1], 0, 10);  // ---> columna A

          $wbs = Wbs::model()->find('code=:codigo AND active=1', array('codigo'=>$wbs_code));


          $descrip  = $data->sheets[0]['cells'][$i][2];  // ---> columna B

          $commcode = $data->sheets[0]['cells'][$i][3];  // ---> columna C


          $csi_code = substr($data->sheets[0]['cells'][$i][4], 0, 20);  // ---> columna D


          $cliecode = $data->sheets[0]['cells'][$i][5];  // ---> columna E

          $csidescr = $data->sheets[0]['cells'][$i][6];  // ---> columna F

          $csiunit  = $data->sheets[0]['cells'][$i][7];  // ---> columna G


          // Verifica cada valor de D en csi_codes.csi_code, 

          // si alguno no está en el catálogo se tiene que agregar.

          $exists = CsiCodes::model()->exists('csi_code=:codigo AND active=1', array('codigo'=>$csi_code));

          if (!$exists)

          {

            // Se tiene que agregar al catálogo pero se necesita el cost_groups.id para ponerlo en

            // csi_codes.id_cost_group, se obtiene usando el valor de la col. C

            $csicode = new CsiCodes;


            // Obtiene el ID del Commodity Group que está en la col. C

            $commgr = CostGroups::model()->find('csi_cost_group=:codigo AND active=1', 

                         array('codigo'=>$commcode));


            // Llena el nuevo CSI Codes

            $csicode->id_cost_group = $commgr->id;

            $csicode->csi_code      = $csi_code;

            $csicode->client_code   = $cliecode;

            $csicode->descr         = $csidescr;

            $csicode->unit          = $csiunit;

            if ($csicode->validate())

              $csicode->save();

            else

              break;


          }

          else

            $csicode = CsiCodes::model()->find('csi_code=:codigo AND active=1', 

                           array('codigo'=>$csi_code));


          $estimateQty = $data->sheets[0]['cells'][$i][8];  // ---> columna H

          $jobHours    = $data->sheets[0]['cells'][$i][9];  // ---> columna I

          $labor       = $data->sheets[0]['cells'][$i][10];  // ---> columna J

          $material    = $data->sheets[0]['cells'][$i][11];  // ---> columna K

          $equipment   = $data->sheets[0]['cells'][$i][12];  // ---> columna L

          $subcontract = $data->sheets[0]['cells'][$i][13];  // ---> columna M

          $labor2      = $data->sheets[0]['cells'][$i][14];  // ---> columna N

          $material2   = $data->sheets[0]['cells'][$i][15];  // ---> columna O

          $equipment2  = $data->sheets[0]['cells'][$i][16];  // ---> columna P

          $subcontract2= $data->sheets[0]['cells'][$i][17];  // ---> columna Q

          $start       = substr($data->sheets[0]['cells'][$i][18], 0, 10);  // ---> columna R

          $end         = substr($data->sheets[0]['cells'][$i][19], 0, 10);  // ---> columna S


          // Se inserta a bill_of_qtys

          $billOfQtys = new BillOfQtys();

          $billOfQtys->id_wbs = $wbs->id;

          $billOfQtys->id_csi_code = $csicode->id;

          $billOfQtys->estim_qty = $estimateQty;

          $billOfQtys->tot_jobhrs = $jobHours;

          $billOfQtys->tot_labor = $labor;

          $billOfQtys->tot_material = $material;

          $billOfQtys->tot_equipment = $equipment;

          $billOfQtys->tot_subcontract = $subcontract;

          $billOfQtys->ini_date = $start;

          $billOfQtys->end_date = $end;

          $billOfQtys->estim_labor = $labor2;

          $billOfQtys->estim_material = $material2;

          $billOfQtys->estim_equipment = $equipment2;

          $billOfQtys->estim_subcontract = $subcontract2;

          $billOfQtys->active = 1;

          $billOfQtys->cancel_reason = '';

          $billOfQtys->u_crea = 1;

          if ($billOfQtys->validate())

            $billOfQtys->save();

          else

            break;


        }  // fin FOR recorre de nuevo para ir guardando


        /*

         * Se guarda toda la transacción

        */

        $transaction->commit();


        Yii::app()->getUser()->setFlash('success','The excel file was successfully imported.');

        $this->refresh();

      }  

      catch(Exception $e) // si alguna consulta falla se genera una excepción

      {

        $transaction->rollBack();

        Yii::app()->user->setFlash('error', "{$e->getMessage()}");

        $this->refresh();

      }

    }  

    else

    {

      Yii::app()->getUser()->setFlash('error','Invalid file: type, size or undefined.');

      $this->refresh();

    }

  }

  else

    $this->render('uploadxls',array('model'=>$model,'uploaded' => $uploaded));

}



Gracias

Buenas tardes.

Si no entiendo mal tu código, cuando tú haces esto:




if ($modelo1->validate())

      $modelo1->save();

    else

      break;      <<-- para no recorrer todo el XLS



Si el modelo no se valida, se sale del bucle. Y la próxima línea que sigue al bucle es:


$transaction->commit();



Entonces, si tu guardas por ejemplo 5 registros y falla el 6, al salir del bucle no va al catch a hacer el roolback, sino que te va a ejecutar el commit de los 5 registros anteriores.

Podrías por ejemplo añadir una variable de control para indicar si se debe guardar o no:




$transaction = Yii::app()->db->beginTransaction();


$guardar=true;


try

{

  for($i=1, $i...    <<-- recorre el XLS línea por línea

  {

    ..

    if ($modelo1->validate())

      $modelo1->save();

    else

    {

      $guardar=false;

      break;      <<-- para no recorrer todo el XLS

    }


  }


if($guardar)

  $transaction->commit();

else

  $transaction->rollBack();


  Yii::app()->getUser()->setFlash('success','The excel file was successfully imported.');

  $this->refresh();

}

catch(Exception $e)

{

  $transaction->rollBack();

  Yii::app()->user->setFlash('error', "{$e->getMessage()}");

  $this->refresh();

}



Espero q te sirva.

Un saludo.

Gracias @lagogz

Si pensé en eso, pero yo había entendido que al momento de que se generara un error ya sea por save() o por validate() eso ‘disparaba’ el catch o generaba una excepción, si lo hago de esa manera entonces no me sirve el try / catch.

Estoy entendiendo mal ?

Gracias por tu tiempo.

Saludos.

Buenos días.

Cuando tú haces un validate(), le estás diciendo q si hay algún error, haga un break. Entonces no se va a lanzar la excepción (Catch). Por lo menos eso es lo que entiendo yo. Es decir, estás capturando manualmente la excepción, por lo tanto no irá al Catch.

Fíjate que si el validate() lanzase la excepción, nunca llegaría al break, y esa línea de código no tendría sentido.

De todas formas, prueba a hacer:




if ($modelo1->validate())

      $modelo1->save();

    /*else

      break;*/

A ver si te lanza la excepción.

Otra opción sería poner rollbacks antes de cada break.

A modo de aclaración:

Validate(): Validates one or several models and returns the results in JSON format. This is a helper method that simplifies the way of writing AJAX validation code.

Save(): …Validation will be performed before saving the record. If the validation fails, the record will not be saved. You can call getErrors() to retrieve the validation errors.

El método save() hace ya una validación implícita.

Un saludo.