2012-10-08 38 views
8

Tôi có hai bảng, nói A và B. Cả hai đều có một id khóa chính. Họ có mối quan hệ nhiều-nhiều, SEC.Sqlalchemy: cập nhật mối quan hệ thứ cấp

SEC = Table('sec', Base.metadata, 
    Column('a_id', Integer, ForeignKey('A.id'), primary_key=True, nullable=False), 
    Column('b_id', Integer, ForeignKey('B.id'), primary_key=True, nullable=False) 
) 

class A(): 
    ... 
    id = Column(Integer, primary_key=True) 
    ... 
    rels = relationship(B, secondary=SEC) 

class B(): 
    ... 
    id = Column(Integer, primary_key=True) 
    ... 

Hãy xem xét đoạn mã này.

a = A() 
b1 = B() 
b2 = B() 
a.rels = [b1, b2] 
... 
#some place later 
b3 = B() 
a.rels = [b1, b3] # errors sometimes 

Đôi khi, tôi nhận được một lỗi ở dòng cuối cùng nói

duplicate key value violates unique constraint a_b_pkey 

Trong hiểu biết của tôi, tôi nghĩ rằng nó sẽ cố gắng để thêm (a.id, b.id) vào bảng 's' một lần nữa dẫn đến một lỗi ràng buộc duy nhất. Đây co thật sự la bản chât của no? Nếu vậy, làm thế nào tôi có thể tránh điều này? Nếu không, tại sao tôi có lỗi này?

Trả lời

3

Lỗi bạn đề cập thực sự là chèn giá trị mâu thuẫn vào bảng sec. Để chắc chắn rằng đó là từ các hoạt động bạn nghĩ rằng đó là, không phải một số thay đổi trước đó, bật đăng nhập SQL và kiểm tra những giá trị là nó cố gắng để chèn trước khi lỗi ra.

Khi ghi đè giá trị bộ sưu tập nhiều đến nhiều, SQLAlchemy so sánh nội dung mới của bộ sưu tập với trạng thái trong cơ sở dữ liệu và các vấn đề xóa và chèn tương ứng. Trừ khi bạn đang poking xung quanh trong SQLAlchemy internals, cần có hai cách để gặp phải lỗi này. Đầu tiên là sửa đổi đồng thời: Quy trình 1 lấy giá trị a.rels và thông báo rằng nó rỗng, trong khi đó Process 2 cũng lấy a.rels, đặt nó thành [b1, b2] và cam kết xóa (a, b1) , (a, b2) tuples, Quy trình 1 đặt a.rels thành [b1, b3] để ý rằng các nội dung trước đó trống và khi nó cố gắng xóa tuple sec (a, b1) nó sẽ nhận được một lỗi khóa trùng lặp. Hành động chính xác trong các trường hợp như vậy thường là thử lại giao dịch từ trên cùng. Bạn có thể sử dụng serializable transaction isolation để thay vào đó nhận được lỗi tuần tự hóa trong trường hợp này khác với lỗi logic nghiệp vụ gây ra lỗi khóa trùng lặp.

Trường hợp thứ hai xảy ra khi bạn đã quản lý thuyết phục SQLAlchemy rằng bạn không cần biết trạng thái cơ sở dữ liệu bằng cách đặt chiến lược tải thuộc tính rels thành noload. Điều này có thể được thực hiện khi xác định mối quan hệ bằng cách thêm tham số lazy='noload' hoặc khi truy vấn, gọi .options(noload(A.rels)) trên truy vấn. SQLAlchemy sẽ giả sử rằng bảng sec không có hàng phù hợp cho các đối tượng được tải với chiến lược này có hiệu lực.

+0

Tôi không thực sự chắc chắn tại sao.Tôi cần phải kiểm tra nó đúng cách và sẽ cho bạn biết. Cảm ơn đã giúp đỡ. – Sri

8

Vấn đề là bạn muốn đảm bảo các trường hợp bạn tạo là duy nhất. Chúng ta có thể tạo một hàm tạo thay thế để kiểm tra bộ nhớ cache của các cá thể hiện tại không được yêu cầu hoặc truy vấn cơ sở dữ liệu cho cá thể đã cam kết hiện có trước khi trả về một cá thể mới.

Dưới đây là một minh chứng của một phương pháp như:

from sqlalchemy import Column, Integer, String, ForeignKey, Table 
from sqlalchemy.engine import create_engine 
from sqlalchemy.ext.declarative.api import declarative_base 
from sqlalchemy.orm import sessionmaker, relationship 

engine = create_engine('sqlite:///:memory:', echo=True) 
Session = sessionmaker(engine) 
Base = declarative_base(engine) 

session = Session() 


class Role(Base): 
    __tablename__ = 'role' 

    id = Column(Integer, primary_key=True) 
    name = Column(String, nullable=False, unique=True) 

    @classmethod 
    def get_unique(cls, name): 
     # get the session cache, creating it if necessary 
     cache = session._unique_cache = getattr(session, '_unique_cache', {}) 
     # create a key for memoizing 
     key = (cls, name) 
     # check the cache first 
     o = cache.get(key) 
     if o is None: 
      # check the database if it's not in the cache 
      o = session.query(cls).filter_by(name=name).first() 
      if o is None: 
       # create a new one if it's not in the database 
       o = cls(name=name) 
       session.add(o) 
      # update the cache 
      cache[key] = o 
     return o 


Base.metadata.create_all() 

# demonstrate cache check 
r1 = Role.get_unique('admin') # this is new 
r2 = Role.get_unique('admin') # from cache 
session.commit() # doesn't fail 

# demonstrate database check 
r1 = Role.get_unique('mod') # this is new 
session.commit() 
session._unique_cache.clear() # empty cache 
r2 = Role.get_unique('mod') # from database 
session.commit() # nop 

# show final state 
print session.query(Role).all() # two unique instances from four create calls 

Phương pháp create_unique được lấy cảm hứng từ example from the SQLAlchemy wiki. Phiên bản này ít phức tạp hơn, ưu tiên sự đơn giản hơn tính linh hoạt. Tôi đã sử dụng nó trong các hệ thống sản xuất mà không có vấn đề gì.

Có những cải tiến rõ ràng có thể được thêm vào; đây chỉ là một ví dụ đơn giản. Phương thức get_unique có thể được kế thừa từ UniqueMixin, được sử dụng cho bất kỳ số lượng mô hình nào. Việc ghi nhớ các đối số linh hoạt hơn có thể được thực hiện. Điều này cũng đặt sang một bên vấn đề của nhiều chủ đề chèn dữ liệu xung đột được đề cập bởi Ants Aasma; việc xử lý phức tạp hơn nhưng phải là một phần mở rộng rõ ràng. Tôi để điều đó cho bạn.

Các vấn đề liên quan