其實這些都可以在官方文件及 Google IO 的 slides 裡找到,不過我就幫大家整理一下(並且用中文說 XD)。

Google App Engine 帶給大家一個新的資料儲存概念,就是 Datastore 這個東西,它跟一般的關聯式資料庫(RDB)的概念相差不少,從官方文件或是 Google 工程師的演講中都不斷強調要大家用新的思維去想這個資料儲存方式,而不要一直侷限於怎麼把 RDB 的那一套搬過來。

不過好在 Datastore API 在描述資料的 Model 或 Expando 的資料欄位上提供了不少好用的資料型別(data type),所以一些常用的關聯還是很容易可以完成。這篇就介紹幾個常用的關聯,在 Datastore 上要怎麼下手。

One-to-One & One-to-Many

通常會用到這個關聯,就是 A model 中有個 column 的值可能是一個串列,在 RDB 中我們可能就會再建立一個 B model,然後在 B 中設下一個 A_id 的 column 用來指向 A 的某個 row,但是在 Datastore 中沒有 join 運算,所以得換個方法來做。

假設我在建立一個通訊錄的 Model,一個人可能會有很多 E-Mail,我的 model (在 Datastore 中)就可以寫成這樣:


class User(db.Model):
    name = db.StringProperty(required=True)

class Email(db.Model):
    user = db.ReferenceProperty(User, collection_name='emails')
    addr = db.EmailProperty(required=True)
    mail_type = db.StringProperty()

在 Email model 中,用了一個 Reference 型別的欄位,用來表示這個欄位是 reference 到 User 這個 model,並且在 User 下建立一個集合,名為 emails,用來表示所有的 references。

所以我在建立 Entity 的時候就可以這樣做:


ericsk = User(name='Lin-Chieh Shangkuan')
ericsk.put()

mail1 = Email(user=ericsk,
              addr='foo@bar.to',
              type='example')
mail1.put()

mail2 = Email(user=ericsk,
              addr='bar@foo.to',
              type='blog')
mail2.put()

這樣建立之後,要列出一個 user 的所有 email 也很簡單:


for email in ericsk.emails:
    print 'type: %s, addr: %s' % (email.mail_type, email.addr)

因為我們在建立 Email model 時,有指定說要在 Reference 的 User 中建立一個 emails 欄位,直接對這個集合作 iteration 拿出所有的內容。這樣一個簡單的 One-to-Many 就算大功告成了。

而同時你拿到了一個 email entity 也可以使用 user 這個欄位關聯到對應的 user entity,這樣也很快就完成 One-to-One 的關聯。

Many-to-Many

Many-to-Many 常被出現在指定分類的應用中,舉例來說,我的部落格可能有很多分類(Category),每個分類下會有很多篇文章,而對一篇文章來說,它也可能屬於不同的分類下,這樣就產生了一個 Many-to-Many 的關聯。

廢話不多說,寫成 Datastore 的 Model 就可以寫成這樣:


class Post(db.Model):
    title = db.StringProperty(required=True)
    body = db.TextProperty(required=True)
    post_at = db.DateTimeProperty(auto_now_add=True)
    categories = db.ListProperty(db.Key)

class Category(db.Model):
    name = db.StringProperty(required=True)
    description = db.TextProperty()
   
    @property
    def posts(self):
        return Post.gql('WHERE categories = :1', self.key())

在 Post model 中我建立了一個 categories 欄位,它是一個 List 型別,裡面的資料都是 db.Key 這個型別,每一個放進 Datastore 的 entity 都有一個唯一的 key,所以打算用這個 list 來表示某篇文章究竟有多少分類。而 Category model 中,我們自己寫了一個 posts 的 property,裡面只是要根據 key 來取出對應的 Posts。

這樣在建立 entity 的時候就可以寫成這樣:


cat_comp = Category(name='Computer Science')
cat_comp.put()
cat_prog = Category(name='Programming')
cat_prog.put()

post1 = Post(title='Blah', body='First Post')
post1.categories.append(cat_comp.key())
post1.categories.append(cat_prog.key())
post1.put()

這樣我的這篇文章便同時有 Computer Science 分類,也在 Programming 分類下。

所以我要從 Post 取得它所有分類時,就寫成這樣:


categories = db.get(post1.categories)
for cat in categories:
    print 'Category: %s' % cat.name

而要看某個分類下有什麼文章則是寫成:


for post in cat_comp.posts.order('-post_at'):
    print 'Title: %s' % post.title

這樣是不是也很簡單就達到 Many-to-Many 的效果了呢,只要好好想一下 Datastore 中 Reference 及 List 資料型態的用法就可以做不少關聯了喔。

參考文獻:

 

歷史上的今天