【问题标题】:Creating One-to-Many with Relationship for the ORM in SQLAlchemy在 SQLAlchemy 中为 ORM 创建一对多关系
【发布时间】:2015-02-19 13:53:20
【问题描述】:

我正在学习 SQLAlchemy,我今天阅读了很多关于关系的文章,其中包括一些关于 SO 的帖子。然而,我发现没有一个例子能完全回答这个问题,尽管我认为这将是处理关系时最先回答的问题之一。

我有网页表单数据,其中很多是重复的 - 例如访问者浏览器用户代理字符串、提供表单的域和提供表单的域。我想保留这些数据,但显然将诸如代理之类的东西存储在他们自己的表中,然后在表单数据表中保留一个 ID 更有意义。所以我有一个像这样的代理类:

class Agent(Base):
    __tablename__ = 'agents'
    __table_args__ = {'mysql_engine': 'InnoDB'}

    ID = Column(Integer, autoincrement = True, primary_key = True)
    UserAgent = Column(VARCHAR(256), nullable = False, unique = True)

    #UniqueConstraint('UserAgent')

    def __repr__(self):
        return "<Agent(UserAgent='%s')>" % (self.UserAgent)

然后我有一个表单数据类:

class Lead(Base):
    __tablename__ = 'leads'
    __table_args__ = {'mysql_engine': 'InnoDB'}

    ID = Column(String(32), primary_key = True)
    AgentID = Column(Integer, ForeignKey('agents.ID'), nullable = False)
    .... other non relational fields ....
    IsPartial = Column(Boolean, nullable = False)

    Agent = relationship('Agent', backref = backref('leads', uselist = True))

    def __repr__(self):
        return "<Lead(ID='%s')>" % (self.ID)

此时,SQLAlchemy 创建了我要求它创建的所有表,并且我可以创建一个测试 Lead 实例:

testLead = Lead(ID='....', ...)

然后创建一个测试代理实例:

testAgent = Agent(UserAgent='...', leads=testLead)

testLead 实例化得很好。但是,代理实例化失败:

TypeError: Incompatible collection type: Lead is not list-like

使用 testLead.Agent = [...] 结果:

AttributeError: 'list' object has no attribute '_sa_instance_state'

理想情况下,我希望能够使用 Agent 字符串实例化 Lead 对象。然后,当我使用 session.add 和 session.commit 时,如果缺少,则让 ORM 将代理字符串添加到代理表中。同样,当我实例化 Lead 类时,我希望能够这样做:

lead = Lead(ID='...')

如果我使用:

lead.Agent

代理字符串应该会显示出来。如果我正确阅读了文档,则需要添加一些延迟加载设置。

有没有办法做到这一点?如果没有,如何解决上述错误?

谢谢

【问题讨论】:

    标签: python mysql sqlalchemy


    【解决方案1】:

    您似乎混合了1-many 关系的两个方面:您的代码与测试数据的工作方式好像一个Lead 有多个Agents,而relationship 的定义则相反。假设模型是正确的,下面应该可以工作:

    testLead = Lead(ID='....', ...)
    testAgent = Agent(UserAgent='...', leads=[testLead])
    

    或:

    testAgent = Agent(UserAgent='...')
    testLead = Lead(ID='....', ..., Agent=testAgent)
    

    至于你问题的第二部分,你可以用简单的python来实现:

    class Lead(Base):
        # ...
    
        @property
        def UserAgent(self):
            return self.Agent and self.Agent.UserAgent
    
        @UserAgent.setter
        def UserAgent(self, value):
            # @note: you might first want to search for agent with this UserAgent value, 
            # and create a new one only if one does not exist
            # agent_obj = object_session(self).query(Agent).filter(Agent.UserAgent == value).one()
            agent_obj = Agent(UserAgent=value)
            self.Agent = agent_obj
    
        @UserAgent.deleter
        def UserAgent(self):
            self.Agent = None
    

    或者你可以使用Association Proxy来达到目的:

    def _agent_find_or_create(user_agent):
        agent = session.query(Agent).filter(Agent.UserAgent==user_agent).first()
        return agent or Agent(UserAgent=user_agent)
    
    class Lead(Base):
        # ...
    
        Agent = relationship('Agent', backref = backref('leads', uselist = True))
        UserAgent = association_proxy('Agent', 'UserAgent',
                                      creator=_agent_find_or_create)
    

    【讨论】:

    • 许多潜在客户可以有相同的用户代理,但一个潜在客户应该只有 1 个用户代理。我认为 SQLAlchemy 的 ORM 中关系的目的是处理所有这些。不过,我明白你现在在做什么。当我的关系需要成为 Agent 的实例时,我试图用字符串设置 Agent。当您输入答案时,我正在让类插入数据,但随后遇到了一个错误,即潜在客户通过关系尝试在 UNIQUE 列上重新插入一个值。所以现在我要弄清楚如何插入忽略。
    • 您设置了 testAgent 的第二个代码是我的目的的正确方法。我喜欢 setter 和 deleter,但我有很多属性可以创建 setter 和 deleter 属性,所以我可能会寻找更通用的东西。谢谢
    • 查看关联代理的更新,它也有 find_or_create 例程(类似于 INSERT IGNORE)。
    • association_proxy 代码就像一个魅力。谢谢
    • 所以只要我只有 2 个关联/关系,您的解决方案就可以很好地工作。一旦我添加了第三个循环,我就使用它来将数据输入到 DB 炸弹中。对于不允许为 NULL 的 NULL 项目之一,它总是给我一个 IntegrityError。但它发生是因为你的 find_and_create 中的 session.query 抛出了一个错误。 SQLAlchemy 然后捕获它并返回到脚本的其余部分,然后尝试在大部分数据丢失的情况下执行 INSERT。奇怪的是,当我只有 2 个关联/关系但我有 12 个时,一切正常...
    猜你喜欢
    • 2016-03-31
    • 2021-06-26
    • 2019-01-12
    • 2012-04-17
    • 2020-05-16
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    • 2015-04-29
    相关资源
    最近更新 更多