2010-02-16 4 views
6

J'ai une tableIgnorer les doublons lors de l'utilisation INSERT dans une base de données avec Symfony et Doctrine

CREATE TABLE `sob_tags_articles` (
    `tag_id` int(11) NOT NULL, 
    `article_id` int(11) NOT NULL, 
    `id` int(11) NOT NULL auto_increment, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM AUTO_INCREMENT=112 

Et triing pour sauver un objet Doctrine:

$sbTagsArticles = new SobTagsArticles(); 
$sbTagsArticles->article_id = $pubId; 
$sbTagsArticles->tag_id = $tagId; 
$sbTagsArticles->save(); 

Mais si l'enregistrement existe avec le même $ Le nouvel enregistrement de pubId et $ tagId sera inséré avec le nouveau PK.

Comment faire INSERT IGNORE dans une table avec symfony?

$sbTagsArticles->isNew(); 

retours 1.

Thnx.

+0

Pourquoi n'utilisez-vous pas '(tag_id, article_id)' comme clé primaire? –

+2

@ user274101: Vous n'avez pas besoin de changer le titre d'une question résolue sur StackOverflow. Vous devriez accepter la bonne réponse à la place. –

Répondre

13
try 
{ 
    $record->save(); 
} 
catch(Doctrine_Exception $e) 
{ 
    if($e->getErrorCode() !== $duplicateKeyCode) 
    { 
     /** 
     * if its not the error code for a duplicate key 
     * value then rethrow the exception 
     */ 
     throw $e; 
    } 

    /** 
    * you might want to fetch the real record here instead 
    * so yure working with the persisted copy 
    */ 
} 

Vous devez vous assurer que le même enregistrement n'existe pas du côté de l'application et pas du côté SQL. Si vous ne voulez jamais que le même article/tag combo existe, ajoutez un index unique à (article_id, tag_id). Cela devrait générer une erreur mysql qui générera à son tour une exception de doctrine que vous pouvez attraper. Il n'y a pas de drapeau ignorer pour les sauvegardes ... Vous pourriez en utiliser un qui opère à un niveau inférieur du DBAL (Doctrine_Query, Doctrine_Connection, etc.) mais pas directement à partir de la couche ORM.

Doctrine_Record::isNew() vaudra toujours vrai si vous avez instancié enregistrement asopposed pour le tirant de la db sinon il a comme il n'a aucun moyen de savoir que le dossier est/isnt nouveau.

Pourquoi utilisez-vous également le moteur de stockage MyISAM? Je suis assez sûr que cela entraînera plus de frais généraux en utilisant Doctrine car il doit ensuite émuler les contraintes du côté php. Normalement, votre schéma ressemblerait à quelque chose comme ceci:

CREATE TABLE `sob_tags_articles` (
    `tag_id` int(11) NOT NULL, 
    `article_id` int(11) NOT NULL, 
    `id` int(11) NOT NULL auto_increment, 
    PRIMARY KEY (`id`), 
    CONSTRAINT `some_unique_constraint_name_1` 
     FOREIGN KEY `article_id` 
     REFERENCES `article` (`id`) 
     ON DELETE CASCADE, 
    CONSTRAINT `some_unique_constraint_name_2` 
     FOREIGN KEY `tag_id` 
     REFERENCES `tag` (`id`) 
     ON DELETE CASCADE 
) ENGINE=InnoDB AUTO_INCREMENT=112 
+0

oh merci! ce schéma serait idéal pour cette tâche, mais je ne peux pas modifier le moteur de table. Et j'ai lu que Doctrine est meilleur et plus récent que Propel, donc je l'utilise. J'ai ajouté un index. Et maintenant je dois attraper l'exception de Doctrine. Savez-vous comment le faire? J'utilise une requête ajax pour enregistrer des enregistrements, donc je n'ai pas besoin que toute cette faille soit visible dans firebug. – user274101

+0

bloc Try/Catch typique avec une certaine logique pour s'assurer que c'est l'erreur que vous attendez ...Vous devrez peut-être vérifier le code d'erreur que je ne me souviens pas si Doctrine lance une exception de Doctrine générique ou quelque chose de plus spécifique. voir ci-dessus par exemple. – prodigitalson

+0

ok, merci. Je pense que le problème est résolu – user274101

-1

Vous pouvez étendre les SobTagsArticles objet avec une nouvelle méthode de sauvegarde, et vérifier si le dossier existe déjà:

public function exists() { 
    $q = Doctrine_Query::create() 
    ->from('sobtagsarticles ta') 
    ->where('ta.tag_id = ? and ta.article_id = ?', array($this->getTagId(), $this->getArticleId())); 

    if (!$result = $q->execute()) 
    { 
    parent::save(); 
    } 
} 

De cette façon, l'objet sera sauvé seulement s'il n'existe pas.

Vous pouvez également définir un index unique à votre table comme ceci:

UNIQUE INDEX `sb_tags_articles_unique` (`tag_id` ASC, `article_id` ASC) 

Votre schéma ressemblerait à ceci:

CREATE TABLE `sob_tags_articles` (
    `tag_id` int(11) NOT NULL, 
    `article_id` int(11) NOT NULL, 
    `id` int(11) NOT NULL auto_increment, 
    PRIMARY KEY (`id`), 
    UNIQUE INDEX `sb_tags_articles_unique` (`tag_id` ASC, `article_id` ASC), 
    CONSTRAINT `some_unique_constraint_name_1` 
     FOREIGN KEY `article_id` 
     REFERENCES `article` (`id`) 
     ON DELETE CASCADE, 
    CONSTRAINT `some_unique_constraint_name_2` 
     FOREIGN KEY `tag_id` 
     REFERENCES `tag` (`id`) 
     ON DELETE CASCADE 
) ENGINE=InnoDB AUTO_INCREMENT=112 
+0

Je pense qu'il est préférable de ne pas interroger mysql une fois de plus pour vérifier si l'enregistrement existe. – user274101

+0

Oui, vous pouvez utiliser la file d'attente memcached ou redis à la place –

6

Ceci est le code réel à utiliser

try 
{ 
    $record->save(); 
} 
catch(Doctrine_Connection_Exception $e) 
{ 
    if($e->getPortableCode() != Doctrine::ERR_ALREADY_EXISTS) 
    { 
     /** 
     * if its not the error code for a duplicate key 
     * value then rethrow the exception 
     */ 
     throw $e; 
    } 
    /** 
    * you might want to fetch the real record here instead 
    * so yure working with the persisted copy 
    */ 
} 
Questions connexes