diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index c7b2937684da87cfbaf87fe1d9cfed737675f6fa..4d2a7f6cbf046029fdd728b3b81ebe9d67629735 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1454,3 +1454,25 @@ function system_element_info_alter(&$type) {
     $type['page']['#theme_wrappers']['off_canvas_page_wrapper'] = ['#weight' => -1000];
   }
 }
+
+/**
+ * Implements hook_modules_uninstalled().
+ */
+function system_modules_uninstalled($modules) {
+  // @todo Remove this when modules are able to maintain their revision metadata
+  //   keys.
+  //   @see https://www.drupal.org/project/drupal/issues/3074333
+  if (!in_array('workspaces', $modules, TRUE)) {
+    return;
+  }
+
+  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) {
+    $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
+    if ($revision_metadata_keys && array_key_exists('workspace', $revision_metadata_keys)) {
+      unset($revision_metadata_keys['workspace']);
+      $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+      $entity_definition_update_manager->updateEntityType($entity_type);
+    }
+  }
+}
diff --git a/core/modules/workspaces/src/Entity/WorkspaceAssociation.php b/core/modules/workspaces/src/Entity/WorkspaceAssociation.php
deleted file mode 100644
index 6c65c81ed024c60b426fee0955dc4fcbf48bfb71..0000000000000000000000000000000000000000
--- a/core/modules/workspaces/src/Entity/WorkspaceAssociation.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-namespace Drupal\workspaces\Entity;
-
-use Drupal\Core\Entity\ContentEntityBase;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-
-/**
- * Defines the Workspace association entity.
- *
- * @ContentEntityType(
- *   id = "workspace_association",
- *   label = @Translation("Workspace association"),
- *   label_collection = @Translation("Workspace associations"),
- *   label_singular = @Translation("workspace association"),
- *   label_plural = @Translation("workspace associations"),
- *   label_count = @PluralTranslation(
- *     singular = "@count workspace association",
- *     plural = "@count workspace associations"
- *   ),
- *   handlers = {
- *     "storage" = "Drupal\workspaces\WorkspaceAssociationStorage"
- *   },
- *   base_table = "workspace_association",
- *   revision_table = "workspace_association_revision",
- *   internal = TRUE,
- *   entity_keys = {
- *     "id" = "id",
- *     "revision" = "revision_id",
- *     "uuid" = "uuid",
- *   }
- * )
- *
- * @internal
- *   This entity is marked internal because it should not be used directly to
- *   alter the workspace an entity belongs to.
- */
-class WorkspaceAssociation extends ContentEntityBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
-    $fields = parent::baseFieldDefinitions($entity_type);
-
-    $fields['workspace'] = BaseFieldDefinition::create('entity_reference')
-      ->setLabel(new TranslatableMarkup('workspace'))
-      ->setDescription(new TranslatableMarkup('The workspace of the referenced content.'))
-      ->setSetting('target_type', 'workspace')
-      ->setRequired(TRUE)
-      ->setRevisionable(TRUE);
-
-    $fields['target_entity_type_id'] = BaseFieldDefinition::create('string')
-      ->setLabel(new TranslatableMarkup('Content entity type ID'))
-      ->setDescription(new TranslatableMarkup('The ID of the content entity type associated with this workspace.'))
-      ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH)
-      ->setRequired(TRUE)
-      ->setRevisionable(TRUE);
-
-    $fields['target_entity_id'] = BaseFieldDefinition::create('integer')
-      ->setLabel(new TranslatableMarkup('Content entity ID'))
-      ->setDescription(new TranslatableMarkup('The ID of the content entity associated with this workspace.'))
-      ->setRequired(TRUE)
-      ->setRevisionable(TRUE);
-
-    $fields['target_entity_revision_id'] = BaseFieldDefinition::create('integer')
-      ->setLabel(new TranslatableMarkup('Content entity revision ID'))
-      ->setDescription(new TranslatableMarkup('The revision ID of the content entity associated with this workspace.'))
-      ->setRequired(TRUE)
-      ->setRevisionable(TRUE);
-
-    return $fields;
-  }
-
-}
diff --git a/core/modules/workspaces/src/EntityOperations.php b/core/modules/workspaces/src/EntityOperations.php
index 6182da200f2cbc7606a7d0071f0e3c90741e67b6..4409162a282b2dcd967bed2286af0f6c14f222b7 100644
--- a/core/modules/workspaces/src/EntityOperations.php
+++ b/core/modules/workspaces/src/EntityOperations.php
@@ -34,6 +34,13 @@ class EntityOperations implements ContainerInjectionInterface {
    */
   protected $workspaceManager;
 
+  /**
+   * The workspace association service.
+   *
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
+   */
+  protected $workspaceAssociation;
+
   /**
    * Constructs a new EntityOperations instance.
    *
@@ -41,10 +48,13 @@ class EntityOperations implements ContainerInjectionInterface {
    *   The entity type manager service.
    * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
    *   The workspace manager service.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) {
     $this->entityTypeManager = $entity_type_manager;
     $this->workspaceManager = $workspace_manager;
+    $this->workspaceAssociation = $workspace_association;
   }
 
   /**
@@ -53,7 +63,8 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Wor
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity_type.manager'),
-      $container->get('workspaces.manager')
+      $container->get('workspaces.manager'),
+      $container->get('workspaces.association')
     );
   }
 
@@ -74,31 +85,13 @@ public function entityPreload(array $ids, $entity_type_id) {
     // Get a list of revision IDs for entities that have a revision set for the
     // current active workspace. If an entity has multiple revisions set for a
     // workspace, only the one with the highest ID is returned.
-    $max_revision_id = 'max_target_entity_revision_id';
-    $query = $this->entityTypeManager
-      ->getStorage('workspace_association')
-      ->getAggregateQuery()
-      ->accessCheck(FALSE)
-      ->allRevisions()
-      ->aggregate('target_entity_revision_id', 'MAX', NULL, $max_revision_id)
-      ->groupBy('target_entity_id')
-      ->condition('target_entity_type_id', $entity_type_id)
-      ->condition('workspace', $this->workspaceManager->getActiveWorkspace()->id());
-
-    if ($ids) {
-      $query->condition('target_entity_id', $ids, 'IN');
-    }
-
-    $results = $query->execute();
-
-    if ($results) {
+    if ($tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->workspaceManager->getActiveWorkspace()->id(), $entity_type_id, $ids)) {
       /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
       $storage = $this->entityTypeManager->getStorage($entity_type_id);
 
       // Swap out every entity which has a revision set for the current active
       // workspace.
-      $swap_revision_ids = array_column($results, $max_revision_id);
-      foreach ($storage->loadMultipleRevisions($swap_revision_ids) as $revision) {
+      foreach ($storage->loadMultipleRevisions(array_keys($tracked_entities[$entity_type_id])) as $revision) {
         $entities[$revision->id()] = $revision;
       }
     }
@@ -142,6 +135,10 @@ public function entityPresave(EntityInterface $entity) {
       // become the default revision only when it is replicated to the default
       // workspace.
       $entity->isDefaultRevision(FALSE);
+
+      // Track the workspaces in which the new revision was saved.
+      $field_name = $entity_type->getRevisionMetadataKey('workspace');
+      $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id();
     }
 
     // When a new published entity is inserted in a non-default workspace, we
@@ -174,7 +171,7 @@ public function entityInsert(EntityInterface $entity) {
       return;
     }
 
-    $this->trackEntity($entity);
+    $this->workspaceAssociation->trackEntity($entity, $this->workspaceManager->getActiveWorkspace());
 
     // When an entity is newly created in a workspace, it should be published in
     // that workspace, but not yet published on the live workspace. It is first
@@ -211,7 +208,7 @@ public function entityUpdate(EntityInterface $entity) {
     // Only track new revisions.
     /** @var \Drupal\Core\Entity\RevisionableInterface $entity */
     if ($entity->getLoadedRevisionId() != $entity->getRevisionId()) {
-      $this->trackEntity($entity);
+      $this->workspaceAssociation->trackEntity($entity, $this->workspaceManager->getActiveWorkspace());
     }
   }
 
@@ -240,51 +237,6 @@ public function entityPredelete(EntityInterface $entity) {
     }
   }
 
