保存数据

准备工作

在使用 Realtime Database 之前,您需要先完成以下步骤:

  • 注册 Unity 项目并将其配置为使用 Firebase。

    • 如果您的 Unity 项目已在使用 Firebase,那么该项目已经注册并已配置为使用 Firebase。

    • 如果您没有 Unity 项目,则可以下载示例应用

  • Firebase Unity SDK(具体而言是 FirebaseDatabase.unitypackage)添加到您的 Unity 项目中。

请注意,将 Firebase 添加到 Unity 项目需要在 Firebase 控制台中和打开的 Unity 项目中执行若干任务(例如,从控制台下载 Firebase 配置文件,然后将配置文件移动到 Unity 项目中)。

保存数据

有五种方法可以将数据写入 Firebase Realtime Database:

方法 常见用途
SetValueAsync() 将数据写入到指定路径(例如 users/<user-id>/<username>)或替换指定路径下的数据
SetRawJsonValueAsync() 写入或替换为原始 Json 数据,例如 users/<user-id>/<username>
Push() 添加到数据列表。每次您调用 Push() 时,Firebase 都会生成一个唯一的键(也可以用作唯一标识符),例如 user-scores/<user-id>/<unique-score-id>
UpdateChildrenAsync() 更新指定路径中的部分键,而不替换所有数据。
RunTransaction() 更新可能被并发更新损坏的复杂数据。

获取 DatabaseReference

要将数据写入到数据库中,您需要一个 DatabaseReference 实例:

using Firebase;
using Firebase.Database;

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

写入、更新或删除引用中的数据

基本写入操作

对于基本写入操作,您可以使用 SetValueAsync() 将数据保存至指定引用,替换该路径中的任何现有数据。您可以使用此方法传递与可用 JSON 类型相对应的类型,如下所示:

  • string
  • long
  • double
  • bool
  • Dictionary<string, Object>
  • List<Object>

如果您使用 C# 类型的对象,可以使用内置的 JsonUtility.ToJson() 将对象转换为原始 Json 并调用 SetRawJsonValueAsync()。例如,您可能有一个如下所示的用户类:

public class User {
    public string username;
    public string email;

    public User() {
    }

    public User(string username, string email) {
        this.username = username;
        this.email = email;
    }
}

您可以使用 SetRawJsonValueAsync() 添加用户,如下所示:

private void writeNewUser(string userId, string name, string email) {
    User user = new User(name, email);
    string json = JsonUtility.ToJson(user);

    mDatabaseRef.Child("users").Child(userId).SetRawJsonValueAsync(json);
}

以这种方式使用 SetValueAsync()SetRawJsonValueAsync() 将重写指定位置的数据,包括所有子节点。但是,您仍可在不重写整个对象的情况下更新子节点。如果要允许用户更新其个人资料,您可按照如下所示更新用户名:

mDatabaseRef.Child("users").Child(userId).Child("username").SetValueAsync(name);

向数据列表附加数据

使用 Push() 方法可将数据附加到多用户应用的列表中。每次将新的子项添加到指定的 Firebase 引用时,Push() 方法均会生成一个唯一键。利用这些为列表中的每个新元素自动生成的键,多个客户端就可以同时向同一位置添加子项,而不会引起写入冲突。Push() 生成的唯一键是以时间戳为基础的,因此列表项会自动按时间顺序排列。

您可以使用 Push() 方法所返回新数据的引用,获取为子项自动生成的键的值或为子项设置数据。对 Push() 引用调用 Key 将返回自动生成的键的值。

更新特定字段

如需同时向一个节点的某些特定子节点写入数据,而又不覆盖其他子节点,请使用 UpdateChildrenAsync() 方法。

调用 UpdateChildrenAsync() 时,您可以为键指定路径来更新较低层级的子节点值。如果为了更好地实现扩缩而将数据存储在多个位置,则可使用数据扇出更新这些数据的所有实例。例如,游戏可能具有 LeaderboardEntry 类,如下所示:

public class LeaderboardEntry {
    public string uid;
    public int score = 0;

    public LeaderboardEntry() {
    }

    public LeaderboardEntry(string uid, int score) {
        this.uid = uid;
        this.score = score;
    }

    public Dictionary<string, Object> ToDictionary() {
        Dictionary<string, Object> result = new Dictionary<string, Object>();
        result["uid"] = uid;
        result["score"] = score;

        return result;
    }
}

