使用SQLAlchemy导入带外键的假数据?

3 投票
3 回答
1181 浏览
提问于 2025-04-15 23:28

我最近在尝试用fixture来加载测试数据集到我的Pylons / PostgreSQL应用中。这一切都很顺利,除了在创建外键时,如果外键指向的是一个自动递增的ID字段,就会出现问题。

我的fixture看起来是这样的:

class AuthorsData(DataSet):
    class frank_herbert:
         first_name = "Frank"
         last_name = "Herbert"

class BooksData(DataSet):
    class dune:
        title = "Dune"
        author_id = AuthorsData.frank_herbert.ref('id')

而模型则是:

t_authors = sa.Table("authors", meta.metadata,
    sa.Column("id", sa.types.Integer, primary_key=True),
    sa.Column("first_name", sa.types.String(100)),
    sa.Column("last_name", sa.types.String(100)),
    )

t_books = sa.Table("books", meta.metadata,
    sa.Column("id", sa.types.Integer, primary_key=True),
    sa.Column("title", sa.types.String(100)),
    sa.Column("author_id", sa.types.Integer, sa.ForeignKey('authors.id')) 
    )

当我运行“paster setup-app development.ini”时,SQLAlchemy报告外键的值为“None”,显然它没有找到这个值:

15:24:27,284 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] 
CREATE TABLE authors (
    id SERIAL NOT NULL, 
    first_name VARCHAR(100), 
    last_name VARCHAR(100), 
    PRIMARY KEY (id)
)

15:24:27,284 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] {}
15:24:27,291 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] COMMIT
15:24:27,292 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] 
CREATE TABLE books (
    id SERIAL NOT NULL, 
    title VARCHAR(100), 
    author_id INTEGER, 
    PRIMARY KEY (id), 
    FOREIGN KEY(author_id) REFERENCES authors (id)
)

15:24:27,293 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] {}
15:24:27,300 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] COMMIT
15:24:27,301 INFO  [blog.websetup] Inserting initial data
15:24:27,302 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] BEGIN
15:24:27,309 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] INSERT INTO authors (first_name, last_name) VALUES (%(first_name)s, %(last_name)s) RETURNING authors.id
15:24:27,309 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] {'first_name': 'Frank', 'last_name': 'Herbert'}
15:24:27,320 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] INSERT INTO books (title, author_id) VALUES (%(title)s, %(author_id)s) RETURNING books.id
15:24:27,320 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] {'author_id': None, 'title': 'Dune'}
15:24:27,322 INFO  [sqlalchemy.engine.base.Engine.0x...9f50] COMMIT
15:24:27,323 INFO  [blog.websetup] Done

fixture的文档实际上警告说这可能会是个问题:

“然而,在某些情况下,你可能需要引用一个在加载之前没有值的属性,比如一个序列ID列。(注意,当使用会话时,SQLAlchemy的数据层不支持这一点。)”

http://farmdev.com/projects/fixture/using-dataset.html#referencing-foreign-dataset-classes

这是否意味着SQLAlchemy根本不支持这个功能?还是说可以在不使用SA“会话”的情况下加载数据?其他人是怎么解决这个问题的呢?

3 个回答

0

我通过这样定义 orm.relation 来解决这个问题:

class BooksData(DataSet):
    class dune:
        title = "Dune"
        author = AuthorsData.frank_herbert


mapper(Book, books, properties={
   'author': relation(Author)
 })
1

你不能直接写 author = AuthorsData.frank_herbert 来代替直接指定 author_id 吗?

除此之外,也许在插入你的 AuthorsData 之后调用 meta.Session.flush() 会强制让 id 属性获得值?我在测试时就是这么做的,当我需要分配 ID 时(我不使用固定数据)。

1

在SQLAlchemy中,SQL表达式层不支持引用还没有插入的行,因为这样没有意义:所有的查询都是明确执行的。ORM层(对象关系映射层)支持引用新对象,它甚至使用了一种叫做“工作单元模式”的方法来正确安排插入的顺序。为了解决这个问题,工具使用了ref()方法。不过要注意,在SQL表达式层,你(工具)执行的是语句,所以SQLAlchemy没有机会重新排序这些语句。因此,如果你试图在插入作者之前存储书籍,author_id就无法使用。你确定你的工具是按照正确的顺序组织(传递给data()方法)的吗?

撰写回答