-  /**
-   * Updates or creates a WorkspaceAssociation entity for a given entity.
-   *
-   * If the passed-in entity can belong to a workspace and already has a
-   * WorkspaceAssociation entity, then a new revision of this will be created with
-   * the new information. Otherwise, a new WorkspaceAssociation entity is created to
-   * store the passed-in entity's information.
-   *
-   * @param \Drupal\Core\Entity\RevisionableInterface $entity
-   *   The entity to update or create from.
-   */
-  protected function trackEntity(RevisionableInterface $entity) {
-    // If the entity is not new, check if there's an existing
-    // WorkspaceAssociation entity for it.
-    $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
-    if (!$entity->isNew()) {
-      $workspace_associations = $workspace_association_storage->loadByProperties([
-        'target_entity_type_id' => $entity->getEntityTypeId(),
-        'target_entity_id' => $entity->id(),
-      ]);
-
-      /** @var \Drupal\Core\Entity\ContentEntityInterface $workspace_association */
-      $workspace_association = reset($workspace_associations);
-    }
-
-    // If there was a WorkspaceAssociation entry create a new revision,
-    // otherwise create a new entity with the type and ID.
-    if (!empty($workspace_association)) {
-      $workspace_association->setNewRevision(TRUE);
-    }
-    else {
-      $workspace_association = $workspace_association_storage->create([
-        'target_entity_type_id' => $entity->getEntityTypeId(),
-        'target_entity_id' => $entity->id(),
-      ]);
-    }
-
-    // Add the revision ID and the workspace ID.
-    $workspace_association->set('target_entity_revision_id', $entity->getRevisionId());
-    $workspace_association->set('workspace', $this->workspaceManager->getActiveWorkspace()->id());
-
-    // Save without updating the tracked content entity.
-    $workspace_association->save();
-  }
-
   /**
    * Alters entity forms to disallow concurrent editing in multiple workspaces.
    *
@@ -298,7 +250,7 @@ protected function trackEntity(RevisionableInterface $entity) {
    * @see hook_form_alter()
    */
   public function entityFormAlter(array &$form, FormStateInterface $form_state, $form_id) {
-    /** @var \Drupal\Core\Entity\EntityInterface $entity */
+    /** @var \Drupal\Core\Entity\RevisionableInterface $entity */
     $entity = $form_state->getFormObject()->getEntity();
     if (!$this->workspaceManager->isEntityTypeSupported($entity->getEntityType())) {
       return;
@@ -318,9 +270,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, $f
       $form['#entity_builders'][] = [get_called_class(), 'entityFormEntityBuild'];
     }
 
-    /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
-    $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
-    if ($workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity)) {
+    if ($workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity)) {
       // An entity can only be edited in one workspace.
       $workspace_id = reset($workspace_ids);
 
diff --git a/core/modules/workspaces/src/EntityTypeInfo.php b/core/modules/workspaces/src/EntityTypeInfo.php
index 5495c7fa4e7c09c31db6484bc881ad7126279341..7a72eb246d26cc8beefb42fc5000599e04bb53e1 100644
--- a/core/modules/workspaces/src/EntityTypeInfo.php
+++ b/core/modules/workspaces/src/EntityTypeInfo.php
@@ -3,7 +3,10 @@
 namespace Drupal\workspaces;
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -66,6 +69,10 @@ public function entityTypeBuild(array &$entity_types) {
     foreach ($entity_types as $entity_type) {
       if ($this->workspaceManager->isEntityTypeSupported($entity_type)) {
         $entity_type->addConstraint('EntityWorkspaceConflict');
+
+        $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
+        $revision_metadata_keys['workspace'] = 'workspace';
+        $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
       }
     }
   }
@@ -84,4 +91,30 @@ public function fieldInfoAlter(&$definitions) {
     }
   }
 
+  /**
+   * Provides custom base field definitions for a content entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   *
+   * @return \Drupal\Core\Field\FieldDefinitionInterface[]
+   *   An array of field definitions, keyed by field name.
+   *
+   * @see hook_entity_base_field_info()
+   */
+  public function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
+    if ($this->workspaceManager->isEntityTypeSupported($entity_type)) {
+      $field_name = $entity_type->getRevisionMetadataKey('workspace');
+      $fields[$field_name] = BaseFieldDefinition::create('entity_reference')
+        ->setLabel(new TranslatableMarkup('Workspace'))
+        ->setDescription(new TranslatableMarkup('Indicates the workspace that this revision belongs to.'))
+        ->setSetting('target_type', 'workspace')
+        ->setInternal(TRUE)
+        ->setTranslatable(FALSE)
+        ->setRevisionable(TRUE);
+
+      return $fields;
+    }
+  }
+
 }
diff --git a/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..30fd77aa6445cca0ed96aacd74acaef04a63c299
--- /dev/null
+++ b/core/modules/workspaces/src/EventSubscriber/EntitySchemaSubscriber.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Drupal\workspaces\EventSubscriber;
+
+use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
+use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeListenerInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\workspaces\WorkspaceManagerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Defines a class for listening to entity schema changes.
+ */
+class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface {
+
+  use EntityTypeEventSubscriberTrait;
+  use StringTranslationTrait;
+
+  /**
+   * The definition update manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
+   */
+  protected $entityDefinitionUpdateManager;
+
+  /**
+   * The last installed schema definitions.
+   *
+   * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
+   */
+  protected $entityLastInstalledSchemaRepository;
+
+  /**
+   * The workspace manager.
+   *
+   * @var \Drupal\workspaces\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * Constructs a new EntitySchemaSubscriber.
+   *
+   * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager
+   *   Definition update manager.
+   * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository
+   *   Last definitions.
+   * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
+   *   The workspace manager.
+   */
+  public function __construct(EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager, EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository, WorkspaceManagerInterface $workspace_manager) {
+    $this->entityDefinitionUpdateManager = $entityDefinitionUpdateManager;
+    $this->entityLastInstalledSchemaRepository = $entityLastInstalledSchemaRepository;
+    $this->workspaceManager = $workspace_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return static::getEntityTypeEvents();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
+    // Nothing to do here.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    // If the entity type is now supported by Workspaces, add the revision
+    // metadata field.
+    if ($this->workspaceManager->isEntityTypeSupported($entity_type) && !$this->workspaceManager->isEntityTypeSupported($original)) {
+      $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
+
+      if (!isset($revision_metadata_keys['workspace'])) {
+        // Bail out if there's an existing field called 'workspace'.
+        if ($this->entityDefinitionUpdateManager->getFieldStorageDefinition('workspace', $entity_type->id())) {
+          throw new \RuntimeException("An existing 'workspace' field was found for the '{$entity_type->id()}' entity type. Set the 'workspace' revision metadata key to use a different field name and run this update function again.");
+        }
+
+        $revision_metadata_keys['workspace'] = 'workspace';
+        $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+
+        // We are only adding a revision metadata key so we don't need to go
+        // through the entity update process.
+        $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
+      }
+
+      $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_metadata_keys['workspace'], $entity_type->id(), 'workspaces', $this->getWorkspaceFieldDefinition());
+    }
+
+    // If the entity type is no longer supported by Workspaces, remove the
+    // revision metadata field.
+    if ($this->workspaceManager->isEntityTypeSupported($original) && !$this->workspaceManager->isEntityTypeSupported($entity_type)) {
+      $revision_metadata_keys = $original->get('revision_metadata_keys');
+      $field_storage_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type->id())[$revision_metadata_keys['workspace']];
+      $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($field_storage_definition);
+
+      $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
+      unset($revision_metadata_keys['workspace']);
+      $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+
+      // We are only removing a revision metadata key so we don't need to go
+      // through the entity update process.
+      $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original, array $field_storage_definitions, array $original_field_storage_definitions, array &$sandbox = NULL) {
+    $this->onEntityTypeUpdate($entity_type, $original);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+    // Nothing to do here.
+  }
+
+  /**
+   * Gets the base field definition for the 'workspace' revision metadata field.
+   *
+   * @return \Drupal\Core\Field\BaseFieldDefinition
+   *   The base field definition.
+   */
+  protected function getWorkspaceFieldDefinition() {
+    return BaseFieldDefinition::create('entity_reference')
+      ->setLabel($this->t('Workspace'))
+      ->setDescription($this->t('Indicates the workspace that this revision belongs to.'))
+      ->setSetting('target_type', 'workspace')
+      ->setInternal(TRUE)
+      ->setTranslatable(FALSE)
+      ->setRevisionable(TRUE);
+  }
+
+}
diff --git a/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php b/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php
index 8086873f9ade48b67d544f764fdbc7b1b9aaaf04..195cac732fd28135267597680714a079850962a9 100644
--- a/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php
+++ b/core/modules/workspaces/src/Form/WorkspaceDeleteForm.php
@@ -2,8 +2,13 @@
 
 namespace Drupal\workspaces\Form;
 
+use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Entity\ContentEntityDeleteForm;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\workspaces\WorkspaceAssociationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a form for deleting a workspace.
@@ -19,14 +24,52 @@ class WorkspaceDeleteForm extends ContentEntityDeleteForm implements WorkspaceFo
    */
   protected $entity;
 
+  /**
+   * The workspace association service.
+   *
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
+   */
+  protected $workspaceAssociation;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.repository'),
+      $container->get('workspaces.association'),
+      $container->get('entity_type.bundle.info'),
+      $container->get('datetime.time')
+    );
+  }
+
+  /**
+   * Constructs a WorkspaceDeleteForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
+   *   The entity repository service.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service to check how many revisions will be
+   *   deleted.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
+   *   The entity type bundle service.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   */
+  public function __construct(EntityRepositoryInterface $entity_repository, WorkspaceAssociationInterface $workspace_association, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
+    parent::__construct($entity_repository, $entity_type_bundle_info, $time);
+    $this->workspaceAssociation = $workspace_association;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     $form = parent::buildForm($form, $form_state);
-    $source_rev_diff = $this->entityTypeManager->getStorage('workspace_association')->getTrackedEntities($this->entity->id());
+    $tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->entity->id());
     $items = [];
