diff --git a/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj b/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj
index f06ba4e7017253f414ff4ef6b462b93bc2a07096..b8c181dee6901108edb0ed47ffc02d29f0de28b7 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/test_util.clj
@@ -9,9 +9,11 @@
    [metabase.models :refer [Card Collection Dashboard DashboardCard DashboardCardSeries Database
                             Field NativeQuerySnippet Pulse PulseCard Segment Table User]]
    [metabase.models.collection :as collection]
+   [metabase.models.serialization :as serdes]
    [metabase.shared.models.visualization-settings :as mb.viz]
    [metabase.test :as mt]
    [metabase.test.data :as data]
+   [metabase.util :as u]
    [metabase.util.files :as u.files]
    [toucan2.connection :as t2.conn]
    [toucan2.core :as t2]
@@ -526,3 +528,11 @@
 
 ;; Don't memoize as IDs change in each `with-world` context
 (alter-var-root #'names/path->context (fn [_] #'names/path->context*))
+
+(defn extract-one [model-name where]
+  (let [where (cond
+                (nil? where)    true
+                (number? where) [:= :id where]
+                (string? where) [:= :entity_id where]
+                :else           where)]
+    (u/rfirst (serdes/extract-all model-name {:where where}))))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj
index e9a34efdc4a67d8165fcc7f86d48321ac0127cb3..d597e125ecde5377a997e1ff506a6c432ef5c5b3 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj
@@ -17,7 +17,6 @@
                             Field
                             ParameterCard
                             Table]]
-   [metabase.models.action :as action]
    [metabase.models.serialization :as serdes]
    [metabase.models.setting :as setting]
    [metabase.test :as mt]