为了创建 LeaderboardEntry 并同时将其更新到最近的分数 Feed 和用户自己的分数列表中,游戏会使用如下代码:

private void WriteNewScore(string userId, int score) {
    // Create new entry at /user-scores/$userid/$scoreid and at
    // /leaderboard/$scoreid simultaneously
    string key = mDatabase.Child("scores").Push().Key;
    LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
    Dictionary<string, Object> entryValues = entry.ToDictionary();

    Dictionary<string, Object> childUpdates = new Dictionary<string, Object>();
    childUpdates["/scores/" + key] = entryValues;
    childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

    mDatabase.UpdateChildrenAsync(childUpdates);
}

此示例使用 Push() 在包含所有用户条目的节点 (/scores/$key) 中创建了一个条目,同时使用 Key 检索相应键。接着,该键可用于在用户的分数位置 (/user-scores/$userid/$key) 创建第二个条目。

通过使用这些路径,只需调用 UpdateChildrenAsync() 一次即可同时更新 JSON 树中的多个位置,此示例就使用了这种方式在两个位置同时创建新条目。通过这种方式同时进行更新属于原子操作:所有更新要么全部成功,要么全部失败。

删除数据

删除数据最简单的方法是对数据所在位置的引用调用 RemoveValue()

您也可以指定 null 作为另一个写入操作(如 SetValueAsync()UpdateChildrenAsync())的值来进行删除。您可以将此方法与 UpdateChildrenAsync() 结合使用,在一次 API 调用中删除多个子节点。

了解您的数据的提交时间。

如需了解将您的数据提交到 Firebase Realtime Database 服务器的时间,可以添加一个接续参数。SetValueAsync()UpdateChildrenAsync() 都会返回一个 Task,让您了解操作何时完成。如果调用因为某种原因没有成功,Task 的 IsFaulted 将为 true,并使用 Exception 属性指出失败的原因。

将数据另存为事务

处理可能因并发修改而损坏的数据(例如,增量计数器)时,您可以使用事务操作。您需要为此操作提供一个 Func 函数。此更新函数 Func 会将数据的当前状态视为参数,并返回您要写入的新目标状态。如果另一个客户端在您成功写入新值前向该位置写入数据,则系统会使用新的当前值再次调用更新函数,然后重新尝试执行写入操作。

例如,在一个游戏中,您可以允许用户更新排行榜中的前五个最高得分:

private void AddScoreToLeaders(string email, 
                               long score,
                               DatabaseReference leaderBoardRef) {

    leaderBoardRef.RunTransaction(mutableData => {
      List<object> leaders = mutableData.Value as List<object>

      if (leaders == null) {
        leaders = new List<object>();
      } else if (mutableData.ChildrenCount >= MaxScores) {
        long minScore = long.MaxValue;
        object minVal = null;
        foreach (var child in leaders) {
          if (!(child is Dictionary<string, object>)) continue;
          long childScore = (long)
                      ((Dictionary<string, object>)child)["score"];
          if (childScore < minScore) {
            minScore = childScore;
            minVal = child;
          }
        }
        if (minScore > score) {
          // The new score is lower than the existing 5 scores, abort.
          return TransactionResult.Abort();
        }

        // Remove the lowest score.
        leaders.Remove(minVal);
      }

      // Add the new high score.
      Dictionary<string, object> newScoreMap =
                       new Dictionary<string, object>();
      newScoreMap["score"] = score;
      newScoreMap["email"] = email;
      leaders.Add(newScoreMap);
      mutableData.Value = leaders;
      return TransactionResult.Success(mutableData);
    });
}

如果多个用户同时记录得分或客户端存在过时数据,使用事务可防止排行榜出错。如果事务遭到拒绝,服务器会将当前值返回到客户端,然后客户端会使用更新后的值再次运行事务。此过程将反复进行,直到事务被接受或尝试次数过多为止。

离线写入数据

如果客户端的网络连接中断,您的应用将继续正常运行。

对于所有有效数据,连接到 Firebase 数据库的每个客户端均维护着各自的内部版本。写入数据时,首先会写入这一本地版本。然后,Firebase 客户端会尽可能将这些数据与远程数据库服务器以及其他客户端同步。

因此,对数据库执行的所有写入操作会立即触发本地事件,然后数据才会写入服务器。这意味着应用仍将保持随时响应的状态,无论网络延迟或连接状况如何。

连接重新建立之后,您的应用将收到一系列相应的事件,以便客户端与当前服务器状态进行同步,而不必编写任何自定义代码。

后续步骤