-    foreach ($source_rev_diff as $entity_type_id => $revision_ids) {
+    foreach (array_keys($tracked_entities) as $entity_type_id => $entity_ids) {
+      $revision_ids = $this->workspaceAssociation->getAssociatedRevisions($this->entity->id(), $entity_type_id, $entity_ids);
       $label = $this->entityTypeManager->getDefinition($entity_type_id)->getLabel();
       $items[] = $this->formatPlural(count($revision_ids), '1 @label revision.', '@count @label revisions.', ['@label' => $label]);
     }
diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php
index 070e89050bb8adae926b7336766230b72be792cf..1543b5fc2b30fca4e9499667221522f25b39b2bc 100644
--- a/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php
+++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/DeletedWorkspaceConstraintValidator.php
@@ -3,7 +3,7 @@
 namespace Drupal\workspaces\Plugin\Validation\Constraint;
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
-use Drupal\workspaces\WorkspaceAssociationStorageInterface;
+use Drupal\workspaces\WorkspaceAssociationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
@@ -14,20 +14,20 @@
 class DeletedWorkspaceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
 
   /**
-   * The workspace association storage.
+   * The workspace association service.
    *
-   * @var \Drupal\workspaces\WorkspaceAssociationStorageInterface
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
    */
-  protected $workspaceAssociationStorage;
+  protected $workspaceAssociation;
 
   /**
    * Creates a new DeletedWorkspaceConstraintValidator instance.
    *
-   * @param \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage
-   *   The workspace association storage.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service.
    */
-  public function __construct(WorkspaceAssociationStorageInterface $workspace_association_storage) {
-    $this->workspaceAssociationStorage = $workspace_association_storage;
+  public function __construct(WorkspaceAssociationInterface $workspace_association) {
+    $this->workspaceAssociation = $workspace_association;
   }
 
   /**
@@ -35,7 +35,7 @@ public function __construct(WorkspaceAssociationStorageInterface $workspace_asso
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity_type.manager')->getStorage('workspace_association')
+      $container->get('workspaces.association')
     );
   }
 
@@ -49,14 +49,7 @@ public function validate($value, Constraint $constraint) {
       return;
     }
 
-    $count = $this->workspaceAssociationStorage
-      ->getQuery()
-      ->allRevisions()
-      ->accessCheck(FALSE)
-      ->condition('workspace', $value->getEntity()->id())
-      ->count()
-      ->execute();
-    if ($count) {
+    if ($this->workspaceAssociation->getTrackedEntities($value->getEntity()->id())) {
       $this->context->addViolation($constraint->message);
     }
   }
diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php
index 17adc7c0d2d892930b2f3904f0a9758ce2cacca5..66bb887e7b1243ee6047817035d75063826a6e62 100644
--- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php
+++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\workspaces\WorkspaceAssociationInterface;
 use Drupal\workspaces\WorkspaceManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\Constraint;
@@ -28,6 +29,13 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp
    */
   protected $workspaceManager;
 
+  /**
+   * The workspace association service.
+   *
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
+   */
+  protected $workspaceAssociation;
+
   /**
    * Constructs an EntityUntranslatableFieldsConstraintValidator object.
    *
@@ -35,10 +43,13 @@ class EntityWorkspaceConflictConstraintValidator extends ConstraintValidator imp
    *   The entity type manager service.
    * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
    *   The workspace manager service.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) {
     $this->entityTypeManager = $entity_type_manager;
     $this->workspaceManager = $workspace_manager;
+    $this->workspaceAssociation = $workspace_association;
   }
 
   /**
@@ -47,7 +58,8 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Wor
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity_type.manager'),
-      $container->get('workspaces.manager')
+      $container->get('workspaces.manager'),
+      $container->get('workspaces.association')
     );
   }
 
@@ -57,9 +69,7 @@ public static function create(ContainerInterface $container) {
   public function validate($entity, Constraint $constraint) {
     /** @var \Drupal\Core\Entity\EntityInterface $entity */
     if (isset($entity) && !$entity->isNew()) {
-      /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
-      $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
-      $workspace_ids = $workspace_association_storage->getEntityTrackingWorkspaceIds($entity);
+      $workspace_ids = $this->workspaceAssociation->getEntityTrackingWorkspaceIds($entity);
       $active_workspace = $this->workspaceManager->getActiveWorkspace();
 
       if ($workspace_ids && (!$active_workspace || !in_array($active_workspace->id(), $workspace_ids, TRUE))) {
diff --git a/core/modules/workspaces/src/WorkspaceAssociation.php b/core/modules/workspaces/src/WorkspaceAssociation.php
new file mode 100644
index 0000000000000000000000000000000000000000..a67f9546937278b9aaa16adcafe0cbf917cbaae8
--- /dev/null
+++ b/core/modules/workspaces/src/WorkspaceAssociation.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\workspaces;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+
+/**
+ * Provides a class for CRUD operations on workspace associations.
+ */
+class WorkspaceAssociation implements WorkspaceAssociationInterface {
+
+  /**
+   * The table for the workspace association storage.
+   */
+  const TABLE = 'workspace_association';
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a WorkspaceAssociation object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   A database connection for reading and writing path aliases.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager for querying revisions.
+   */
+  public function __construct(Connection $connection, EntityTypeManagerInterface $entity_type_manager) {
+    $this->database = $connection;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $workspace) {
+    $this->database->merge(static::TABLE)
+      ->fields([
+        'target_entity_revision_id' => $entity->getRevisionId(),
+      ])
+      ->keys([
+        'workspace' => $workspace->id(),
+        'target_entity_type_id' => $entity->getEntityTypeId(),
+        'target_entity_id' => $entity->id(),
+      ])
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entity_ids = NULL) {
+    $query = $this->database->select(static::TABLE);
+    $query
+      ->fields(static::TABLE, ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id'])
+      ->orderBy('target_entity_revision_id', 'ASC')
+      ->condition('workspace', $workspace_id);
+
+    if ($entity_type_id) {
+      $query->condition('target_entity_type_id', $entity_type_id, '=');
+
+      if ($entity_ids) {
+        $query->condition('target_entity_id', $entity_ids, 'IN');
+      }
+    }
+
+    $tracked_revisions = [];
+    foreach ($query->execute() as $record) {
+      $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id;
+    }
+
+    return $tracked_revisions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL) {
+    /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage($entity_type_id);
+
+    // If the entity type is not using core's default entity storage, we can't
+    // assume the table mapping layout so we have to return only the latest
+    // tracked revisions.
+    if (!$storage instanceof SqlContentEntityStorage) {
+      return $this->getTrackedEntities($workspace_id, $entity_type_id, $entity_ids)[$entity_type_id];
+    }
+
+    $entity_type = $storage->getEntityType();
+    $table_mapping = $storage->getTableMapping();
+    $workspace_field = $table_mapping->getColumnNames($entity_type->get('revision_metadata_keys')['workspace'])['target_id'];
+    $id_field = $table_mapping->getColumnNames($entity_type->getKey('id'))['value'];
+    $revision_id_field = $table_mapping->getColumnNames($entity_type->getKey('revision'))['value'];
+
+    $query = $this->database->select($entity_type->getRevisionTable(), 'revision');
+    $query->leftJoin($entity_type->getBaseTable(), 'base', "revision.$id_field = base.$id_field");
+
+    $query
+      ->fields('revision', [$revision_id_field, $id_field])
+      ->condition("revision.$workspace_field", $workspace_id)
+      ->where("revision.$revision_id_field > base.$revision_id_field")
+      ->orderBy("revision.$revision_id_field", 'ASC');
+
+    // Restrict the result to a set of entity ID's if provided.
+    if ($entity_ids) {
+      $query->condition("revision.$id_field", $entity_ids, 'IN');
+    }
+
+    return $query->execute()->fetchAllKeyed();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity) {
+    $query = $this->database->select(static::TABLE)
+      ->fields(static::TABLE, ['workspace'])
+      ->condition('target_entity_type_id', $entity->getEntityTypeId())
+      ->condition('target_entity_id', $entity->id());
+
+    return $query->execute()->fetchCol();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postPublish(WorkspaceInterface $workspace) {
+    $this->deleteAssociations($workspace->id());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAssociations($workspace_id, $entity_type_id = NULL, $entity_ids = NULL) {
+    $query = $this->database->delete(static::TABLE)
+      ->condition('workspace', $workspace_id);
+
+    if ($entity_type_id) {
+      $query->condition('target_entity_type_id', $entity_type_id, '=');
+
+      if ($entity_ids) {
+        $query->condition('target_entity_id', $entity_ids, 'IN');
+      }
+    }
+
+    $query->execute();
+  }
+
+}
diff --git a/core/modules/workspaces/src/WorkspaceAssociationInterface.php b/core/modules/workspaces/src/WorkspaceAssociationInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c93ca4aaa197bc8320cc7c9465dff9dc34ab64c
--- /dev/null
+++ b/core/modules/workspaces/src/WorkspaceAssociationInterface.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\workspaces;
+
+use Drupal\Core\Entity\RevisionableInterface;
+
+/**
+ * Defines an interface for the workspace_association service.
+ *
+ * The canonical workspace association data is stored in a revision metadata
+ * field on each entity revision that is tracked by a workspace.
+ *
+ * For the purpose of optimizing workspace-specific queries, the default
+ * implementation of this interface defines a custom 'workspace_association'
+ * index table which stores only the latest revisions tracked by a workspace.
+ *
+ * @internal
+ */
+interface WorkspaceAssociationInterface {
+
+  /**
+   * Updates or creates the association for a given entity and a workspace.
+   *
+   * @param \Drupal\Core\Entity\RevisionableInterface $entity
+   *   The entity to update or create from.
+   * @param \Drupal\workspaces\WorkspaceInterface $workspace
+   *   The workspace in which the entity will be tracked.
+   */
+  public function trackEntity(RevisionableInterface $entity, WorkspaceInterface $workspace);
+
+  /**
+   * Retrieves the entities tracked by a given workspace.
+   *
+   * @param string $workspace_id
+   *   The ID of the workspace.
+   * @param string|null $entity_type_id
+   *   (optional) An entity type ID to filter the results by. Defaults to NULL.
+   * @param int[]|string[]|null $entity_ids
+   *   (optional) An array of entity IDs to filter the results by. Defaults to
+   *   NULL.
+   *
+   * @return array
+   *   Returns a multidimensional array where the first level keys are entity
+   *   type IDs and the values are an array of entity IDs keyed by revision IDs.
+   */
+  public function getTrackedEntities($workspace_id, $entity_type_id = NULL, $entity_ids = NULL);
+
+  /**
+   * Retrieves all content revisions tracked by a given workspace.
+   *
+   * Since the 'workspace_association' index table only tracks the latest
+   * associated revisions, this method retrieves all the tracked revisions by
+   * querying the entity type's revision table directly.
+   *
+   * @param string $workspace_id
+   *   The ID of the workspace.
+   * @param string $entity_type_id
+   *   An entity type ID to find revisions for.
+   * @param int[]|string[]|null $entity_ids
+   *   (optional) An array of entity IDs to filter the results by. Defaults to
+   *   NULL.
+   *
+   * @return array
+   *   Returns an array where the values are an array of entity IDs keyed by
+   *   revision IDs.
+   */
+  public function getAssociatedRevisions($workspace_id, $entity_type_id, $entity_ids = NULL);
+
+  /**
+   * Gets a list of workspace IDs in which an entity is tracked.
+   *
+   * @param \Drupal\Core\Entity\RevisionableInterface $entity
+   *   An entity object.
+   *
+   * @return string[]
+   *   An array of workspace IDs where the given entity is tracked, or an empty
+   *   array if it is not tracked anywhere.
+   */
+  public function getEntityTrackingWorkspaceIds(RevisionableInterface $entity);
+
+  /**
+   * Triggers clean-up operations after publishing a workspace.
+   *
+   * @param \Drupal\workspaces\WorkspaceInterface $workspace
+   *   A workspace entity.
+   */
+  public function postPublish(WorkspaceInterface $workspace);
+
+  /**
+   * Deletes all the workspace association records for the given workspace.
+   *
+   * @param string $workspace_id
+   *   A workspace entity ID.
+   * @param string|null $entity_type_id
+   *   (optional) The target entity type of the associations to delete. Defaults
+   *   to NULL.
+   * @param string|null $entity_ids
+   *   (optional) The target entity IDs of the associations to delete. Defaults
+   *   to NULL.
+   */
+  public function deleteAssociations($workspace_id, $entity_type_id = NULL, $entity_ids = NULL);
+
+}
diff --git a/core/modules/workspaces/src/WorkspaceAssociationStorage.php b/core/modules/workspaces/src/WorkspaceAssociationStorage.php
deleted file mode 100644
index 6355e79220e49aa51251fcf1a9bb033e473ce393..0000000000000000000000000000000000000000
--- a/core/modules/workspaces/src/WorkspaceAssociationStorage.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-namespace Drupal\workspaces;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
-
-/**
- * Defines the storage handler class for the Workspace association entity type.
- */
-class WorkspaceAssociationStorage extends SqlContentEntityStorage implements WorkspaceAssociationStorageInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function postPush(WorkspaceInterface $workspace) {
-    $this->database
-      ->delete($this->entityType->getBaseTable())
-      ->condition('workspace', $workspace->id())
-      ->execute();
-    $this->database
-      ->delete($this->entityType->getRevisionTable())
-      ->condition('workspace', $workspace->id())
-      ->execute();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTrackedEntities($workspace_id, $all_revisions = FALSE) {
-    $table = $all_revisions ? $this->getRevisionTable() : $this->getBaseTable();
-    $query = $this->database->select($table, 'base_table');
-    $query
-      ->fields('base_table', ['target_entity_type_id', 'target_entity_id', 'target_entity_revision_id'])
-      ->orderBy('target_entity_revision_id', 'ASC')
-      ->condition('workspace', $workspace_id);
-
-    $tracked_revisions = [];
-    foreach ($query->execute() as $record) {
-      $tracked_revisions[$record->target_entity_type_id][$record->target_entity_revision_id] = $record->target_entity_id;
-    }
-
-    return $tracked_revisions;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getEntityTrackingWorkspaceIds(EntityInterface $entity) {
-    $query = $this->database->select($this->getBaseTable(), 'base_table');
-    $query
-      ->fields('base_table', ['workspace'])
-      ->condition('target_entity_type_id', $entity->getEntityTypeId())
-      ->condition('target_entity_id', $entity->id());
-
-    return $query->execute()->fetchCol();
-  }
-
-}
diff --git a/core/modules/workspaces/src/WorkspaceAssociationStorageInterface.php b/core/modules/workspaces/src/WorkspaceAssociationStorageInterface.php
deleted file mode 100644
index 24663206e30c6d33e04f75e75af8986c8462dc8c..0000000000000000000000000000000000000000
--- a/core/modules/workspaces/src/WorkspaceAssociationStorageInterface.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-namespace Drupal\workspaces;
-
-use Drupal\Core\Entity\ContentEntityStorageInterface;
-use Drupal\Core\Entity\EntityInterface;
-
-/**
- * Defines an interface for workspace association entity storage classes.
- */
-interface WorkspaceAssociationStorageInterface extends ContentEntityStorageInterface {
-
-  /**
-   * Triggers clean-up operations after pushing.
-   *
-   * @param \Drupal\workspaces\WorkspaceInterface $workspace
-   *   A workspace entity.
-   */
-  public function postPush(WorkspaceInterface $workspace);
-
-  /**
-   * Retrieves the content revisions tracked by a given workspace.
-   *
-   * @param string $workspace_id
-   *   The ID of the workspace.
-   * @param bool $all_revisions
-   *   (optional) Whether to return all the tracked revisions for each entity or
-   *   just the latest tracked revision. Defaults to FALSE.
-   *
-   * @return array
-   *   Returns a multidimensional array where the first level keys are entity
-   *   type IDs and the values are an array of entity IDs keyed by revision IDs.
-   */
-  public function getTrackedEntities($workspace_id, $all_revisions = FALSE);
-
-  /**
-   * Gets a list of workspace IDs in which an entity is tracked.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   An entity object.
-   *
-   * @return string[]
-   *   An array of workspace IDs where the given entity is tracked, or an empty
-   *   array if it is not tracked anywhere.
-   */
-  public function getEntityTrackingWorkspaceIds(EntityInterface $entity);
-
-}
diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php
index 5528988a80baa21e71058e55da8c230c43e1c2c8..b53be3613025a3e12416a50da5fea8a57ea35164 100644
--- a/core/modules/workspaces/src/WorkspaceManager.php
+++ b/core/modules/workspaces/src/WorkspaceManager.php
@@ -83,6 +83,13 @@ class WorkspaceManager implements WorkspaceManagerInterface {
    */
   protected $classResolver;
 
+  /**
+   * The workspace association service.
+   *
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
+   */
+  protected $workspaceAssociation;
+
   /**
    * The workspace negotiator service IDs.
    *
@@ -114,10 +121,12 @@ class WorkspaceManager implements WorkspaceManagerInterface {
    *   A logger instance.
    * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
    *   The class resolver.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service.
    * @param array $negotiator_ids
    *   The workspace negotiator service IDs.
    */
-  public function __construct(RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, MemoryCacheInterface $entity_memory_cache, AccountProxyInterface $current_user, StateInterface $state, LoggerInterface $logger, ClassResolverInterface $class_resolver, array $negotiator_ids) {
+  public function __construct(RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, MemoryCacheInterface $entity_memory_cache, AccountProxyInterface $current_user, StateInterface $state, LoggerInterface $logger, ClassResolverInterface $class_resolver, WorkspaceAssociationInterface $workspace_association, array $negotiator_ids) {
     $this->requestStack = $request_stack;
     $this->entityTypeManager = $entity_type_manager;
     $this->entityMemoryCache = $entity_memory_cache;
@@ -125,6 +134,7 @@ public function __construct(RequestStack $request_stack, EntityTypeManagerInterf
     $this->state = $state;
     $this->logger = $logger;
     $this->classResolver = $class_resolver;
+    $this->workspaceAssociation = $workspace_association;
     $this->negotiatorIds = $negotiator_ids;
   }
 
@@ -305,67 +315,35 @@ public function purgeDeletedWorkspacesBatch() {
 
     $batch_size = Settings::get('entity_update_batch_size', 50);
 
-    /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
-    $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
-
     // Get the first deleted workspace from the list and delete the revisions
-    // associated with it, along with the workspace_association entries.
+    // associated with it, along with the workspace association records.
     $workspace_id = reset($deleted_workspace_ids);
-    $workspace_association_ids = $this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size);
-
-    if ($workspace_association_ids) {
-      $workspace_associations = $workspace_association_storage->loadMultipleRevisions(array_keys($workspace_association_ids));
-      foreach ($workspace_associations as $workspace_association) {
-        $associated_entity_storage = $this->entityTypeManager->getStorage($workspace_association->target_entity_type_id->value);
-        // Delete the associated entity revision.
-        if ($entity = $associated_entity_storage->loadRevision($workspace_association->target_entity_revision_id->value)) {
-          if ($entity->isDefaultRevision()) {
-            $entity->delete();
-          }
-          else {
-            $associated_entity_storage->deleteRevision($workspace_association->target_entity_revision_id->value);
-          }
+    $tracked_entities = $this->workspaceAssociation->getTrackedEntities($workspace_id);
+
+    $count = 1;
+    foreach ($tracked_entities as $entity_type_id => $entities) {
+      $associated_entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
+      $associated_revisions = $this->workspaceAssociation->getAssociatedRevisions($workspace_id, $entity_type_id);
+      foreach (array_keys($associated_revisions) as $revision_id) {
+        if ($count > $batch_size) {
+          continue 2;
         }
 
-        // Delete the workspace_association revision.
-        if ($workspace_association->isDefaultRevision()) {
-          $workspace_association->delete();
-        }
-        else {
-          $workspace_association_storage->deleteRevision($workspace_association->getRevisionId());
-        }
+        // Delete the associated entity revision.
+        $associated_entity_storage->deleteRevision($revision_id);
+        $count++;
       }
+      // Delete the workspace association entries.
+      $this->workspaceAssociation->deleteAssociations($workspace_id, $entity_type_id, $entities);
     }
 
     // The purging operation above might have taken a long time, so we need to
-    // request a fresh list of workspace association IDs. If it is empty, we can
-    // go ahead and remove the deleted workspace ID entry from state.
-    if (!$this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size)) {
+    // request a fresh list of tracked entities. If it is empty, we can go ahead
+    // and remove the deleted workspace ID entry from state.
+    if (!$this->workspaceAssociation->getTrackedEntities($workspace_id)) {
       unset($deleted_workspace_ids[$workspace_id]);
       $this->state->set('workspace.deleted', $deleted_workspace_ids);
     }
   }
 
-  /**
-   * Gets a list of workspace association IDs to purge.
-   *
-   * @param string $workspace_id
-   *   The ID of the workspace.
-   * @param int $batch_size
-   *   The maximum number of records that will be purged.
-   *
-   * @return array
-   *   An array of workspace association IDs, keyed by their revision IDs.
-   */
-  protected function getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size) {
-    return $this->entityTypeManager->getStorage('workspace_association')
-      ->getQuery()
-      ->allRevisions()
-      ->accessCheck(FALSE)
-      ->condition('workspace', $workspace_id)
-      ->sort('revision_id', 'ASC')
-      ->range(0, $batch_size)
-      ->execute();
-  }
-
 }
diff --git a/core/modules/workspaces/src/WorkspaceOperationFactory.php b/core/modules/workspaces/src/WorkspaceOperationFactory.php
index d523365667ac508bab102a48d0860ad6d15f7bd4..7b761a4a3994c6f33a6c014b9312a7c72432b959 100644
--- a/core/modules/workspaces/src/WorkspaceOperationFactory.php
+++ b/core/modules/workspaces/src/WorkspaceOperationFactory.php
@@ -36,6 +36,13 @@ class WorkspaceOperationFactory {
    */
   protected $workspaceManager;
 
+  /**
+   * The workspace association service.
+   *
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
+   */
+  protected $workspaceAssociation;
+
   /**
    * Constructs a new WorkspacePublisher.
    *
@@ -43,11 +50,16 @@ class WorkspaceOperationFactory {
    *   The entity type manager.
    * @param \Drupal\Core\Database\Connection $database
    *   Database connection.
+   * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
+   *   The workspace manager service.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association) {
     $this->entityTypeManager = $entity_type_manager;
     $this->database = $database;
     $this->workspaceManager = $workspace_manager;
+    $this->workspaceAssociation = $workspace_association;
   }
 
   /**
@@ -60,7 +72,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Con
    *   A workspace publisher object.
    */
   public function getPublisher(WorkspaceInterface $source) {
-    return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $source);
+    return new WorkspacePublisher($this->entityTypeManager, $this->database, $this->workspaceManager, $this->workspaceAssociation, $source);
   }
 
 }
diff --git a/core/modules/workspaces/src/WorkspacePublisher.php b/core/modules/workspaces/src/WorkspacePublisher.php
index c7da6b8be782c14ba48409cbad592f58a61eb244..d38e97383c39be78a01a655ca638260fe81cf987 100644
--- a/core/modules/workspaces/src/WorkspacePublisher.php
+++ b/core/modules/workspaces/src/WorkspacePublisher.php
@@ -37,18 +37,18 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
   protected $database;
 
   /**
-   * The workspace association storage.
+   * The workspace manager.
    *
-   * @var \Drupal\workspaces\WorkspaceAssociationStorageInterface
+   * @var \Drupal\workspaces\WorkspaceManagerInterface
    */
-  protected $workspaceAssociationStorage;
+  protected $workspaceManager;
 
   /**
-   * The workspace manager.
+   * The workspace association service.
    *
-   * @var \Drupal\workspaces\WorkspaceManagerInterface
+   * @var \Drupal\workspaces\WorkspaceAssociationInterface
    */
-  protected $workspaceManager;
+  protected $workspaceAssociation;
 
   /**
    * Constructs a new WorkspacePublisher.
@@ -59,12 +59,14 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
    *   Database connection.
    * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
    *   The workspace manager.
+   * @param \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association
+   *   The workspace association service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceInterface $source) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, WorkspaceManagerInterface $workspace_manager, WorkspaceAssociationInterface $workspace_association, WorkspaceInterface $source) {
     $this->entityTypeManager = $entity_type_manager;
     $this->database = $database;
-    $this->workspaceAssociationStorage = $entity_type_manager->getStorage('workspace_association');
     $this->workspaceManager = $workspace_manager;
+    $this->workspaceAssociation = $workspace_association;
     $this->sourceWorkspace = $source;
   }
 
@@ -95,6 +97,11 @@ public function publish() {
             // revisions.
             $entity->setSyncing(TRUE);
             $entity->isDefaultRevision(TRUE);
+
+            // The default revision is not workspace-specific anymore.
+            $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace');
+            $entity->{$field_name}->target_id = NULL;
+
             $entity->original = $default_revisions[$entity->id()];
             $entity->save();
           }
@@ -107,9 +114,8 @@ public function publish() {
       throw $e;
     }
 
-    // Notify the workspace association storage that a workspace has been
-    // pushed.
-    $this->workspaceAssociationStorage->postPush($this->sourceWorkspace);
+    // Notify the workspace association that a workspace has been published.
+    $this->workspaceAssociation->postPublish($this->sourceWorkspace);
   }
 
   /**
@@ -141,7 +147,7 @@ public function checkConflictsOnTarget() {
   public function getDifferringRevisionIdsOnTarget() {
     $target_revision_difference = [];
 
-    $tracked_entities = $this->workspaceAssociationStorage->getTrackedEntities($this->sourceWorkspace->id());
+    $tracked_entities = $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id());
     foreach ($tracked_entities as $entity_type_id => $tracked_revisions) {
       $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
 
@@ -171,7 +177,7 @@ public function getDifferringRevisionIdsOnTarget() {
    */
   public function getDifferringRevisionIdsOnSource() {
     // Get the Workspace association revisions which haven't been pushed yet.
-    return $this->workspaceAssociationStorage->getTrackedEntities($this->sourceWorkspace->id());
+    return $this->workspaceAssociation->getTrackedEntities($this->sourceWorkspace->id());
   }
 
   /**
diff --git a/core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php b/core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e6276abfead76a94a69250aa7db85f9d06582ef
Binary files /dev/null and b/core/modules/workspaces/tests/fixtures/update/drupal-8.6.0-workspaces_installed.php differ
diff --git a/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php b/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0fffd21e3784cbccac98fc13d5632cc130024aa9
--- /dev/null
+++ b/core/modules/workspaces/tests/src/Functional/Update/WorkspacesUpdateTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\Tests\workspaces\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests the upgrade path for the Workspaces module.
+ *
+ * @group workspaces
+ * @group Update
+ * @group legacy
+ */
+class WorkspacesUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['workspaces'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/drupal-8.6.0-workspaces_installed.php',
+    ];
+  }
+
+  /**
+   * Tests the move of workspace association data to a custom table.
+   *
+   * @see workspaces_update_8801()
+   * @see workspaces_post_update_move_association_data()
+   */
+  public function testWorkspaceAssociationRemoval() {
+    $database = \Drupal::database();
+
+    // Check that we have two records in the 'workspace_association' base table
+    // and three records in its revision table.
+    $wa_records = $database->select('workspace_association')->countQuery()->execute()->fetchField();
+    $this->assertEquals(2, $wa_records);
+    $war_records = $database->select('workspace_association_revision')->countQuery()->execute()->fetchField();
+    $this->assertEquals(3, $war_records);
+
+    // Check that the node entity type does not have a 'workspace' field.
+    $this->assertNull(\Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('workspace', 'node'));
+
+    $this->runUpdates();
+
+    $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+
+    // Check that the 'workspace' field has been installed for an entity type
+    // that was workspace-supported before Drupal 8.7.0.
+    $this->assertTrue($entity_definition_update_manager->getFieldStorageDefinition('workspace', 'node'));
+
+    // Check that the 'workspace' field has been installed for an entity type
+    // which became workspace-supported as part of an entity schema update.
+    $this->assertTrue($entity_definition_update_manager->getFieldStorageDefinition('workspace', 'taxonomy_term'));
+
+    // Check that the 'workspace' revision metadata field has been created only
+    // in the revision table.
+    $schema = $database->schema();
+    $this->assertTrue($schema->fieldExists('node_revision', 'workspace'));
+    $this->assertFalse($schema->fieldExists('node', 'workspace'));
+    $this->assertFalse($schema->fieldExists('node_field_data', 'workspace'));
+    $this->assertFalse($schema->fieldExists('node_field_revision', 'workspace'));
+
+    // Check that the 'workspace_association' records have been migrated
+    // properly.
+    $wa_records = $database->select('workspace_association')->fields('workspace_association')->execute()->fetchAll(\PDO::FETCH_ASSOC);
+    $expected = [
+      [
+        'workspace' => 'stage',
+        'target_entity_type_id' => 'node',
+        'target_entity_id' => '1',
+        'target_entity_revision_id' => '2',
+      ],
+      [
+        'workspace' => 'dev',
+        'target_entity_type_id' => 'node',
+        'target_entity_id' => '8',
+        'target_entity_revision_id' => '10',
+      ],
+    ];
+    $this->assertEquals($expected, $wa_records);
+
+    // Check that the 'workspace_association' revisions has been migrated
+    // properly to the new 'workspace' revision metadata field.
+    $revisions = \Drupal::entityTypeManager()->getStorage('node')->loadMultipleRevisions([2, 9, 10]);
+    $this->assertEquals('stage', $revisions[2]->workspace->target_id);
+    $this->assertEquals('dev', $revisions[9]->workspace->target_id);
+    $this->assertEquals('dev', $revisions[10]->workspace->target_id);
+
+    // Check that the 'workspace_association' entity type has been uninstalled.
+    $this->assertNull($entity_definition_update_manager->getEntityType('workspace_association'));
+    $this->assertNull($entity_definition_update_manager->getFieldStorageDefinition('id', 'workspace_association'));
+    $this->assertNull(\Drupal::keyValue('entity.storage_schema.sql')->get('workspace_association.entity_schema_data'));
+
+    // Check that the 'workspace_association_revision' table has been removed.
+    $this->assertFalse($schema->tableExists('workspace_association_revision'));
+  }
+
+}
diff --git a/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php b/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php
index e652e3fc826b45a305b34a4e0b9247d5793d75b8..e4932eca3282882270c3f43f311130d27a082dec 100644
--- a/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php
+++ b/core/modules/workspaces/tests/src/Functional/WorkspacesUninstallTest.php
@@ -36,6 +36,12 @@ public function testUninstallingWorkspace() {
     $this->drupalPostForm(NULL, [], 'Uninstall');
     $session->pageTextContains('The selected modules have been uninstalled.');
     $session->pageTextNotContains('Workspaces');
+
+    $this->assertFalse($this->getDatabaseConnection()->schema()->fieldExists('node_revision', 'workspace'));
+
+    // Verify that the revision metadata key has been removed.
+    $revision_metadata_keys = \Drupal::entityDefinitionUpdateManager()->getEntityType('node')->get('revision_metadata_keys');
+    $this->assertArrayNotHasKey('workspace', $revision_metadata_keys);
   }
 
 }
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php
index b8065a15c8dd84def910ac37be9b364737c7c7e2..f8a4964eda303780adc9441df240ca04390ba86a 100644
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php
+++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceAccessTest.php
@@ -33,7 +33,6 @@ protected function setUp() {
     $this->installSchema('system', ['sequences']);
 
     $this->installEntitySchema('workspace');
-    $this->installEntitySchema('workspace_association');
     $this->installEntitySchema('user');
 
     // User 1.
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php
index d7d5710c57010e7ad5c58fe21a380a6fb739970a..c8b1705a7e078911b3e27aaac09cf9e5199e7f36 100644
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php
+++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceCRUDTest.php
@@ -7,7 +7,6 @@
 use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\Tests\user\Traits\UserCreationTrait;
 use Drupal\workspaces\Entity\Workspace;
-use Drupal\workspaces\Entity\WorkspaceAssociation;
 
 /**
  * Tests CRUD operations for workspaces.
@@ -19,6 +18,7 @@ class WorkspaceCRUDTest extends KernelTestBase {
   use UserCreationTrait;
   use NodeCreationTrait;
   use ContentTypeCreationTrait;
+  use WorkspaceTestTrait;
 
   /**
    * The entity type manager.
@@ -66,7 +66,7 @@ protected function setUp() {
     $this->installSchema('node', ['node_access']);
 
     $this->installEntitySchema('workspace');
-    $this->installEntitySchema('workspace_association');
+    $this->installSchema('workspaces', ['workspace_association']);
     $this->installEntitySchema('node');
 
     $this->installConfig(['filter', 'node', 'system']);
@@ -91,10 +91,8 @@ public function testDeletingWorkspaces() {
     ]);
     $this->setCurrentUser($admin);
 
-    /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
-    $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
-    /** @var \Drupal\node\NodeStorageInterface $node_storage */
-    $node_storage = $this->entityTypeManager->getStorage('node');
+    /** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
+    $workspace_association = \Drupal::service('workspaces.association');
 
     // Create a workspace with a very small number of associated node revisions.
     $workspace_1 = Workspace::create([
@@ -106,6 +104,12 @@ public function testDeletingWorkspaces() {
 
     $workspace_1_node_1 = $this->createNode(['status' => FALSE]);
     $workspace_1_node_2 = $this->createNode(['status' => FALSE]);
+
+    // The 'live' workspace should have 2 revisions now. The initial revision
+    // for each node.
+    $live_revisions = $this->getUnassociatedRevisions('node');
+    $this->assertCount(2, $live_revisions);
+
     for ($i = 0; $i < 4; $i++) {
       $workspace_1_node_1->setNewRevision(TRUE);
       $workspace_1_node_1->save();
@@ -114,9 +118,17 @@ public function testDeletingWorkspaces() {
       $workspace_1_node_2->save();
     }
 
-    // The workspace should have 10 associated node revisions, 5 for each node.
-    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE);
-    $this->assertCount(10, $associated_revisions['node']);
+    // The workspace should now track 2 nodes.
+    $tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id());
+    $this->assertCount(2, $tracked_entities['node']);
+
+    // There should still be 2 revisions associated with 'live'.
+    $live_revisions = $this->getUnassociatedRevisions('node');
+    $this->assertCount(2, $live_revisions);
+
+    // The other 8 revisions should be associated with 'workspace_1'.
+    $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node');
+    $this->assertCount(8, $associated_revisions);
 
     // Check that we are allowed to delete the workspace.
     $this->assertTrue($workspace_1->access('delete', $admin));
@@ -125,14 +137,17 @@ public function testDeletingWorkspaces() {
     // entities and all the node revisions have been deleted as well.
     $workspace_1->delete();
 
-    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_1->id(), TRUE);
+    // There are no more tracked entities in 'workspace_1'.
+    $tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id());
+    $this->assertEmpty($tracked_entities);
+
+    // There are no more revisions associated with 'workspace_1'.
+    $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node');
     $this->assertCount(0, $associated_revisions);
-    $node_revision_count = $node_storage
-      ->getQuery()
-      ->allRevisions()
-      ->count()
-      ->execute();
-    $this->assertEquals(0, $node_revision_count);
+
+    // There should still be 2 revisions associated with 'live'.
+    $live_revisions = $this->getUnassociatedRevisions('node');
+    $this->assertCount(2, $live_revisions);
 
     // Create another workspace, this time with a larger number of associated
     // node revisions so we can test the batch purge process.
@@ -149,16 +164,27 @@ public function testDeletingWorkspaces() {
       $workspace_2_node_1->save();
     }
 
-    // The workspace should have 60 associated node revisions.
-    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
-    $this->assertCount(60, $associated_revisions['node']);
+    // Now there is one entity tracked in 'workspace_2'.
+    $tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id());
+    $this->assertCount(1, $tracked_entities['node']);
+
+    // One revision of this entity is in 'live'.
+    $live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]);
+    $this->assertCount(1, $live_revisions);
 
-    // Delete the workspace and check that we still have 10 revision left to
+    // The other 59 are associated with 'workspace_2'.
+    $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
+    $this->assertCount(59, $associated_revisions);
+
+    // Delete the workspace and check that we still have 9 revision left to
     // delete.
     $workspace_2->delete();
+    $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
+    $this->assertCount(9, $associated_revisions);
 
-    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
-    $this->assertCount(10, $associated_revisions['node']);
+    // The live revision is also still there.
+    $live_revisions = $this->getUnassociatedRevisions('node', [$workspace_2_node_1->id()]);
+    $this->assertCount(1, $live_revisions);
 
     $workspace_deleted = \Drupal::state()->get('workspace.deleted');
     $this->assertCount(1, $workspace_deleted);
@@ -177,41 +203,94 @@ public function testDeletingWorkspaces() {
     // from the "workspace.delete" state entry.
     \Drupal::service('cron')->run();
 
-    $associated_revisions = $workspace_association_storage->getTrackedEntities($workspace_2->id(), TRUE);
+    $associated_revisions = $workspace_association->getTrackedEntities($workspace_2->id());
+    $this->assertCount(0, $associated_revisions);
+
+    // 'workspace_2 'is empty now.
+    $associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
     $this->assertCount(0, $associated_revisions);
-    $node_revision_count = $node_storage
-      ->getQuery()
-      ->allRevisions()
-      ->count()
-      ->execute();
-    $this->assertEquals(0, $node_revision_count);
+    $tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id());
+    $this->assertEmpty($tracked_entities);
+
+    // The 3 revisions in 'live' remain.
+    $live_revisions = $this->getUnassociatedRevisions('node');
+    $this->assertCount(3, $live_revisions);
 
     $workspace_deleted = \Drupal::state()->get('workspace.deleted');
     $this->assertCount(0, $workspace_deleted);
   }
 
   /**
-   * Tests workspace association validation.
-   *
-   * @covers \Drupal\workspaces\Entity\WorkspaceAssociation::validate
+   * Tests that deleting a workspace keeps its already published content.
    */
-  public function testWorkspaceAssociationValidation() {
-    $workspace = Workspace::create([
-      'id' => 'gibbon',
-      'label' => 'Gibbon',
+  public function testDeletingPublishedWorkspace() {
+    $admin = $this->createUser([
+      'administer nodes',
+      'create workspace',
+      'view any workspace',
+      'delete any workspace',
     ]);
-    $workspace->save();
-    $node = $this->createNode();
+    $this->setCurrentUser($admin);
 
-    $workspace_association = WorkspaceAssociation::create([
-      'workspace' => $workspace,
-      'target_entity_type_id' => $node->getEntityTypeId(),
-      'target_entity_id' => $node->id(),
-      'target_entity_revision_id' => $node->getRevisionId(),
+    $live_workspace = Workspace::create([
+      'id' => 'live',
+      'label' => 'Live',
     ]);
-
-    $violations = $workspace_association->validate();
-    $this->assertCount(0, $violations);
+    $live_workspace->save();
+    $workspace = Workspace::create([
+      'id' => 'stage',
+      'label' => 'Stage',
+    ]);
+    $workspace->save();
+    $this->workspaceManager->setActiveWorkspace($workspace);
+
+    // Create a new node in the 'stage' workspace
+    $node = $this->createNode(['status' => TRUE]);
+
+    // Create an additional workspace-specific revision for the node.
+    $node->setNewRevision(TRUE);
+    $node->save();
+
+    // The node should have 3 revisions now: a default and 2 pending ones.
+    $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3]);
+    $this->assertCount(3, $revisions);
+    $this->assertTrue($revisions[1]->isDefaultRevision());
+    $this->assertFalse($revisions[2]->isDefaultRevision());
+    $this->assertFalse($revisions[3]->isDefaultRevision());
+
+    // Publish the workspace, which should mark revision 3 as the default one
+    // and keep revision 2 as a 'source' draft revision.
+    $workspace->publish();
+    $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3]);
+    $this->assertFalse($revisions[1]->isDefaultRevision());
+    $this->assertFalse($revisions[2]->isDefaultRevision());
+    $this->assertTrue($revisions[3]->isDefaultRevision());
+
+    // Create two new workspace-revisions for the node.
+    $node->setNewRevision(TRUE);
+    $node->save();
+    $node->setNewRevision(TRUE);
+    $node->save();
+
+    // The node should now have 5 revisions.
+    $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3, 4, 5]);
+    $this->assertFalse($revisions[1]->isDefaultRevision());
+    $this->assertFalse($revisions[2]->isDefaultRevision());
+    $this->assertTrue($revisions[3]->isDefaultRevision());
+    $this->assertFalse($revisions[4]->isDefaultRevision());
+    $this->assertFalse($revisions[5]->isDefaultRevision());
+
+    // Delete the workspace and check that only the two new pending revisions
+    // were deleted by the workspace purging process.
+    $workspace->delete();
+
+    $revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3, 4, 5]);
+    $this->assertCount(3, $revisions);
+    $this->assertFalse($revisions[1]->isDefaultRevision());
+    $this->assertFalse($revisions[2]->isDefaultRevision());
+    $this->assertTrue($revisions[3]->isDefaultRevision());
+    $this->assertFalse(isset($revisions[4]));
+    $this->assertFalse(isset($revisions[5]));
   }
 
 }
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php
index 20a7efd04e30adc75d0cec4cf401851461c09d22..1530f3865830743097c2c761d203c4c3d3f68c8b 100644
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php
+++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceIntegrationTest.php
@@ -275,7 +275,7 @@ public function testWorkspaces() {
       ],
     ]);
     $test_scenarios['add_published_node_in_stage'] = $revision_state;
-    $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 6, 7]];
+    $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 7]];
 
     // Deploying 'stage' to 'live' should simply make the latest revisions in
     // 'stage' the default ones in 'live'.