@@ -113,87 +112,87 @@
         (ts/with-db source-db
           (testing "insert"
             (test-gen/insert!
-              {;; Actions are special case where there is a 1:1 relationship between an action and an action subtype (query, implicit, or http)
-               ;; We generate 10 actions for each subtype, and 10 of each subtype.
-               ;; actions 0-9 are query actions, 10-19 are implicit actions, and 20-29 are http actions.
-               :action                  (apply concat
-                                               (for [type [:query :implicit :http]]
-                                                 (many-random-fks 10
-                                                                  {:spec-gen {:type type}}
-                                                                  {:model_id   [:sm 10]
-                                                                   :creator_id [:u 10]})))
-               :query-action            (map-indexed
-                                         (fn [idx x]
-                                           (assoc-in x [1 :refs :action_id] (keyword (str "action" idx))))
-                                         (many-random-fks 10 {} {:database_id [:db 10]}))
-               :implicit-action         (map-indexed
-                                         (fn [idx x]
-                                           (update-in x [1 :refs]
-                                                      (fn [refs]
-                                                        (assoc refs :action_id (keyword (str "action" (+ 10 idx)))))))
-                                         (many-random-fks 10 {} {}))
-               :http-action             (map-indexed
-                                         (fn [idx x]
-                                           (update-in x [1 :refs]
-                                                      (fn [refs]
-                                                        (assoc refs :action_id (keyword (str "action" (+ 20 idx)))))))
-                                         (many-random-fks 10 {} {}))
-               :collection              [[100 {:refs     {:personal_owner_id ::rs/omit}}]
-                                         [10  {:refs     {:personal_owner_id ::rs/omit}
-                                               :spec-gen {:namespace :snippets}}]]
-               :database                [[10]]
-               ;; Tables are special - we define table 0-9 under db0, 10-19 under db1, etc. The :card spec below
-               ;; depends on this relationship.
-               :table                   (into [] (for [db [:db0 :db1 :db2 :db3 :db4 :db5 :db6 :db7 :db8 :db9]]
-                                                   [10 {:refs {:db_id db}}]))
-               :field                   (many-random-fks 1000 {} {:table_id [:t 100]})
-               :core-user               [[100]]
-               :card                    (mapv #(update-in % [1 :refs] table->db)
-                                              (many-random-fks
-                                               100
-                                               {:spec-gen {:dataset_query {:database 1
-                                                                           :type     :native
-                                                                           :native   {:query "SELECT * FROM whatever;"}}
-                                                           :type          :model}}
-                                               {:table_id      [:t    100]
-                                                :collection_id [:coll 100]
-                                                :creator_id    [:u    10]}))
-               ;; Simple model is primary used for actions.
-               ;; We can't use :card for actions because implicit actions require the model's query to contain
-               ;; nothing but a source table
-               :simple-model            (mapv #(update-in % [1 :refs] table->db)
-                                               (many-random-fks
-                                                10
-                                                {:spec-gen {:dataset_query {:database 1
-                                                                            :query {:source-table 3}
-                                                                            :type :query}
-                                                            :type          :model}}
-                                                {:table_id      [:t    10]
-                                                 :collection_id [:coll 10]
-                                                 :creator_id    [:u    10]}))
-               :dashboard               (concat (many-random-fks 100 {} {:collection_id [:coll 100]
-                                                                         :creator_id    [:u    10]})
-                                                ;; create some root collection dashboards
-                                                (many-random-fks 50 {} {:creator_id    [:u 10]}))
-               :dashboard-card          (many-random-fks 300 {} {:card_id      [:c 100]
-                                                                 :dashboard_id [:d 100]})
-               :dimension               (vec (concat
-                                               ;; 20 with both IDs set
-                                              (many-random-fks 20 {}
-                                                               {:field_id                [:field 1000]
-                                                                :human_readable_field_id [:field 1000]})
-                                               ;; 20 with just :field_id
-                                              (many-random-fks 20 {:refs {:human_readable_field_id ::rs/omit}}
-                                                               {:field_id [:field 1000]})))
-               :segment                 (many-random-fks 30 {:spec-gen {:definition {:filter [:!= [:field 60 nil] 50],
-                                                                                     :source-table 4}}}
-                                                         {:table_id   [:t 100]
-                                                          :creator_id [:u 10]})
-               :native-query-snippet    (many-random-fks 10 {} {:creator_id    [:u 10]
-                                                                :collection_id [:coll 10 100]})
-               :timeline                (many-random-fks 10 {} {:creator_id    [:u 10]
-                                                                :collection_id [:coll 100]})
-               :timeline-event          (many-random-fks 90 {} {:timeline_id   [:timeline 10]})}))
+             { ;; Actions are special case where there is a 1:1 relationship between an action and an action subtype (query, implicit, or http)
+              ;; We generate 10 actions for each subtype, and 10 of each subtype.
+              ;; actions 0-9 are query actions, 10-19 are implicit actions, and 20-29 are http actions.
+              :action                  (apply concat
+                                              (for [type [:query :implicit :http]]
+                                                (many-random-fks 10
+                                                                 {:spec-gen {:type type}}
+                                                                 {:model_id   [:sm 10]
+                                                                  :creator_id [:u 10]})))
+              :query-action            (map-indexed
+                                        (fn [idx x]
+                                          (assoc-in x [1 :refs :action_id] (keyword (str "action" idx))))
+                                        (many-random-fks 10 {} {:database_id [:db 10]}))
+              :implicit-action         (map-indexed
+                                        (fn [idx x]
+                                          (update-in x [1 :refs]
+                                                     (fn [refs]
+                                                       (assoc refs :action_id (keyword (str "action" (+ 10 idx)))))))
+                                        (many-random-fks 10 {} {}))
+              :http-action             (map-indexed
+                                        (fn [idx x]
+                                          (update-in x [1 :refs]
+                                                     (fn [refs]
+                                                       (assoc refs :action_id (keyword (str "action" (+ 20 idx)))))))
+                                        (many-random-fks 10 {} {}))
+              :collection              [[100 {:refs     {:personal_owner_id ::rs/omit}}]
+                                        [10  {:refs     {:personal_owner_id ::rs/omit}
+                                              :spec-gen {:namespace :snippets}}]]
+              :database                [[10]]
+              ;; Tables are special - we define table 0-9 under db0, 10-19 under db1, etc. The :card spec below
+              ;; depends on this relationship.
+              :table                   (into [] (for [db [:db0 :db1 :db2 :db3 :db4 :db5 :db6 :db7 :db8 :db9]]
+                                                  [10 {:refs {:db_id db}}]))
+              :field                   (many-random-fks 1000 {} {:table_id [:t 100]})
+              :core-user               [[100]]
+              :card                    (mapv #(update-in % [1 :refs] table->db)
+                                             (many-random-fks
+                                              100
+                                              {:spec-gen {:dataset_query {:database 1
+                                                                          :type     :native
+                                                                          :native   {:query "SELECT * FROM whatever;"}}
+                                                          :type          :model}}
+                                              {:table_id      [:t    100]
+                                               :collection_id [:coll 100]
+                                               :creator_id    [:u    10]}))
+              ;; Simple model is primary used for actions.
+              ;; We can't use :card for actions because implicit actions require the model's query to contain
+              ;; nothing but a source table
+              :simple-model            (mapv #(update-in % [1 :refs] table->db)
+                                             (many-random-fks
+                                              10
+                                              {:spec-gen {:dataset_query {:database 1
+                                                                          :query {:source-table 3}
+                                                                          :type :query}
+                                                          :type          :model}}
+                                              {:table_id      [:t    10]
+                                               :collection_id [:coll 10]
+                                               :creator_id    [:u    10]}))
+              :dashboard               (concat (many-random-fks 100 {} {:collection_id [:coll 100]
+                                                                        :creator_id    [:u    10]})
+                                               ;; create some root collection dashboards
+                                               (many-random-fks 50 {} {:creator_id    [:u 10]}))
+              :dashboard-card          (many-random-fks 300 {} {:card_id      [:c 100]
+                                                                :dashboard_id [:d 100]})
+              :dimension               (vec (concat
+                                             ;; 20 with both IDs set
+                                             (many-random-fks 20 {}
+                                                              {:field_id                [:field 1000]
+                                                               :human_readable_field_id [:field 1000]})
+                                             ;; 20 with just :field_id
+                                             (many-random-fks 20 {:refs {:human_readable_field_id ::rs/omit}}
+                                                              {:field_id [:field 1000]})))
+              :segment                 (many-random-fks 30 {:spec-gen {:definition {:filter [:!= [:field 60 nil] 50],
+                                                                                    :source-table 4}}}
+                                                        {:table_id   [:t 100]
+                                                         :creator_id [:u 10]})
+              :native-query-snippet    (many-random-fks 10 {} {:creator_id    [:u 10]
+                                                               :collection_id [:coll 10 100]})
+              :timeline                (many-random-fks 10 {} {:creator_id    [:u 10]
+                                                               :collection_id [:coll 100]})
+              :timeline-event          (many-random-fks 90 {} {:timeline_id   [:timeline 10]})}))
 
           (is (= 101 (count (t2/select-fn-set :email 'User)))) ; +1 for the internal user
 
@@ -291,91 +290,79 @@
               (testing "for Actions"
                 (doseq [{:keys [entity_id] :as coll} (get @entities "Action")]
                   (is (= (clean-entity coll)
-                         (->> (t2/select-one 'Action :entity_id entity_id)
-                              (@#'action/hydrate-subtype)
-                              (serdes/extract-one "Action" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Action" entity_id)
+                             clean-entity)))))
 
               (testing "for Collections"
                 (doseq [{:keys [entity_id] :as coll} (get @entities "Collection")]
                   (is (= (clean-entity coll)
-                         (->> (t2/select-one 'Collection :entity_id entity_id)
-                              (serdes/extract-one "Collection" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Collection" entity_id)
+                             clean-entity)))))
 
               (testing "for Databases"
                 (doseq [{:keys [name] :as db} (get @entities "Database")]
                   (is (= (assoc (clean-entity db) :initial_sync_status "complete")
-                         (->> (t2/select-one 'Database :name name)
-                              (serdes/extract-one "Database" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Database" [:= :name name])
+                             clean-entity)))))
 
               (testing "for Tables"
                 (doseq [{:keys [db_id name] :as coll} (get @entities "Table")]
                   (is (= (clean-entity coll)
-                         (->> (t2/select-one-fn :id 'Database :name db_id)
-                              (t2/select-one 'Table :name name :db_id)
-                              (serdes/extract-one "Table" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Table" [:and
+                                                      [:= :name name]
+                                                      [:= :db_id (t2/select-one-pk 'Database :name db_id)]])
+                             clean-entity)))))
 
               (testing "for Fields"
                 (doseq [{[db schema table] :table_id name :name :as coll} (get @entities "Field")]
                   (is (nil? schema))
-                  (is (= (clean-entity coll)
-                         (->> (t2/select-one-fn :id 'Database :name db)
-                              (t2/select-one-fn :id 'Table :schema schema :name table :db_id)
-                              (t2/select-one 'Field :name name :table_id)
-                              (serdes/extract-one "Field" {})
-                              clean-entity)))))
+                  (let [db (t2/select-one-pk 'Database :name db)
+                        table (t2/select-one-fn :id 'Table :schema schema :name table :db_id db)]
+                    (is (= (clean-entity coll)
+                           (-> (ts/extract-one "Field" [:and [:= :name name] [:= :table_id table]])
+                               clean-entity))))))
 
               (testing "for cards"
                 (doseq [{:keys [entity_id] :as card} (get @entities "Card")]
                   (is (= (clean-entity card)
-                         (->> (t2/select-one 'Card :entity_id entity_id)
-                              (serdes/extract-one "Card" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Card" entity_id)
+                             clean-entity)))))
 
               (testing "for dashboards"
                 (doseq [{:keys [entity_id] :as dash} (get @entities "Dashboard")]
                   (is (= (clean-entity dash)
-                         (->> (t2/select-one 'Dashboard :entity_id entity_id)
-                              (serdes/extract-one "Dashboard" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Dashboard" entity_id)
+                             clean-entity)))))
 
               (testing "for dashboard cards"
                 (doseq [{:keys [entity_id] :as dashcard} (get @entities "DashboardCard")]
                   (is (= (clean-entity dashcard)
-                         (->> (t2/select-one 'DashboardCard :entity_id entity_id)
-                              (serdes/extract-one "DashboardCard" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "DashboardCard" entity_id)
+                             clean-entity)))))
 
               (testing "for dimensions"
                 (doseq [{:keys [entity_id] :as dim} (get @entities "Dimension")]
                   (is (= (clean-entity dim)
-                         (->> (t2/select-one 'Dimension :entity_id entity_id)
-                              (serdes/extract-one "Dimension" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Dimension" entity_id)
+                             clean-entity)))))
 
               (testing "for segments"
                 (doseq [{:keys [entity_id] :as segment} (get @entities "Segment")]
                   (is (= (clean-entity segment)
-                         (->> (t2/select-one 'Segment :entity_id entity_id)
-                              (serdes/extract-one "Segment" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Segment" entity_id)
+                             clean-entity)))))
 
               (testing "for native query snippets"
                 (doseq [{:keys [entity_id] :as snippet} (get @entities "NativeQuerySnippet")]
                   (is (= (clean-entity snippet)
-                         (->> (t2/select-one 'NativeQuerySnippet :entity_id entity_id)
-                              (serdes/extract-one "NativeQuerySnippet" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "NativeQuerySnippet" entity_id)
+                             clean-entity)))))
 
               (testing "for timelines and events"
                 (doseq [{:keys [entity_id] :as timeline} (get @entities "Timeline")]
                   (is (= (clean-entity timeline)
-                         (->> (t2/select-one 'Timeline :entity_id entity_id)
-                              (serdes/extract-one "Timeline" {})
-                              clean-entity)))))
+                         (-> (ts/extract-one "Timeline" entity_id)
+                             clean-entity)))))
 
               (testing "for settings"
                 (let [settings (get @entities "Setting")]
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
index 9e9afab15f893f39e78ac2ce656f0e85f40ad932..a975cc8a17794e0ce835da687cea4ca8f1fbe6f4 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
@@ -455,7 +455,7 @@
                    (set (serdes/dependencies ser)))))))
 
       (testing "Dashboards include their Dashcards"
-        (let [ser (u/rfirst (serdes/extract-all "Dashboard" {:where [:= :id other-dash-id]}))]
+        (let [ser (ts/extract-one "Dashboard" other-dash-id)]
           (is (=? {:serdes/meta            [{:model "Dashboard" :id other-dash :label "dave_s_dash"}]
                    :entity_id              other-dash
                    :dashcards
@@ -496,7 +496,7 @@
                    (set (serdes/dependencies ser)))))))
 
       (testing "Dashboards with parameters where the source is a card"
-        (let [ser (u/rfirst (serdes/extract-all "Dashboard" {:where [:= :id param-dash-id]}))]
+        (let [ser (ts/extract-one "Dashboard" param-dash-id)]
           (is (=? {:parameters
                    [{:id                   "abc"
                      :name                 "CATEGORY"
@@ -515,7 +515,7 @@
                  (set (serdes/dependencies ser))))))
 
       (testing "Cards with parameters where the source is a card"
-        (let [ser (u/rfirst (serdes/extract-all "Dashboard" {:where [:= :id param-dash-id]}))]
+        (let [ser (ts/extract-one "Dashboard" param-dash-id)]
           (is (=? {:parameters
                    [{:id                   "abc"
                      :name                 "CATEGORY"
@@ -579,7 +579,7 @@
          :model/DashboardCardSeries _ {:card_id c2-id, :dashboardcard_id dc1-id, :position 0}]
         (testing "Inlined dashcards include their series' card entity IDs"
           (let [ser (t2/with-call-count [q]
-                      (u/prog1 (u/rfirst (serdes/extract-all "Dashboard" {:where [:= :id dash-id]}))
+                      (u/prog1 (ts/extract-one "Dashboard" dash-id)
                         (is (< (q) 13))))]
             (is (=? {:entity_id dash-eid
                      :dashcards [{:entity_id dc1-eid
@@ -626,7 +626,7 @@
                                                              :field_id fk-id
                                                              :human_readable_field_id cust-name}]
       (testing "dimensions without foreign keys are inlined into their Fields\n"
-        (let [ser (u/rfirst (serdes/extract-all "Field" {:where [:= :id email-id]}))]
+        (let [ser (ts/extract-one "Field" email-id)]
           (is (malli= [:map
                        [:serdes/meta [:= [{:model "Database", :id "My Database"}
                                           {:model "Table", :id "Schemaless Table"}
@@ -648,7 +648,7 @@
                    (set (serdes/dependencies ser)))))))
 
       (testing "foreign key dimensions are inlined into their Fields"
-        (let [ser (u/rfirst (serdes/extract-all "Field" {:where [:= :id fk-id]}))]
+        (let [ser (ts/extract-one "Field" fk-id)]
           (is (malli= [:map
                        [:serdes/meta        [:= [{:model "Database" :id "My Database"}
                                                  {:model "Schema" :id "PUBLIC"}
@@ -1707,3 +1707,65 @@
                                   :no-data-model true})]
         (is (= #{(:entity_id c)}
                (by-model "Collection" ser)))))))
+
+(deftest extract-nested-test
+  (testing "extract-nested working"
+    (mt/with-temp [:model/Dashboard           d   {:name "Top Dash"}
+                   :model/Card                c1  {:name "Some Card"}
+                   :model/Card                c2  {:name "Some Inner Card"}
+                   :model/DashboardCard       dc1 {:dashboard_id (:id d) :card_id (:id c1)}
+                   :model/DashboardCardSeries s   {:dashboardcard_id (:id dc1) :card_id (:id c2)}]
+      (let [spec (serdes/make-spec "DashboardCard" nil)]
+        (is (= {(:id dc1) [s]}
+               (#'serdes/transform->nested (-> spec :transform :series) [dc1])))
+        (is (=? (assoc dc1 :series [s])
+                (u/rfirst (serdes/extract-query "DashboardCard" {:where [:= :id (:id dc1)]})))))
+      (let [spec (serdes/make-spec "Dashboard" nil)]
+        (is (= {(:id d) [(assoc dc1 :series [s])]}
+               (#'serdes/transform->nested (-> spec :transform :dashcards) [d])))
+        (is (=? (assoc d
+                       :dashcards [(assoc dc1 :series [s])]
+                       :tabs nil)
+                (u/rfirst (serdes/extract-query "Dashboard" {:where [:= :id (:id d)]}))))))))
+
+(deftest extract-nested-efficient-test
+  (testing "extract-nested is efficient"
+    (mt/with-temp [:model/Dashboard           d1  {:name "Top Dash 1"}
+                   :model/Dashboard           d2  {:name "Top Dash 2"}
+                   :model/Card                c1  {:name "Some Card"}
+                   :model/Card                c2  {:name "Some Inner Card"}
+                   :model/Card                c3  {:name "Card for Dash 2"}
+                   :model/DashboardCard       dc1 {:dashboard_id (:id d1) :card_id (:id c1)}
+                   :model/DashboardCard       dc2 {:dashboard_id (:id d2) :card_id (:id c2)}
+                   :model/DashboardCard       dc3 {:dashboard_id (:id d2) :card_id (:id c3)}
+                   :model/DashboardCardSeries s   {:dashboardcard_id (:id dc1) :card_id (:id c2)}]
+      (t2/with-call-count [qc]
+        (is (=? [(assoc d1
+                        :dashcards [(assoc dc1 :series [s])]
+                        :tabs nil)
+                 (assoc d2
+                        :dashcards [(assoc dc2 :series nil)
+                                    (assoc dc3 :series nil)]
+                        :tabs nil)]
+                (into [] (serdes/extract-query "Dashboard" {:where [:in :id [(:id d1) (:id d2)]]}))))
+        ;; 1 per dashboard/dashcard/series/tabs
+        (is (= 4 (qc)))))))
+
+(deftest extract-nested-partitioned-test
+  (testing "extract-nested will partition stuff by 100s"
+    (mt/with-empty-h2-app-db
+      (let [d   (ts/create! :model/Dashboard {:name "Dash"})
+            c1  (ts/create! :model/Card {:name "Card"})
+            dcs (vec (for [_ (range 7)]
+                       (ts/create! :model/DashboardCard {:dashboard_id (:id d)
+                                                         :card_id      (:id c1)})))]
+        (t2/with-call-count [qc]
+          (is (=? [(assoc d :dashcards dcs)]
+                  (into [] (serdes/extract-query "Dashboard" {:batch-limit 5
+                                                              :where [:= :id (:id d)]}))))
+          ;; query count breakdown:
+          ;; - 1 for dashboard
+          ;; - 1 for tabs, there are none
+          ;; - 1 for dashcards, there are 7
+          ;; - 2 for series (7 dashcards / 5 -> 2 batches)
+          (is (= 5 (qc))))))))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj
index b71df33fd23ac4fce256c83b7ad11d000c7956c1..96757ea7b94318f39954465ac5bb06ba80ea9657 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj
@@ -10,7 +10,6 @@
    [metabase-enterprise.serialization.v2.storage :as storage]
    [metabase.models :refer [Card Collection Dashboard DashboardCard Database Field FieldValues NativeQuerySnippet
                             Table]]
-   [metabase.models.serialization :as serdes]
    [metabase.test :as mt]
    [metabase.util.date-2 :as u.date]
    [metabase.util.yaml :as yaml]
@@ -145,8 +144,8 @@
                 "Slashes in directory names get escaped"))
 
           (testing "the Field was properly exported"
-            (is (= (-> (into {} (serdes/extract-one "Field" {} (t2/select-one 'Field :id (:id website))))
-                       (update :created_at      u.date/format))
+            (is (= (-> (ts/extract-one "Field" (:id website))
+                       (update :created_at u.date/format))
                    (-> (yaml/from-file (io/file dump-dir
                                                 "databases" "My Company Data"
                                                 "tables"    "Customers"
diff --git a/src/metabase/models/action.clj b/src/metabase/models/action.clj
index a86c57cb5193ff0ad2892110ea6391c35b26c859..38637824117b1aeb975d5007c2e938a8b5b82613 100644
--- a/src/metabase/models/action.clj
+++ b/src/metabase/models/action.clj
@@ -352,9 +352,9 @@
 
 ;;; ------------------------------------------------ Serialization ---------------------------------------------------
 
-(defmethod serdes/extract-query "Action" [_model _opts]
+(defmethod serdes/extract-query "Action" [_model opts]
   (eduction (map hydrate-subtype)
-            (t2/reducible-select Action)))
+            (t2/reducible-select Action {:where (or (:where opts) true)})))
 
 (defmethod serdes/hash-fields :model/Action [_action]
   [:name (serdes/hydrated-hash :model) :created_at])
diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj
index bc257d3425d0ea26e2355cc920dfa580d45bd4ed..9b8ee57293ab1931f696597bbb992c287c66b895 100644
--- a/src/metabase/models/card.clj
+++ b/src/metabase/models/card.clj
@@ -813,9 +813,6 @@
 
 ;;; ------------------------------------------------- Serialization --------------------------------------------------
 
-(defmethod serdes/extract-query "Card" [_ opts]
-  (serdes/extract-query-collections Card opts))
-
 (defn- export-result-metadata [metadata]
   (when metadata
     (for [m metadata]
diff --git a/src/metabase/models/collection.clj b/src/metabase/models/collection.clj
index e68211531291d2f257cd30490127af08ceebece9..ca37d0bc030e6a51eb8ccd4ae3ceca322de6488f 100644
--- a/src/metabase/models/collection.clj
+++ b/src/metabase/models/collection.clj
@@ -1224,7 +1224,7 @@
   [_collection]
   [:name :namespace parent-identity-hash :created_at])
 
-(defmethod serdes/extract-query "Collection" [_model {:keys [collection-set]}]
+(defmethod serdes/extract-query "Collection" [_model {:keys [collection-set where]}]
   (let [not-trash-clause [:or
                           [:= :type nil]
                           [:not= :type trash-collection-type]]]
@@ -1233,12 +1233,14 @@
                            {:where
                             [:and
                              [:in :id collection-set]
-                             not-trash-clause]})
+                             not-trash-clause
+                             (or where true)]})
       (t2/reducible-select Collection
                            {:where
                             [:and
                              [:= :personal_owner_id nil]
-                             not-trash-clause]}))))
+                             not-trash-clause
+                             (or where true)]}))))
 
 (defmethod serdes/extract-one "Collection"
   ;; Transform :location (which uses database IDs) into a portable :parent_id with the parent's entity ID.
diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj
index 7d5c6e70aba2a4f7c71bddacaeee1fe8e21798e5..53c3ffd79b06c7370be56e44455d1e1dae5a764d 100644
--- a/src/metabase/models/dashboard.clj
+++ b/src/metabase/models/dashboard.clj
@@ -580,10 +580,6 @@
 ;;; |                                               SERIALIZATION                                                    |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(defmethod serdes/extract-query "Dashboard" [_ opts]
-  (eduction (map #(t2/hydrate % :tabs [:dashcards :series]))
-            (serdes/extract-query-collections Dashboard opts)))
-
 (defmethod serdes/make-spec "Dashboard" [_model-name opts]
   {:copy      [:archived :archived_directly :auto_apply_filters :cache_ttl :caveats :collection_position
                :description :embedding_params :enable_embedding :entity_id :initially_published_at :name
diff --git a/src/metabase/models/dashboard_card.clj b/src/metabase/models/dashboard_card.clj
index 509361901e9343ec5604b3164ca44935aa3f78bf..665392001ccb32e1345e34f509ce3ebfaf7f871a 100644
--- a/src/metabase/models/dashboard_card.clj
+++ b/src/metabase/models/dashboard_card.clj
@@ -372,15 +372,7 @@
                                         :import serdes/import-parameter-mappings}
                :visualization_settings {:export serdes/export-visualization-settings
                                         :import serdes/import-visualization-settings}
-               :series
-               (-> (serdes/nested :model/DashboardCardSeries :dashboardcard_id
-                                  (assoc opts
-                                         :sort-by :position
-                                         :key-field :card_id))
-                   ;; FIXME: this waits to be removed when `extract-nested` (instead of using hydration) is
-                   ;; implemented; see comment at `make-spec` for `DashboardCardSeries`
-                   (assoc :export (fn [data]
-                                    (vec (map-indexed (fn [i x]
-                                                        {:card_id  (serdes/*export-fk* (:id x) :model/Card)
-                                                         :position i})
-                                                      data)))))}})
+               :series                 (serdes/nested :model/DashboardCardSeries :dashboardcard_id
+                                                      (assoc opts
+                                                             :sort-by :position
+                                                             :key-field :card_id)) }})
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index 17ff8800eec45fd3b658f401ca60166882b84e39..cbb61f1ed32670e3060279082f48cef8d0ba1a7b 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -377,13 +377,6 @@
   (let [table (serdes/load-find-local (pop path))]
     (t2/select-one Field :name (-> path last :id) :table_id (:id table))))
 
-(defmethod serdes/extract-query "Field" [_model-name opts]
-  (let [d          (t2/select Dimension)
-        dimensions (->> d
-                        (group-by :field_id))]
-    (eduction (map #(assoc % :dimensions (get dimensions (:id %))))
-              (t2/reducible-select Field {:where (:where opts true)}))))
-
 (defmethod serdes/dependencies "Field" [field]
   ;; Fields depend on their parent Table, plus any foreign Fields referenced by their Dimensions.
   ;; Take the path, but drop the Field section to get the parent Table's path instead.
diff --git a/src/metabase/models/serialization.clj b/src/metabase/models/serialization.clj
index 694f04bfca783c7cab1270d2d539ab1130b1ab4b..061dade1944f35a8d7e51c3d6266aa3107317635 100644
--- a/src/metabase/models/serialization.clj
+++ b/src/metabase/models/serialization.clj
@@ -32,6 +32,9 @@
 
 (set! *warn-on-reflection* true)
 
+;; there was no science behind picking 100 as a number
+(def ^:private extract-nested-batch-limit "max amount of entities to fetch nested entities for" 100)
+
 ;;; # Serialization Overview
 ;;;
 ;;; Serialization (or "serdes") is a system for exporting entities (Dashboards, Cards, Collections, etc.) from one
@@ -330,7 +333,7 @@
             (m/assoc-some :serdes/meta (generate-path model-name instance))
             (into (for [[k transform] (:transform spec)
                         :let [res ((:export transform) (get instance k))]
-                        ;; include only non-nil transform results
+                        ;; include only non-nil `transform` results
                         :when res]
                     [k res])))))
     (catch Exception e
@@ -410,23 +413,53 @@
   (eduction (map (partial log-and-extract-one model opts))
             (extract-query model opts)))
 
+(declare extract-query)
+
+(defn- transform->nested [transform opts batch]
+  (let [backward-fk (:backward-fk transform)
+        entities    (-> (extract-query (name (:model transform))
+                                       (assoc opts :where [:in backward-fk (map :id batch)]))
+                        t2.realize/realize)]
+    (group-by backward-fk entities)))
+
+(defn- extract-batch-nested [model-name opts batch]
+  (let [spec (make-spec model-name opts)]
+    (reduce-kv (fn [batch k transform]
+                 (if-not (::nested transform)
+                   batch
+                   (mi/instances-with-hydrated-data batch k #(transform->nested transform opts batch) :id)))
+               batch
+               (:transform spec))))
+
+(defn- extract-reducible-nested [model-name opts reducible]
+  (eduction (comp (map t2.realize/realize)
+                  (partition-all (or (:batch-limit opts)
+                                     extract-nested-batch-limit))
+                  (map (partial extract-batch-nested model-name opts))
+                  cat)
+            reducible))
+
 (defn extract-query-collections
   "Helper for the common (but not default) [[extract-query]] case of fetching everything that isn't in a personal
   collection."
-  [model {:keys [collection-set where]}]
-  (if collection-set
-    ;; If collection-set is defined, select everything in those collections, or with nil :collection_id.
-    (t2/reducible-select model {:where [:or
-                                        [:in :collection_id collection-set]
-                                        (when (contains? collection-set nil)
-                                          [:= :collection_id nil])
-                                        (when where
-                                          where)]})
-    ;; If collection-set is nil, just select everything.
-    (t2/reducible-select model {:where (or where true)})))
-
-(defmethod extract-query :default [model-name {:keys [where]}]
-  (t2/reducible-select (symbol model-name) {:where (or where true)}))
+  [model {:keys [collection-set where] :as opts}]
+  (let [spec (make-spec (name model) opts)]
+    (if (or (nil? collection-set)
+            (nil? (-> spec :transform :collection_id)))
+      ;; either no collections specified or our model has no collection
+      (t2/reducible-select model {:where (or where true)})
+      (t2/reducible-select model {:where [:or
+                                          [:in :collection_id collection-set]
+                                          (when (contains? collection-set nil)
+                                            [:= :collection_id nil])
+                                          (when where
+                                            where)]}))))
+
+(defmethod extract-query :default [model-name opts]
+  (let [spec    (make-spec model-name opts)
+        nested? (some ::nested (vals (:transform spec)))]
+    (cond->> (extract-query-collections (keyword "model" model-name) opts)
+      nested? (extract-reducible-nested model-name (dissoc opts :where)))))
 
 (defn extract-one-basics
   "A helper for writing [[extract-one]] implementations. It takes care of the basics:
@@ -1534,15 +1567,14 @@
     {::nested     true
      :model       model
      :backward-fk backward-fk
+     :opts        opts
      :export      (fn [data]
                     (assert (every? #(t2/instance-of? model %) data)
                             (format "Nested data is expected to be a %s, not %s" model (t2/model (first data))))
                     ;; `nil? data` check is for `extract-one` case in tests; make sure to add empty vectors in
                     ;; `extract-query` implementations for nested collections
                     (try
-                      (->> (or data (when (nil? data)
-                                      (t2/select model backward-fk (:id *current*))))
-                           (sort-by sorter)
+                      (->> (sort-by sorter data)
                            (mapv #(extract-one model-name opts %)))
                       (catch Exception e
                         (throw (ex-info (format "Error exporting nested %s" model)