J'ai beaucoup de classes de modèles avec des relations entre elles avec une interface CRUD à éditer. Le problème est que certains objets ne peuvent pas être supprimés car d'autres objets y font référence. Parfois, je peux configurer la règle ON DELETE pour gérer ce cas, mais dans la plupart des cas, je ne souhaite pas la suppression automatique des objets associés tant qu'ils ne sont pas liés manuellement. Quoi qu'il en soit, j'aimerais présenter à l'éditeur une liste d'objets faisant référence à l'objet actuellement consulté et mettre en évidence ceux qui empêchent sa suppression en raison de la contrainte FOREIGN KEY. Existe-t-il une solution prête à découvrir automatiquement les référents ?

Mettre à jour

La tâche semble être assez courante (par exemple, django ORM affiche toutes les dépendances), donc je me demande s'il n'y a pas encore de solution.

Deux directions sont suggérées :

  1. Énumérez toutes les relations de l'objet courant et parcourez leurs backref. Mais il n'y a aucune garantie que toutes les relations aient backref définies. De plus, il y a des cas où backref n'a pas de sens. Bien que je puisse le définir partout, je n'aime pas faire de cette façon et ce n'est pas fiable.
  2. (Suggéré par van et stephan) Vérifiez toutes les tables de l'objet MetaData et collectez les dépendances de leur propriété foreign_keys (le code de sqlalchemy_schemadisplay peut être utilisé comme exemple, grâce aux commentaires de stephan). Cela permettra d'attraper toutes les dépendances entre les tables, mais ce dont j'ai besoin, ce sont les dépendances entre les classes de modèle. Certaines clés étrangères sont définies dans des tables intermédiaires et n'ont pas de modèles qui leur correspondent (utilisées comme secondary dans les relations). Bien sûr, je peux aller plus loin et trouver un modèle associé (je dois encore trouver un moyen de le faire), mais cela semble trop compliqué.

Solution

Vous trouverez ci-dessous une méthode de classe de modèle de base (conçue pour l'extension déclarative) que j'utilise comme solution. Ce n'est pas parfait et ne répond pas à toutes mes exigences, mais cela fonctionne pour l'état actuel de mon projet. Le résultat est collecté sous forme de dictionnaire de dictionnaires, je peux donc les montrer regroupés par objets et leurs propriétés. Je n'ai pas encore décidé si c'est une bonne idée, car la liste de référents est parfois énorme et je suis obligé de la limiter à un nombre raisonnable.

def _get_referers(self):
    db = object_session(self)
    cls, ident = identity_key(instance=self)
    medatada = cls.__table__.metadata
    result = {}
    # _mapped_models is my extension. It is collected by metaclass, so I didn't
    # look for other ways to find all model classes.
    for other_class in medatada._mapped_models:
        queries = {}
        for prop in class_mapper(other_class).iterate_properties:
            if not (isinstance(prop, PropertyLoader) and \
                    issubclass(cls, prop.mapper.class_)):
                continue
            query = db.query(prop.parent)
            comp = prop.comparator
            if prop.uselist:
                query = query.filter(comp.contains(self))
            else:
                query = query.filter(comp==self)
            count = query.count()
            if count:
                queries[prop] = (count, query)
        if queries:
            result[other_class] = queries
    return result

Merci à tous ceux qui m'ont aidé, en particulier stephan et van.

8
Denis Otkidach 17 févr. 2010 à 13:37
Je ne suis pas sûr de comprendre la question. Les objets de table ont une propriété foreign_keys qui doit être définie correctement si vous avez obtenu les métadonnées par réflexion. Les classes mappées ORM ont des relations et des backrefs dans le cadre du mappeur. Peux-tu élaborer?
 – 
stephan
17 févr. 2010 à 14:21
@stephan: J'ai mis à jour la question, j'espère que c'est clair maintenant.
 – 
Denis Otkidach
17 févr. 2010 à 18:42
@Denis: oui beaucoup plus clair maintenant, et je pense que van l'a déjà expliqué en grande partie. Étant donné une classe de modèle, vous pouvez obtenir le mappeur avec la fonction class_mapper() ou object_mapper(). Cela vous donne la table mappée. Ensuite, vous pouvez parcourir les foreign_keys de ces tables pour obtenir toutes les tables associées. Pour ceux-ci, vous vérifiez à quelles classes ils sont mappés. Voir le deuxième exemple dans sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay pour quelque chose de similaire. Je ne connais pas de meilleur moyen.
 – 
stephan
17 févr. 2010 à 20:01
@stephan : Non, je dois énumérer toutes les tables et trouver celles qui foreign_keys renvoient aux tables du modèle actuel. Et faites particulièrement attention lorsque l'héritage de table jointe est utilisé.
 – 
Denis Otkidach
17 févr. 2010 à 20:10
@Denis : Je savais que je ne comprenais pas tout à fait les choses ;-). En fin de compte, ne cherchez-vous pas quelque chose qui ressemble au diagramme UML publié dans le lien précédent pour savoir ce qui est lié à quoi et comment ?
 – 
stephan
17 févr. 2010 à 20:26

3 réponses

Meilleure réponse

SQL : je suis absolument en désaccord avec S.Lott' répondez. Je ne connais pas de solution prête à l'emploi, mais il est tout à fait possible de découvrir toutes les tables qui ont des contraintes ForeignKey pour une table donnée. Il faut utiliser correctement les vues INFORMATION_SCHEMA telles que REFERENTIAL_CONSTRAINTS, KEY_COLUMN_USAGE, TABLE_CONSTRAINTS, etc. Voir Exemple SQL Server. Avec quelques limitations et extensions, la plupart des versions des nouvelles bases de données relationnelles prennent en charge la norme INFORMATION_SCHEMA. Lorsque vous avez toutes les informations FK et l'objet (ligne) dans la table, il s'agit d'exécuter quelques instructions SELECT pour obtenir toutes les autres lignes dans d'autres tables qui font référence à une ligne donnée et empêcher sa suppression.