@@ -365,8 +365,9 @@ public function testEntityQueryWithoutConditions() {
     $this->switchToWorkspace('stage');
 
     // Add a workspace-specific revision to a pre-existing node.
-    $this->nodes[1]->title->value = 'stage - 2 - r3 - published';
-    $this->nodes[1]->save();
+    $node = $this->entityTypeManager->getStorage('node')->load(2);
+    $node->title->value = 'stage - 2 - r3 - published';
+    $node->save();
 
     $query = $this->entityTypeManager->getStorage('node')->getQuery();
     $query->sort('nid');
@@ -809,7 +810,7 @@ protected function assertEntityQuery(array $expected_values, $entity_type_id) {
   }
 
   /**
-   * Checks the workspace_association entries for a test scenario.
+   * Checks the workspace_association records for a test scenario.
    *
    * @param array $expected
    *   An array of expected values, as defined in ::testWorkspaces().
@@ -817,10 +818,10 @@ protected function assertEntityQuery(array $expected_values, $entity_type_id) {
    *   The ID of the entity type that is being tested.
    */
   protected function assertWorkspaceAssociation(array $expected, $entity_type_id) {
-    /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
-    $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
+    /** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
+    $workspace_association = \Drupal::service('workspaces.association');
     foreach ($expected as $workspace_id => $expected_tracked_revision_ids) {
-      $tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE);
+      $tracked_entities = $workspace_association->getTrackedEntities($workspace_id, $entity_type_id);
       $tracked_revision_ids = isset($tracked_entities[$entity_type_id]) ? $tracked_entities[$entity_type_id] : [];
       $this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids));
     }
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceInternalResourceTest.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceInternalResourceTest.php
deleted file mode 100644
index 06201ce7446d31c4275a077b912fa3f7d7ca2e8e..0000000000000000000000000000000000000000
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceInternalResourceTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workspaces\Kernel;
-
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
-use Drupal\KernelTests\KernelTestBase;
-use Drupal\rest\Entity\RestResourceConfig;
-use Drupal\rest\RestResourceConfigInterface;
-
-/**
- * Tests REST module with internal workspace entity types.
- *
- * @group workspaces
- */
-class WorkspaceInternalResourceTest extends KernelTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['user', 'serialization', 'rest', 'workspaces'];
-
-  /**
-   * Tests enabling workspace associations for REST throws an exception.
-   *
-   * @see \Drupal\workspaces\Entity\WorkspaceAssociation
-   */
-  public function testCreateWorkspaceAssociationResource() {
-    $this->expectException(PluginNotFoundException::class);
-    $this->expectExceptionMessage('The "entity:workspace_association" plugin does not exist.');
-    RestResourceConfig::create([
-      'id' => 'entity.workspace_association',
-      'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
-      'configuration' => [
-        'methods' => ['GET'],
-        'formats' => ['json'],
-        'authentication' => ['cookie'],
-      ],
-    ])
-      ->enable()
-      ->save();
-  }
-
-}
diff --git a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php
index 13cff6a3dfebf3dac95c51b25ef7e5b188ed3cd6..50cf4704c396d5961c450169a0eb0fcdaefaf4a7 100644
--- a/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php
+++ b/core/modules/workspaces/tests/src/Kernel/WorkspaceTestTrait.php
@@ -35,7 +35,7 @@ protected function initializeWorkspacesModule() {
     $this->workspaceManager = \Drupal::service('workspaces.manager');
 
     $this->installEntitySchema('workspace');
-    $this->installEntitySchema('workspace_association');
+    $this->installSchema('workspaces', ['workspace_association']);
 
     // Create two workspaces by default, 'live' and 'stage'.
     $this->workspaces['live'] = Workspace::create(['id' => 'live']);
@@ -64,4 +64,33 @@ protected function switchToWorkspace($workspace_id) {
     \Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
   }
 
+  /**
+   * Returns all the revisions which are not associated with any workspace.
+   *
+   * @param string $entity_type_id
+   *   An entity type ID to find revisions for.
+   * @param int[]|string[]|null $entity_ids
+   *   (optional) An array of entity IDs to filter the results by. Defaults to
+   *   NULL.
+   *
+   * @return array
+   *   An array of entity IDs, keyed by revision IDs.
+   */
+  protected function getUnassociatedRevisions($entity_type_id, $entity_ids = NULL) {
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+
+    $query = \Drupal::entityTypeManager()
+      ->getStorage($entity_type_id)
+      ->getQuery()
+      ->allRevisions()
+      ->accessCheck(FALSE)
+      ->notExists($entity_type->get('revision_metadata_keys')['workspace']);
+
+    if ($entity_ids) {
+      $query->condition($entity_type->getKey('id'), $entity_ids, 'IN');
+    }
+
+    return $query->execute();
+  }
+
 }
diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install
index c6ac304b6db36a477bb20594639e99b0cbcdb162..2bfc003f0e102871d0d8af9ff9571b4cc9457988 100644
--- a/core/modules/workspaces/workspaces.install
+++ b/core/modules/workspaces/workspaces.install
@@ -5,6 +5,8 @@
  * Contains install, update and uninstall functions for the Workspaces module.
  */
 
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\workspaces\Entity\Workspace;
 
 /**
@@ -32,6 +34,27 @@ function workspaces_requirements($phase) {
   return $requirements;
 }
 
+/**
+ * Implements hook_module_preinstall().
+ */
+function workspaces_module_preinstall($module) {
+  if ($module !== 'workspaces') {
+    return;
+  }
+
+  /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */
+  $workspace_manager = \Drupal::service('workspaces.manager');
+  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) {
+    $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
+    if ($workspace_manager->isEntityTypeSupported($entity_type)) {
+      $revision_metadata_keys['workspace'] = 'workspace';
+      $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+      $entity_definition_update_manager->updateEntityType($entity_type);
+    }
+  }
+}
+
 /**
  * Implements hook_install().
  */
