SQLAlchemy对象已附加到会话

28 投票
6 回答
36334 浏览
提问于 2025-04-18 10:15

我正在为一个应用程序搭建服务器,但在登录时遇到了一个错误:

[!] Object '<User at 0x7f12bc185a90>' is already attached to session '2' (this is '3')

看起来我添加的会话已经在数据库里了。这段代码就是导致问题的原因:

@app.route('/login', methods=['POST'])
def login():
    u = User.query.filter(User.username == request.form["username"]).first()
    if not u or u.password != request.form["password"]:
        return error("E1")

    s = Session.get_by_user(u)
    if s is not None:
         db_session.delete(s)
         db_session.commit()

     print db_session.execute("SELECT * FROM sessions").fetchall()

     s = Session(u)
     db_session.add(s)
     db_session.commit()

     return jsonify(s.values)

如你所见,我在尝试添加任何内容之前,先打印了会话表里的内容,但它是空的!([])

还有什么其他原因可能导致这个问题呢?

这是“会话”的实现:

class Session(Base):
__tablename__ = "sessions"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), unique=True)
user = relationship(User)
key = Column(String(50), unique=True)
created = Column(DateTime)

def __init__(self, user=None):
    self.user = user
    self.key = base64.encodestring(os.urandom(24)).strip()
    self.created = datetime.now()

def __repr__(self):
    return '<Session %r>' % (self.key)

@property
def values(self):
    return {"username" : self.user.username,
            "key" : self.key,
            "created" : str(self.created),
            }
@classmethod
def get_by_key(cls, key):
    s = cls.query.filter(cls.key == key).first()
    #print datetime.now() - s.created
    if s and datetime.now() - s.created > settings.SESSION_LIFETIME:
        s = None
    return s

@classmethod
def get_by_user(cls, user):
    s = cls.query.filter(cls.user == user).first()
    if s and datetime.now() - s.created > settings.SESSION_LIFETIME:
        s.query.delete()
        db_session.commit()
        s = None
    return s

6 个回答

0

我也遇到过这个问题。

我创建了一个叫 test_file.py 的文件,并在里面加了这段代码:

from app import app
from models import Tovar  
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)

tovardel = Tovar.query.filter(Tovar.region == 1 and Tovar.price == 12).first()
db.session.delete(tovardel)
tovar = Tovar.query.filter(Tovar.region == 1 and Tovar.price == 12).first()
print(tovar.description)

然后当我运行这段代码时,出现了这个错误:

Object '<Tovar at 0x7f09cbf74208>' is already attached to session '1' (this is '2')

解决问题的方法:

如果你在比如说 text_file.py 里有 db = SQLAlchemy(app) 这行代码,而在 app.py 里,你总是会遇到这个问题。你应该删除 db = SQLAlchemy(app) 这行,然后从 app 里导入 db,使用 from app import db

7

这个错误的意思是你正在处理的记录被连接到了两个不同的会话(db)!

其中一个原因可能是你用一个 db = SQLAlchemy(app) 来定义你的模型,然后又用另一个来添加、插入或修改数据库!

我的解决办法是让数据库保持一致!

试试这个:

u = db.session.query(User).filter(User.username == request.form["username"]).first()

而不是这个:

u = User.query.filter(User.username == request.form["username"]).first()
11

这个数据库会话的问题会出现,如果你的 server.py 和 model.py 互相导入对方的话。

server.py

from flask import Flask
import os
import models as appmod #################### importing models here in server.py<----------

app = Flask(__name__)                                  # L1
app.config.from_object(os.environ['APP_SETTINGS'])     # L2
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False   # L3
database = SQLAlchemy(app)                             # L4
db = database                                          # L5

@app.route('/item_delete/<id>', methods=['DELETE'])
def remove_method(id = None):
    data_rec = appmod.Employee.query.get(id)    
    db.session.delete(data_rec)
    db.session.commit()
    return "DELETE"

if __name__ == '__main__':

    app.run(port=5000, host='0.0.0.0',debug=True,threaded=True)

models.py

from server import db #################### importing server in models.py here <------------
from sqlalchemy.dialects.mysql import JSON


class Employee(db.Model):
    __tablename__ = 'employe_flask'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    datetime = db.Column(db.DateTime)
    designation = db.Column(db.String(128))


    def __init__(self, name, datetime, designation):
        self.name = name
        self.datetime = datetime
        self.designation = designation

    @staticmethod
    def delete_rec(data_rec):
        db.session.delete(data_rec)#.delete
        db.session.commit()

    def __repr__(self):
        record = {"name":self.name,"date":self.datetime.ctime(),"designation":self.designation}.__str__()
        return record

把 server.py 中的 L1 到 L5 这一行删掉,放到一个公共文件里,比如 settings.py,然后在 server.py 中导入 'app' 和 'db',在 models.py 中导入 db。

下面是这些文件的示例:

server.py

from flask import Flask
import os
import models as appmod 
from settings import app, db


@app.route('/item_delete/<id>', methods=['DELETE'])
def remove_method(id = None):
    data_rec = appmod.Employee.query.get(id)    
    db.session.delete(data_rec)
    db.session.commit()
    return "DELETE"

if __name__ == '__main__':

    app.run(port=5000, host='0.0.0.0',debug=True,threaded=True)

settings.py

import os
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)                                  # L1
app.config.from_object(os.environ['APP_SETTINGS'])     # L2
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False   # L3
database = SQLAlchemy(app)                             # L4
db = database                                          # L5

models.py

from settings import db
from sqlalchemy.dialects.mysql import JSON


class Employee(db.Model):
    __tablename__ = 'employe_flask'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    datetime = db.Column(db.DateTime)
    designation = db.Column(db.String(128))


    def __init__(self, name, datetime, designation):
        self.name = name
        self.datetime = datetime
        self.designation = designation

    @staticmethod
    def delete_rec(data_rec):
        db.session.delete(data_rec)#.delete
        db.session.commit()

    def __repr__(self):
        record = {"name":self.name,"date":self.datetime.ctime(),"designation":self.designation}.__str__()
        return record
22

你想修改的对象已经和另一个会话绑定在一起了。可能是你导入的东西不对,导致db_session是一个新的实例。

一个好的解决办法是提取当前绑定的会话,然后使用它:

不要这样做:

db_session.add(s)

要这样做:

current_db_sessions = db_session.object_session(s)
current_db_sessions.add(s)
29

正如@marcinkuzminski提到的,你不能把一个已经和另一个会话(session)关联的对象再添加到新的会话中。不过,直接用object_session()从对象中获取原来的会话是有风险的,特别是当你不确定这个会话是否是在你当前操作的同一个线程中创建的。为了安全起见,可以使用一种线程安全的方法,叫做merge()

    local_object = db_session.merge(original_object)
    db_session.add(local_object)
    db_session.commit()

撰写回答