You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore技术咨询:如何从指定Routine加载Workout及对应ExerciseEntries

Got it, let's walk through this properly since Firestore doesn't support SQL-style joins—we have to handle these relationships manually. Here's a step-by-step approach to load all related Workouts and their ExerciseEntries for a specified Routine, plus how to trigger that "everything has loaded" prompt once it's all done:

First, create simple models to mirror your Firestore objects. This makes managing and accessing the loaded data way cleaner. For example:

// JavaScript example
class Routine {
  constructor(id, /* add your Routine properties here */) {
    this.id = id;
    // e.g., this.name = name;
  }
}

class Workout {
  constructor(id, routineKey, exerciseEntryKeys, /* other Workout fields */) {
    this.id = id;
    this.routineKey = routineKey;
    this.exerciseEntryKeys = exerciseEntryKeys;
    this.exerciseEntries = []; // We'll populate this later
  }
}

class ExerciseEntry {
  constructor(id, /* add your ExerciseEntry properties here */) {
    this.id = id;
    // e.g., this.exerciseName = exerciseName;
  }
}

Step 2: Fetch All Workouts Linked to the Target Routine

Start by querying the Workout collection to get all documents where RoutineKey matches your target Routine's document ID.

Step 3: Batch-Fetch Associated ExerciseEntries

For each Workout, grab its ExerciseEntryKeys list. To optimize performance, collect all unique keys first and use batch queries (Firestore's whereIn method) instead of fetching each document individually—this cuts down on network calls. Note: whereIn has a 50-document limit, so split keys into batches if needed.

Step 4: Assemble the Data & Trigger the Loaded Prompt

Once all ExerciseEntries are fetched, map them back to their respective Workouts, then trigger your "everything has loaded" message.


Full Working Example (JavaScript/Web)

Using async/await for readability:

async function loadRoutineWithAllData(targetRoutineId) {
  const db = firebase.firestore();
  let allDataLoaded = false;

  try {
    // Optional: Fetch the Routine itself if you need its details
    const routineDoc = await db.collection('Routine').doc(targetRoutineId).get();
    const routine = new Routine(routineDoc.id, routineDoc.data());

    // Step 1: Fetch all Workouts for the Routine
    const workoutsQuery = await db.collection('Workout')
      .where('RoutineKey', '==', targetRoutineId)
      .get();

    const workouts = workoutsQuery.docs.map(doc => {
      const data = doc.data();
      return new Workout(doc.id, data.RoutineKey, data.ExerciseEntryKeys);
    });

    // Step 2: Collect all unique ExerciseEntry keys to avoid duplicate fetches
    const allExerciseKeys = [...new Set(workouts.flatMap(workout => workout.exerciseEntryKeys))];

    // Split into batches of 50 (Firestore's whereIn limit)
    const batches = [];
    while (allExerciseKeys.length > 0) {
      batches.push(allExerciseKeys.splice(0, 50));
    }

    // Fetch all ExerciseEntries in batches
    const exerciseEntriesMap = new Map();
    for (const batch of batches) {
      const entriesQuery = await db.collection('ExerciseEntries')
        .where(firebase.firestore.FieldPath.documentId(), 'in', batch)
        .get();

      entriesQuery.docs.forEach(doc => {
        const entry = new ExerciseEntry(doc.id, doc.data());
        exerciseEntriesMap.set(doc.id, entry);
      });
    }

    // Assign ExerciseEntries to their parent Workouts
    workouts.forEach(workout => {
      workout.exerciseEntries = workout.exerciseEntryKeys.map(key => exerciseEntriesMap.get(key));
    });

    // All data is loaded!
    allDataLoaded = true;
    if (allDataLoaded) {
      console.log("everything has loaded");
      // Use your fully populated routine/workouts/entries here
    }

    return { routine, workouts };
  } catch (error) {
    console.error('Error loading data:', error);
    // Handle errors (e.g., network issues, missing documents)
  }
}

// Call with your target Routine ID
loadRoutineWithAllData('your-routine-document-id');

Full Working Example (Android/Kotlin with Coroutines)

If you're building for Android:

data class Routine(
    val id: String,
    // Add your Routine properties here
)

data class Workout(
    val id: String,
    val routineKey: String,
    val exerciseEntryKeys: List<String>,
    var exerciseEntries: List<ExerciseEntry> = emptyList()
)

data class ExerciseEntry(
    val id: String,
    // Add your ExerciseEntry properties here
)

suspend fun loadRoutineWithAllData(targetRoutineId: String) {
    val db = Firebase.firestore
    var allDataLoaded = false

    try {
        // Optional: Fetch the Routine document
        val routineDoc = db.collection("Routine").document(targetRoutineId).get().await()
        val routine = Routine(routineDoc.id /* map other fields */)

        // Fetch all Workouts linked to the Routine
        val workoutsQuery = db.collection("Workout")
            .whereEqualTo("RoutineKey", targetRoutineId)
            .get().await()

        val workouts = workoutsQuery.documents.map { doc ->
            val data = doc.data
            Workout(
                id = doc.id,
                routineKey = data?.get("RoutineKey") as String,
                exerciseEntryKeys = data?.get("ExerciseEntryKeys") as List<String>
                // Map other Workout fields here
            )
        }

        // Collect unique ExerciseEntry keys
        val allExerciseKeys = workouts.flatMap { it.exerciseEntryKeys }.distinct()

        // Fetch ExerciseEntries in batches of 50
        val exerciseEntriesMap = mutableMapOf<String, ExerciseEntry>()
        allExerciseKeys.chunked(50).forEach { batch ->
            val entriesQuery = db.collection("ExerciseEntries")
                .whereIn(FieldPath.documentId(), batch)
                .get().await()

            entriesQuery.documents.forEach { doc ->
                val entry = ExerciseEntry(
                    id = doc.id /* map other fields */
                )
                exerciseEntriesMap[doc.id] = entry
            }
        }

        // Assign entries to Workouts
        workouts.forEach { workout ->
            workout.exerciseEntries = workout.exerciseEntryKeys.mapNotNull { key ->
                exerciseEntriesMap[key]
            }
        }

        // Trigger loaded prompt
        allDataLoaded = true
        if (allDataLoaded) {
            android.util.Log.d("Firestore", "everything has loaded")
            // Use your populated data here
        }

    } catch (e: Exception) {
        android.util.Log.e("Firestore", "Error loading data", e)
    }
}

Key Tips:

  • Batch Queries: Always use whereIn with batches for large datasets—this is far more efficient than individual document fetches.
  • Error Handling: Don't skip catching exceptions—network drops or missing documents are common, so plan for them.
  • Duplicate Prevention: Using a Set to collect unique ExerciseEntry keys avoids fetching the same document multiple times if multiple Workouts reference it.

内容的提问来源于stack exchange,提问作者Jonas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:18:23