@@ -61,3 +84,83 @@ function workspaces_install() {
     'uid' => $owner_id,
   ])->save();
 }
+
+/**
+ * Implements hook_schema().
+ */
+function workspaces_schema() {
+  $schema['workspace_association'] = [
+    'description' => 'Stores the association between entity revisions and their workspace.',
+    'fields' => [
+      'workspace' => [
+        'type' => 'varchar_ascii',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The workspace ID.',
+      ],
+      'target_entity_type_id' => [
+        'type' => 'varchar_ascii',
+        'length' => EntityTypeInterface::ID_MAX_LENGTH,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The ID of the associated entity type.',
+      ],
+      'target_entity_id' => [
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The ID of the associated entity.',
+      ],
+      'target_entity_revision_id' => [
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The revision ID of the associated entity.',
+      ],
+    ],
+    'indexes' => [
+      'target_entity_revision_id' => ['target_entity_revision_id'],
+    ],
+    'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'],
+  ];
+
+  return $schema;
+}
+
+/**
+ * Add the 'workspace' revision metadata field on all supported entity types.
+ */
+function workspaces_update_8801() {
+  /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */
+  $workspace_manager = \Drupal::service('workspaces.manager');
+  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type_id => $entity_type) {
+    if ($workspace_manager->isEntityTypeSupported($entity_type)) {
+      $revision_metadata_keys = $entity_type->get('revision_metadata_keys');
+
+      if (!isset($revision_metadata_keys['workspace'])) {
+        // Bail out if there's an existing field called 'workspace'.
+        if ($entity_definition_update_manager->getFieldStorageDefinition('workspace', $entity_type_id)) {
+          throw new \RuntimeException("An existing 'workspace' field was found for the '$entity_type_id' entity type. Set the 'workspace' revision metadata key to use a different field name and run this update function again.");
+        }
+
+        $revision_metadata_keys['workspace'] = 'workspace';
+        $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+        $entity_definition_update_manager->updateEntityType($entity_type);
+      }
+
+      $field_storage = BaseFieldDefinition::create('entity_reference')
+        ->setLabel(t('Workspace'))
+        ->setDescription(t('Indicates the workspace that this revision belongs to.'))
+        ->setSetting('target_type', 'workspace')
+        ->setInternal(TRUE)
+        ->setTranslatable(FALSE)
+        ->setRevisionable(TRUE);
+
+      $entity_definition_update_manager->installFieldStorageDefinition($revision_metadata_keys['workspace'], $entity_type_id, 'workspaces', $field_storage);
+    }
+  }
+
+  return t("The 'workspace' revision metadata field has been installed.");
+}
diff --git a/core/modules/workspaces/workspaces.module b/core/modules/workspaces/workspaces.module
index d47985b223ded51e652cebe5be2d964adc8bbe70..c3b0d0776274589f7b94c4621be88363b90eee45 100644
--- a/core/modules/workspaces/workspaces.module
+++ b/core/modules/workspaces/workspaces.module
@@ -8,6 +8,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Core\Entity\EntityFormInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -66,6 +67,15 @@ function workspaces_field_info_alter(&$definitions) {
     ->fieldInfoAlter($definitions);
 }
 
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function workspaces_entity_base_field_info(EntityTypeInterface $entity_type) {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->entityBaseFieldInfo($entity_type);
+}
+
 /**
  * Implements hook_entity_preload().
  */
