发现SQLAlchemy对象的引用者
我有很多模型类,它们之间有关系,并且有一个CRUD接口可以进行编辑。问题是,有些对象不能被删除,因为有其他对象在引用它们。有时候我可以设置ON DELETE规则来处理这种情况,但在大多数情况下,我不想自动删除相关的对象,直到它们被手动解除绑定。无论如何,我想给编辑器展示一个当前查看对象的引用列表,并突出显示那些由于外键约束而阻止删除的对象。有没有现成的解决方案可以自动发现这些引用对象?
更新
这个任务似乎相当常见(例如,django ORM会显示所有依赖关系),所以我想知道为什么还没有解决方案。
这里有两个建议的方向:
- 列出当前对象的所有关系,并查看它们的
backref
。但并不能保证所有关系都有定义backref
。而且,有些情况下backref
是没有意义的。虽然我可以在每个地方定义它,但我不喜欢这样做,而且这也不可靠。 - (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 个回答
对于每个模型类,你可以很简单地检查它的所有一对多关系是否为空,只需要查看每个关系的列表,看看里面有多少条记录。(其实可能还有更高效的方法,比如用COUNT来统计。)如果有任何外键与这个对象相关,并且你正确设置了对象之间的关系,那么至少有一个列表的长度会大于零,也就是说里面会有数据。
一般来说,想要“发现”关系型数据库中的所有引用是没办法的。
在一些数据库中,它们可能会使用一种叫做声明性参照完整性的方式,这种方式通过明确的外键(Foreign Key)或检查约束(Check constraints)来实现。
不过,这并不是强制要求的,所以可能会有不完整或不一致的情况。
任何查询都可以包含一个未声明的外键关系。如果没有所有查询的完整信息,你就无法知道哪些关系是被使用但没有被声明的。
要想找到一般的“引用者”,你必须实际了解数据库的设计,并且掌握所有的查询。
SQL: 我完全不同意 S.Lott 的 回答。我不知道有没有现成的解决方案,但绝对可以找出所有与某个表有外键关系的表。你需要正确使用 INFORMATION_SCHEMA
视图,比如 REFERENTIAL_CONSTRAINTS
、KEY_COLUMN_USAGE
、TABLE_CONSTRAINTS
等等。可以参考这个 SQL Server 示例。大多数新关系型数据库在某些限制和扩展下都支持 INFORMATION_SCHEMA
标准。当你拥有所有外键信息和表中的对象(行)后,只需运行几条 SELECT
语句,就能找到所有其他表中引用该行的其他行,从而防止它被删除。
SqlAlchemy: 正如 stephan 在评论中提到的,如果你在关系中使用 orm
和 backref
,那么获取引用你想删除的对象的 父 对象列表应该很简单,因为这些对象基本上是你对象的映射属性(child1.Parent
)。
如果你使用的是 SqlAlchemy 的 Table
对象(或者不总是对关系使用 backref
),那么你需要获取所有表的 foreign_keys
值,然后对所有这些 ForeignKey
调用 references(...)
方法,传入你的表作为参数。这样你就能找到所有引用你对象所映射表的外键(和表)。然后你可以为每个外键构建查询,以查询所有引用你对象的对象。