SqlAlchemy : Comme l'a noté stephan dans son commentaire, si vous utilisez orm avec backref pour les relations, il devrait être assez facile pour vous de obtenir la liste des objets parents qui conservent la référence à l'objet que vous essayez de supprimer, car ces objets sont essentiellement des propriétés mappées de votre objet (child1.Parent).

Si vous travaillez avec des objets Table de l'alchimie SQL (ou n'utilisez pas toujours backref pour les relations), alors vous devrez obtenir les valeurs de foreign_keys pour toutes les tables, puis pour toutes celles ForeignKey appelle la méthode references(...), en fournissant votre table en paramètre. De cette façon, vous trouverez tous les FK (et tables) qui font référence à la table à laquelle votre objet est mappé. Ensuite, vous pouvez interroger tous les objets qui conservent une référence à votre objet en construisant la requête pour chacun de ces FK.

6
Community 14 nov. 2018 à 02:43
1
Merci (+1) pour l'idée de vérifier foreign_keys pour toutes les tables, jusqu'à présent, cela semble être le moyen le plus prometteur/fiable. Je n'utilise presque jamais backref car il définit une relation en dehors de la définition du modèle : vous ne pouvez pas voir toutes les propriétés disponibles à partir de la définition de classe, le code n'est pas auto-documenté.
 – 
Denis Otkidach
17 févr. 2010 à 16:59
1
Vous pouvez découvrir toutes les contraintes FK. Je n'ai jamais dit que tu ne pouvais pas. Nous sommes d'accord sur la recherche des contraintes FK. Cependant, vous ne pouvez pas découvrir toutes les requêtes qui échoueront car elles dépendent d'une relation FK non déclarée.
 – 
S.Lott
17 févr. 2010 à 17:14

En général, il n'y a aucun moyen de "découvrir" toutes les références dans une base de données relationnelle.

Dans certaines bases de données, ils peuvent utiliser l'intégrité référentielle déclarative sous la forme de contraintes explicites de clé étrangère ou de vérification.

Mais il n'y a aucune obligation de le faire. Il peut être incomplet ou incohérent.

Toute requête peut inclure une relation FK qui n'est pas déclarée. Sans l'univers de toutes les requêtes, vous ne pouvez pas connaître les relations qui sont utilisées mais non déclarées.

Pour trouver des « référents » en général, vous devez réellement connaître la conception de la base de données et disposer de toutes les requêtes.

1
S.Lott 17 févr. 2010 à 17:16
2
Information_schema fournit les informations nécessaires pour découvrir les références. et Denis ne s'intéresse qu'à ceux qui empêchent réellement la suppression de la ligne, ce qui signifie qu'il s'agit d'une véritable contrainte FOREIGN KEY.
 – 
van
17 févr. 2010 à 15:52
La question portait sur SQLAlchemy, qui a déjà une définition de schéma en tant que structure en mémoire (définie dans le code ou chargée automatiquement à partir de la base de données).
 – 
Denis Otkidach
17 févr. 2010 à 17:08
@van : Le problème est qu'il existe encore des règles informelles. Si une requête attend une relation FK non déclarée, une suppression autorisée interrompra toujours l'application. D'où la partie "en général" de la réponse. Les relations déclarées ne concernent pas tout le magasin en général. La question ne reflète pas cela, ce qui entraînera - en général - des problèmes. Le schéma spécifique peut être vraiment, vraiment complet et peut avoir tous les FK déclarés. Mais, en général, il n'y a aucun moyen de le savoir à moins que vous ne procédiez à une ingénierie inverse de chaque requête.
 – 
S.Lott
17 févr. 2010 à 17:13
@S.Lott: bien sûr, j'ai également vu des bases de données sans une seule contrainte FK (bien qu'il y ait eu des références, que vous appelez "non déclarées"). Étant un très bon point en soi, ce n'est pas ce que la question demande
 – 
van
17 févr. 2010 à 18:02
@van : Exact. Ce n'est pas ce que la question demande littéralement. La question est pauvre à cet égard. On peut espérer trouver toutes sortes d'informations juteuses dans le schéma. Mais le seul univers entier des requêtes révélera les relations réelles réellement utilisées dans l'application réelle.
 – 
S.Lott
17 févr. 2010 à 18:30

Pour chaque classe de modèle, vous pouvez facilement voir si toutes ses relations un-à-plusieurs sont vides simplement en demandant la liste dans chaque cas et en voyant combien d'entrées elle contient. (Il existe probablement un moyen plus efficace d'implémenter également COUNT.) S'il existe des clés étrangères relatives à l'objet et que vos relations d'objet sont correctement configurées, alors au moins une de ces listes sera différente de zéro. en longueur.

0
Kylotan 17 févr. 2010 à 14:20
Ce n'est pas toujours vrai. L'objet actuel ne peut avoir aucune relation, alors qu'il existe un objet qui s'y réfère via une clé étrangère et a une relation sans backref. Notez également que vérifier tous les objets dans toutes les relations d'objets n'est pas une option, car il peut s'agir d'une énorme collection (lazy='dynamic').
 – 
Denis Otkidach
17 févr. 2010 à 16:34
Énorme ou pas, sans lire au moins 1 ligne, vous ne pouvez pas savoir s'il y a des références. En ce qui concerne le manque de backrefs, c'est ce que je voulais dire par "configurer correctement" bien que je reconnaisse qu'il n'est pas incorrect de manquer de backref, juste gênant.
 – 
Kylotan
17 févr. 2010 à 19:00