diff --git a/core/modules/workspaces/workspaces.post_update.php b/core/modules/workspaces/workspaces.post_update.php
index 0df5add2cb8dde0a926effbd8d899c6e322c3d27..ac45f3f93285cf1b9b61a7376beab24725123626 100644
--- a/core/modules/workspaces/workspaces.post_update.php
+++ b/core/modules/workspaces/workspaces.post_update.php
@@ -5,6 +5,11 @@
  * Post update functions for the Workspaces module.
  */
 
+use Drupal\Core\Entity\ContentEntityNullStorage;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+use Drupal\Core\Site\Settings;
+
 /**
  * Clear caches due to access changes.
  */
@@ -19,3 +24,122 @@ function workspaces_post_update_remove_default_workspace() {
     $workspace->delete();
   }
 }
+
+/**
+ * Move the workspace association data to an entity field and a custom table.
+ */
+function workspaces_post_update_move_association_data(&$sandbox) {
+  $database = \Drupal::database();
+  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $entity_type = $entity_definition_update_manager->getEntityType('workspace_association');
+
+  // We can't migrate the workspace association data if the entity type is not
+  // using its default storage.
+  if ($entity_type->getHandlerClasses()['storage'] !== 'Drupal\workspaces\WorkspaceAssociationStorage') {
+    return;
+  }
+
+  // Since the custom storage class doesn't exist anymore, we have to use core's
+  // default storage.
+  $entity_type->setStorageClass(SqlContentEntityStorage::class);
+
+  // If 'progress' is not set, this will be the first run of the batch.
+  if (!isset($sandbox['progress'])) {
+    $sandbox['progress'] = 0;
+    $sandbox['current_id'] = -1;
+
+    // Create a temporary table for the new workspace_association index.
+    $schema = [
+      'description' => 'Stores the association between entity revisions and their workspace.',
+      'fields' => [
+        'workspace' => [
+          'type' => 'varchar_ascii',
+          'length' => 128,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'The workspace ID.',
+        ],
+        'target_entity_type_id' => [
+          'type' => 'varchar_ascii',
+          'length' => EntityTypeInterface::ID_MAX_LENGTH,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'The ID of the associated entity type.',
+        ],
+        'target_entity_id' => [
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'The ID of the associated entity.',
+        ],
+        'target_entity_revision_id' => [
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'The revision ID of the associated entity.',
+        ],
+      ],
+      'indexes' => [
+        'target_entity_revision_id' => ['target_entity_revision_id'],
+      ],
+      'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'],
+    ];
+    if ($database->schema()->tableExists('tmp_workspace_association')) {
+      $database->schema()->dropTable('tmp_workspace_association');
+    }
+    $database->schema()->createTable('tmp_workspace_association', $schema);
+
+    // Copy all the data from the base table of the 'workspace_association'
+    // entity type to the temporary association table.
+    $select = $database->select($entity_type->getBaseTable())
+      ->fields($entity_type->getBaseTable(), ['workspace', 'target_entity_type_id', 'target_entity_id', 'target_entity_revision_id']);
+    $database->insert('tmp_workspace_association')->from($select)->execute();
+  }
+
+  $table_name = $entity_type->getRevisionTable();
+  $revision_field_name = 'revision_id';
+
+  // Get the next entity association revision records to migrate.
+  $step_size = Settings::get('entity_update_batch_size', 50);
+  $workspace_association_records = $database->select($table_name, 't')
+    ->condition("t.$revision_field_name", $sandbox['current_id'], '>')
+    ->fields('t')
+    ->orderBy($revision_field_name, 'ASC')
+    ->range(0, $step_size)
+    ->execute()
+    ->fetchAll();
+
+  foreach ($workspace_association_records as $record) {
+    // Set the workspace reference on the tracked entity revision.
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
+    $revision = $entity_type_manager->getStorage($record->target_entity_type_id)->loadRevision($record->target_entity_revision_id);
+    $revision->set('workspace', $record->workspace);
+    $revision->setSyncing(TRUE);
+    $revision->save();
+
+    $sandbox['progress']++;
+    $sandbox['current_id'] = $record->{$revision_field_name};
+  }
+
+  // Get an updated count of workspace_association revisions that still need to
+  // be migrated to the new storage.
+  $missing = $database->select($table_name, 't')
+    ->condition("t.$revision_field_name", $sandbox['current_id'], '>')
+    ->orderBy($revision_field_name, 'ASC')
+    ->countQuery()
+    ->execute()
+    ->fetchField();
+  $sandbox['#finished'] = $missing ? $sandbox['progress'] / ($sandbox['progress'] + (int) $missing) : 1;
+
+  // Uninstall the 'workspace_association' entity type and rename the temporary
+  // table.
+  if ($sandbox['#finished'] == 1) {
+    $entity_type->setStorageClass(ContentEntityNullStorage::class);
+    $entity_definition_update_manager->uninstallEntityType($entity_type);
+    $database->schema()->dropTable('workspace_association');
+    $database->schema()->dropTable('workspace_association_revision');
+
+    $database->schema()->renameTable('tmp_workspace_association', 'workspace_association');
+  }
+}
diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml
index 9823d5ce2ec9125ac961abb408f1f66f8a3ca9fc..45f1d50746141b382da9faf3884bdcdf826716a1 100644
--- a/core/modules/workspaces/workspaces.services.yml
+++ b/core/modules/workspaces/workspaces.services.yml
@@ -1,12 +1,17 @@
 services:
   workspaces.manager:
     class: Drupal\workspaces\WorkspaceManager
