发现SQLAlchemy对象的引用者

8 投票
3 回答
1856 浏览
提问于 2025-04-15 19:23

我有很多模型类,它们之间有关系,并且有一个CRUD接口可以进行编辑。问题是,有些对象不能被删除,因为有其他对象在引用它们。有时候我可以设置ON DELETE规则来处理这种情况,但在大多数情况下,我不想自动删除相关的对象,直到它们被手动解除绑定。无论如何,我想给编辑器展示一个当前查看对象的引用列表,并突出显示那些由于外键约束而阻止删除的对象。有没有现成的解决方案可以自动发现这些引用对象?

更新

这个任务似乎相当常见(例如,django ORM会显示所有依赖关系),所以我想知道为什么还没有解决方案。

这里有两个建议的方向:

  1. 列出当前对象的所有关系,并查看它们的backref。但并不能保证所有关系都有定义backref。而且,有些情况下backref是没有意义的。虽然我可以在每个地方定义它,但我不喜欢这样做,而且这也不可靠。
  2. (van和stephan建议的)检查MetaData对象的所有表,并从它们的foreign_keys属性中收集依赖关系(可以参考sqlalchemy_schemadisplay的代码,感谢stephan的评论)。这将允许捕捉到表之间的所有依赖关系,但我需要的是模型类之间的依赖关系。有些外键是在中间表中定义的,并没有对应的模型(在关系中用作secondary)。当然,我可以进一步查找相关模型(我还得找到方法),但这看起来太复杂了。

解决方案

下面是我用作解决方案的基础模型类的方法(为声明式扩展设计)。它并不完美,也不能满足我所有的要求,但在我项目的当前状态下是有效的。结果以字典的形式收集,所以我可以按对象和它们的属性进行分组展示。我还没有决定这是否是个好主意,因为引用者的列表有时非常庞大,我不得不将其限制在一个合理的数量内。

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

感谢所有帮助我的人,特别是stephan和van。

3 个回答

0

对于每个模型类,你可以很简单地检查它的所有一对多关系是否为空,只需要查看每个关系的列表,看看里面有多少条记录。(其实可能还有更高效的方法,比如用COUNT来统计。)如果有任何外键与这个对象相关,并且你正确设置了对象之间的关系,那么至少有一个列表的长度会大于零,也就是说里面会有数据。

1

一般来说,想要“发现”关系型数据库中的所有引用是没办法的。

在一些数据库中,它们可能会使用一种叫做声明性参照完整性的方式,这种方式通过明确的外键(Foreign Key)或检查约束(Check constraints)来实现。

不过,这并不是强制要求的,所以可能会有不完整或不一致的情况。

任何查询都可以包含一个未声明的外键关系。如果没有所有查询的完整信息,你就无法知道哪些关系是被使用但没有被声明的。

要想找到一般的“引用者”,你必须实际了解数据库的设计,并且掌握所有的查询。

6

SQL: 我完全不同意 S.Lott 的 回答。我不知道有没有现成的解决方案,但绝对可以找出所有与某个表有外键关系的表。你需要正确使用 INFORMATION_SCHEMA 视图,比如 REFERENTIAL_CONSTRAINTSKEY_COLUMN_USAGETABLE_CONSTRAINTS 等等。可以参考这个 SQL Server 示例。大多数新关系型数据库在某些限制和扩展下都支持 INFORMATION_SCHEMA 标准。当你拥有所有外键信息和表中的对象(行)后,只需运行几条 SELECT 语句,就能找到所有其他表中引用该行的其他行,从而防止它被删除。

SqlAlchemy: 正如 stephan 在评论中提到的,如果你在关系中使用 ormbackref,那么获取引用你想删除的对象的 对象列表应该很简单,因为这些对象基本上是你对象的映射属性(child1.Parent)。

如果你使用的是 SqlAlchemy 的 Table 对象(或者不总是对关系使用 backref),那么你需要获取所有表的 foreign_keys 值,然后对所有这些 ForeignKey 调用 references(...) 方法,传入你的表作为参数。这样你就能找到所有引用你对象所映射表的外键(和表)。然后你可以为每个外键构建查询,以查询所有引用你对象的对象。

撰写回答