使用SQLAlchemy在关联表中插入数据时出现IntegrityError

1 python sqlalchemy

我正在尝试在此关联表中插入角色数据:

class Association(db.Model):
    __tablename__ = 'associations'
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), primary_key=True)
    role = db.Column(db.String)
    user = db.relationship("User", back_populates="teams")
    team = db.relationship("Team", back_populates="users")
Run Code Online (Sandbox Code Playgroud)

它链接到另外两个表:

class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String)
    username = db.Column(db.String, nullable=False, unique=True, index=True)
    password_hash = db.Column(db.String)
    teams = db.relationship("Association", back_populates="user")

class Team(db.Model):
    __tablename__ = 'teams'
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String)
    player = db.Column(db.String)
    users = db.relationship("Association", back_populates="team")
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试在关联表中插入角色时:

t = Team.query.filter_by(id=team_name1_id).first()
a = Association(role=form.role.data)
a.user = User.query.filter_by(id=current_user.id).first()
t.users.append(a)
db.session.add(t)
db.session.commit()
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

IntegrityError :(由于查询调用的自动刷新而引发;如果此刷新过早发生,请考虑使用session.no_autoflush块)(sqlite3.IntegrityError)NOT NULL约束失败:association.team_id [SQL:u'INSERT INTO关联(user_id ,角色)VALUES(?,?)'] [参数:(1,u'控球后卫')]

你知道我该怎么解决吗?

Ilj*_*ilä 5

导致自动刷新的行是

t.users.append(a)
Run Code Online (Sandbox Code Playgroud)

您尚未定义关系加载策略,因此默认为"select"。由于您是Team.users第一次访问Relationship属性,因此它将发出SELECT以便获取相关对象。

您还通过以下方式间接更改了会话

a.user = User.query.filter_by(id=current_user.id).first()
Run Code Online (Sandbox Code Playgroud)

由于默认情况下为save-update Cascade,它也将新Association实例也放置到会话中。

为了保持数据库和会话状态的一致性,SQLAlchemy必须在发出查询之前将您的更改刷新到数据库,因此初始化的一半将Association在与Team实例关联之前发送到数据库。

可能的解决方案:

  1. 暂时禁用该特定追加的自动刷新功能,因为您知道要添加新的Association:

    with db.session.no_autoflush:
        t.users.append(a)
    
    Run Code Online (Sandbox Code Playgroud)

    SELECT仍然会发出。

  2. 反转操作。无需将Association实例附加到Team.users集合,而是将分配Team给Association.team:

    # Instead of t.users.append(a):
    a.team = t  # Mr.
    
    Run Code Online (Sandbox Code Playgroud)
  3. 一个不太明显的解决方案是在获取实例时急于加载关系的内容Team,因此不需要SELECT:

    t = db.session.query(Team).options(db.joinedload(Team.users)).first()
    
    Run Code Online (Sandbox Code Playgroud)

    或根本不使用该db.noload(Team.users)选项加载它们。

注意

db.session.add(t)
Run Code Online (Sandbox Code Playgroud)

是多余的。提取的Team实例已经在会话中–否则,首先Team.users 不能进行的懒惰加载。