-    arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver']
+    arguments: ['@request_stack', '@entity_type.manager', '@entity.memory_cache', '@current_user', '@state', '@logger.channel.workspaces', '@class_resolver', '@workspaces.association']
     tags:
       - { name: service_id_collector, tag: workspace_negotiator }
   workspaces.operation_factory:
     class: Drupal\workspaces\WorkspaceOperationFactory
-    arguments: ['@entity_type.manager', '@database', '@workspaces.manager']
+    arguments: ['@entity_type.manager', '@database', '@workspaces.manager', '@workspaces.association']
+  workspaces.association:
+    class: Drupal\workspaces\WorkspaceAssociation
+    arguments: ['@database', '@entity_type.manager']
+    tags:
+      - { name: backend_overridable }
 
   workspaces.negotiator.session:
     class: Drupal\workspaces\Negotiator\SessionWorkspaceNegotiator
@@ -25,6 +30,12 @@ services:
     tags:
       - { name: access_check, applies_to: _has_active_workspace }
 
+  workspaces.entity_schema_listener:
+    class: Drupal\workspaces\EventSubscriber\EntitySchemaSubscriber
+    arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.manager']
+    tags:
+      - { name: 'event_subscriber' }
+
   cache_context.workspace:
     class: Drupal\workspaces\WorkspaceCacheContext
     arguments: ['@workspaces.manager']