Firestore唯一索引或唯一约束? [英] Firestore unique index or unique constraint?

查看:62
本文介绍了Firestore唯一索引或唯一约束?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Firestore中是否可以定义具有唯一约束的索引?如果没有,如何在文档字段上强制唯一性(不使用文档ID)?

Is it possible in Firestore to define an index with a unique constraint? If not, how is it possible to enforce uniqueness on a document field (without using document ID)?

推荐答案

是的,可以结合使用两个集合(Firestore规则和批处理写入)来实现.

Yes, this is possible using a combination of two collections, Firestore rules and batched writes.

https://cloud.google.com/firestore/docs/manage-data/transactions#batched-writes

一个简单的想法是,使用批处理写入,您可以将文档写入数据"集合,并同时写入单独的索引"集合,在其中您可以索引要唯一的字段的值.

The simple idea is, using a batched write, you write your document to your "data" collection and at the same write to a separate "index" collection where you index the value of the field that you want to be unique.

使用Firestore规则,然后可以确保数据"集合仅在索引集合中还存在文档字段的值的情况下才能向其写入文档,反之亦然,只能写入索引集合索引中的值是否与数据集合中的值匹配.

Using the Firestore rules, you can then ensure that the "data" collection can only have a document written to it if the document field's value also exists in the index collection and, vice versa, that the index collection can only be written to if value in the index matches what's in the data collection.

示例

假设我们有一个User集合,并且我们想确保username字段是唯一的.

Let's say that we have a User collection and we want to ensure that the username field is unique.

我们的User集合将仅包含username

/User/{id}
{
  username: String 
}

我们的Index集合将在路径中包含用户名,并在value属性中包含被索引的User的ID.

Our Index collection will contain the username in the path and a value property that contains the id of the User that is indexed.

/Index/User/username/{username}
{
  value: User.id
}

要创建我们的User,我们使用批处理写入来同时创建User文档和Index文档.

To create our User we use a batch write to create both the User document and the Index document at the same time.

const firebaseApp = ...construct your firebase app

const createUser = async (username) => {
  const database = firebaseApp.firestore()
  const batch = database.batch()

  const Collection = database.collection('User')
  const ref = Collection.doc()
  batch.set(ref, {
    username
  })

  const Index = database.collection('Index')
  const indexRef = Index.doc(`User/username/${username}`)
  batch.set(indexRef, {
    value: ref.id
  })

  await batch.commit()
}

要更新User的用户名,我们使用批量写入来更新User文档,删除以前的Index文档并同时创建一个新的Index文档.

To update our User's username we use a batch write to update the User document, delete the previous Index document and create a new Index document all at the same time.

const firebaseApp = ...construct your firebase app

const updateUser = async (id, username) => {
  const database = firebaseApp.firestore()
  const batch = database.batch()

  const Collection = database.collection('User')
  const ref = Collection.doc(id)
  const refDoc = await ref.get()
  const prevData = refDoc.data()
  batch.update(ref, {
    username
  })

  const Index = database.collection('Index')
  const prevIndexRef = Index.doc(`User/username/${prevData.username}`)
  const indexRef = Index.doc(`User/username/${username}`)
  batch.delete(prevIndexRef)
  batch.set(indexRef, {
    value: ref.id
  })

  await batch.commit()
}

要删除User,我们使用批量写入操作同时删除User文档和Index文档.

To delete a User we use a batch write to delete both the User document and the Index document at the same time.

const firebaseApp = ...construct your firebase app

const deleteUser = async (id) => {
  const database = firebaseApp.firestore()
  const batch = database.batch()

  const Collection = database.collection('User')
  const ref = Collection.doc(id)
  const refDoc = await ref.get()
  const prevData = refDoc.data()
  batch.delete(ref)


  const Index = database.collection('Index')
  const indexRef = Index.doc(`User/username/${prevData.username}`)
  batch.delete(indexRef)

  await batch.commit()
}

然后,我们设置Firestore规则,以便仅当用户名尚未为其他User编制索引时,才允许创建User.仅当Index的用户名不存在时,才可以更新User的用户名,并且如果同时删除Index,则只能删除User.如果已经存在具有相同usernameUser,则创建和更新将失败,并显示缺少权限或权限不足"错误.

We then setup our Firestore rules so that they only allow a User to be created if the username is not already indexed for a different User. A User's username can only be updated if an Index does not already exist for the username and a User can only be deleted if the Index is deleted as well. Create and update will fail with a "Missing or insufficient permissions" error if a User with the same username already exists.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {


    // Index collection helper methods

    function getIndexAfter(path) {
      return getAfter(/databases/$(database)/documents/Index/$(path))
    }

    function getIndexBefore(path) {
      return get(/databases/$(database)/documents/Index/$(path))
    }

    function indexExistsAfter(path) {
      return existsAfter(/databases/$(database)/documents/Index/$(path))
    }

    function indexExistsBefore(path) {
      return exists(/databases/$(database)/documents/Index/$(path))
    }


    // User collection helper methods

    function getUserAfter(id) {
      return getAfter(/databases/$(database)/documents/User/$(id))
    }

    function getUserBefore(id) {
      return get(/databases/$(database)/documents/User/$(id))
    }

    function userExistsAfter(id) {
      return existsAfter(/databases/$(database)/documents/User/$(id))
    }


    match /User/{id} {
      allow read: true;

      allow create: if
        getIndexAfter(/User/username/$(getUserAfter(id).data.username)).data.value == id;

      allow update: if
        getIndexAfter(/User/username/$(getUserAfter(id).data.username)).data.value == id &&
        !indexExistsBefore(/User/username/$(getUserAfter(id).data.username));

      allow delete: if
        !indexExistsAfter(/User/username/$(getUserBefore(id).data.username));
    }

    match /Index/User/username/{username} {
      allow read: if true;

      allow create: if
        getUserAfter(getIndexAfter(/User/username/$(username)).data.value).data.username == username;

      allow delete: if 
        !userExistsAfter(getIndexBefore(/User/username/$(username)).data.value) || 
        getUserAfter(getIndexBefore(/User/username/$(username)).data.value).data.username != username;
    }
  }
}

这篇关于Firestore唯一索引或唯一约束?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