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:
Step 1: Define Data Models (Optional but Highly Recommended)
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
whereInwith 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

