 .../Drupal/Core/Config/Entity/ConfigEntityBase.php |    3 ++
 core/lib/Drupal/Core/Entity/Entity.php             |   31 ++++++++++++++++
 core/lib/Drupal/Core/Entity/EntityInterface.php    |   19 ++++++++++
 core/lib/Drupal/Core/Entity/EntityViewBuilder.php  |    7 ++--
 .../comment/Tests/Entity/CommentLockTest.php       |    8 ++++
 core/modules/filter/filter.module                  |    1 -
 .../lib/Drupal/filter/Entity/FilterFormat.php      |    8 +---
 .../node/lib/Drupal/node/Entity/NodeType.php       |   11 ------
 .../system/lib/Drupal/system/Entity/Menu.php       |   19 ----------
 core/modules/user/lib/Drupal/user/Entity/Role.php  |   13 -------
 .../user/lib/Drupal/user/PermissionsHash.php       |    2 +-
 .../modules/views/lib/Drupal/views/Entity/View.php |   14 +------
 .../views_ui/lib/Drupal/views_ui/ViewUI.php        |   14 +++++++
 .../Config/Entity/ConfigEntityBaseUnitTest.php     |   16 ++++++++
 .../Drupal/Tests/Core/Entity/EntityUnitTest.php    |   39 ++++++++++++++++++--
 15 files changed, 132 insertions(+), 73 deletions(-)

diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
index 492876a..9d01118 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Config\Entity;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Entity;
 use Drupal\Core\Config\ConfigDuplicateUUIDException;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
@@ -155,6 +156,8 @@ public function enable() {
    * {@inheritdoc}
    */
   public function disable() {
+    // An entity was disabled, invalidate its own cache tag.
+    Cache::invalidateTags(array($this->entityTypeId => array($this->id())));
     return $this->setStatus(FALSE);
   }
 
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 2bddb37..9dba0ec 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\DependencyInjection\DependencySerialization;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AccountInterface;
@@ -347,9 +349,18 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
    */
   public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
     $this->onSaveOrDelete();
+
+    // An entity was created or updated: invalidate its list cache tags. (An
+    // updated entity may start to appear in a listing because it now meets that
+    // listing's filtering requirements. A newly created entity may start to
+    // appear in listings because it did not exist before.)
+    $tags = $this->getListCacheTags();
     if ($update) {
+      // An existing entity was updated, also invalidate its unique cache tag.
+      $tags = NestedArray::mergeDeep($tags, $this->getCacheTag());
       $this->onUpdateBundleEntity();
     }
+    Cache::invalidateTags($tags);
   }
 
   /**
@@ -377,6 +388,11 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
     foreach ($entities as $entity) {
       $entity->onSaveOrDelete();
     }
+
+    // An entity was deleted, invalidate its own cache tag.
+    $ids = array_keys($entities);
+    $entity_type_id = $entities[$ids[0]]->getEntityTypeId();
+    Cache::invalidateTags(array($entity_type_id => $ids));
   }
 
   /**
@@ -393,6 +409,21 @@ public function referencedEntities() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function getCacheTag() {
+    return array($this->entityTypeId => array($this->id()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getListCacheTags() {
+    // @todo Add bundle-specific listing cache tag? https://drupal.org/node/2145751
+    return array($this->entityTypeId . 's' => TRUE);
+  }
+
+  /**
    * Acts on an entity after it was saved or deleted.
    */
   protected function onSaveOrDelete() {
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index ce4909a..a2a43ae 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -319,4 +319,23 @@ public function getOriginalId();
    */
   public function setOriginalId($id);
 
+  /**
+   * The unique cache tag associated with this entity.
+   *
+   * @return array
+   *   An array of cache tags.
+   */
+  public function getCacheTag();
+
+  /**
+   * The list cache tags associated with this entity.
+   *
+   * Enables code listing entities of this type to ensure that newly created
+   * entities show up immediately.
+   *
+   * @return array
+   *   An array of cache tags.
+   */
+  public function getListCacheTags();
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 2e48ecc..f57ae78 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -149,6 +149,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
       '#cache' => array(
         'tags' =>  array(
           $this->entityTypeId . '_view' => TRUE,
+          $this->entityTypeId . '_view_' . $entity->bundle() => TRUE,
           $this->entityTypeId => array($entity->id()),
         ),
       )
@@ -276,14 +277,12 @@ public function resetCache(array $entities = NULL) {
     if (isset($entities)) {
       $tags = array();
       foreach ($entities as $entity) {
-        $id = $entity->id();
-        $tags[$this->entityTypeId][$id] = $id;
         $tags[$this->entityTypeId . '_view_' . $entity->bundle()] = TRUE;
       }
-      Cache::deleteTags($tags);
+      Cache::invalidateTags($tags);
     }
     else {
-      Cache::deleteTags(array($this->entityTypeId . '_view' => TRUE));
+      Cache::invalidateTags(array($this->entityTypeId . '_view' => TRUE));
     }
   }
 
diff --git a/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php b/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php
index 1d54be4..48010ec 100644
--- a/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php
+++ b/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php
@@ -35,6 +35,8 @@ public function testLocks() {
     $container = new ContainerBuilder();
     $container->set('module_handler', $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'));
     $container->set('current_user', $this->getMock('Drupal\Core\Session\AccountInterface'));
+    $container->set('cache.test', $this->getMock('Drupal\Core\Cache\CacheBackendInterface'));
+    $container->setParameter('cache_bins', array('cache.test' => 'test'));
     $container->register('request', 'Symfony\Component\HttpFoundation\Request');
     $lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface');
     $cid = 2;
@@ -78,6 +80,12 @@ public function testLocks() {
       ->method('get')
       ->with('status')
       ->will($this->returnValue((object) array('value' => NULL)));
+    $comment->expects($this->once())
+      ->method('getCacheTag')
+      ->will($this->returnValue(array('comment' => array($cid))));
+    $comment->expects($this->once())
+      ->method('getListCacheTags')
+      ->will($this->returnValue(array('comments' => TRUE)));
     $storage_controller = $this->getMock('Drupal\comment\CommentStorageControllerInterface');
     $comment->preSave($storage_controller);
     $comment->postSave($storage_controller);
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 9c166e5..75a8b36 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -195,7 +195,6 @@ function filter_formats(AccountInterface $account = NULL) {
  * @see filter_formats()
  */
 function filter_formats_reset() {
-  Cache::deleteTags(array('filter_formats' => TRUE));
   drupal_static_reset('filter_formats');
 }
 
diff --git a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php
index 8e0aadd..cf7673c 100644
--- a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php
+++ b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\filter\Entity;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Config\Entity\EntityWithPluginBagInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
@@ -196,7 +195,6 @@ public function disable() {
 
     // Clear the filter cache whenever a text format is disabled.
     filter_formats_reset();
-    Cache::deleteTags(array('filter_format' => $this->format));
 
     return $this;
   }
@@ -234,11 +232,7 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
     // Clear the static caches of filter_formats() and others.
     filter_formats_reset();
 
-    if ($update) {
-      // Clear the filter cache whenever a text format is updated.
-      Cache::deleteTags(array('filter_format' => $this->id()));
-    }
-    else {
+    if (!$update) {
       // Default configuration of modules and installation profiles is allowed
       // to specify a list of user roles to grant access to for the new format;
       // apply the defined user role permissions when a new format is inserted
diff --git a/core/modules/node/lib/Drupal/node/Entity/NodeType.php b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
index b187c75..bfe7014 100644
--- a/core/modules/node/lib/Drupal/node/Entity/NodeType.php
+++ b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
@@ -8,7 +8,6 @@
 namespace Drupal\node\Entity;
 
 use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\node\NodeTypeInterface;
@@ -157,9 +156,6 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
     parent::postSave($storage_controller, $update);
 
     if (!$update) {
-      // Clear the node type cache, so the new type appears.
-      Cache::deleteTags(array('node_types' => TRUE));
-
       entity_invoke_bundle_hook('create', 'node', $this->id());
 
       // Create a body if the create_body property is true and we're not in
@@ -170,9 +166,6 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
       }
     }
     elseif ($this->getOriginalId() != $this->id()) {
-      // Clear the node type cache to reflect the rename.
-      Cache::deleteTags(array('node_types' => TRUE));
-
       $update_count = node_type_update_nodes($this->getOriginalId(), $this->id());
       if ($update_count) {
         drupal_set_message(format_plural($update_count,
@@ -185,10 +178,6 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
       }
       entity_invoke_bundle_hook('rename', 'node', $this->getOriginalId(), $this->id());
     }
-    else {
-      // Invalidate the cache tag of the updated node type only.
-      Cache::invalidateTags(array('node_type' => $this->id()));
-    }
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Entity/Menu.php b/core/modules/system/lib/Drupal/system/Entity/Menu.php
index 8dadc1c..64a8e76 100644
--- a/core/modules/system/lib/Drupal/system/Entity/Menu.php
+++ b/core/modules/system/lib/Drupal/system/Entity/Menu.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\system\Entity;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\system\MenuInterface;
@@ -81,22 +80,4 @@ public function isLocked() {
     return (bool) $this->locked;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
-    parent::postSave($storage_controller, $update);
-
-    Cache::invalidateTags(array('menu' => $this->id()));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) {
-    parent::postDelete($storage_controller, $entities);
-
-    Cache::invalidateTags(array('menu' => array_keys($entities)));
-  }
-
 }
diff --git a/core/modules/user/lib/Drupal/user/Entity/Role.php b/core/modules/user/lib/Drupal/user/Entity/Role.php
index 0ce5186..92c53a5 100644
--- a/core/modules/user/lib/Drupal/user/Entity/Role.php
+++ b/core/modules/user/lib/Drupal/user/Entity/Role.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\user\Entity;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\user\RoleInterface;
@@ -134,20 +133,8 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
   public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
     parent::postSave($storage_controller, $update);
 
-    Cache::invalidateTags(array('role' => $this->id()));
     // Clear render cache.
     entity_render_cache_clear();
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) {
-    parent::postDelete($storage_controller, $entities);
-
-    $ids = array_keys($entities);
-    $storage_controller->deleteRoleReferences($ids);
-    Cache::invalidateTags(array('role' => $ids));
-  }
-
 }
diff --git a/core/modules/user/lib/Drupal/user/PermissionsHash.php b/core/modules/user/lib/Drupal/user/PermissionsHash.php
index 3c6fe87..1fed13e 100644
--- a/core/modules/user/lib/Drupal/user/PermissionsHash.php
+++ b/core/modules/user/lib/Drupal/user/PermissionsHash.php
@@ -58,7 +58,7 @@ public function generate(AccountInterface $account) {
     }
     else {
       $permissions_hash = $this->doGenerate($sorted_roles);
-      $this->cache->set("user_permissions_hash:$role_list", $permissions_hash, Cache::PERMANENT, array('role' => $sorted_roles));
+      $this->cache->set("user_permissions_hash:$role_list", $permissions_hash, Cache::PERMANENT, array('user_role' => $sorted_roles));
     }
 
     return $permissions_hash;
diff --git a/core/modules/views/lib/Drupal/views/Entity/View.php b/core/modules/views/lib/Drupal/views/Entity/View.php
index 097059a..712e719 100644
--- a/core/modules/views/lib/Drupal/views/Entity/View.php
+++ b/core/modules/views/lib/Drupal/views/Entity/View.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\views\Entity;
 
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\views\Views;
@@ -305,10 +304,7 @@ public function calculateDependencies() {
   public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
     parent::postSave($storage_controller, $update);
 
-    // Clear cache tags for this view.
     // @todo Remove if views implements a view_builder controller.
-    $id = $this->id();
-    Cache::deleteTags(array('view' => array($id => $id)));
     views_invalidate_cache();
   }
 
@@ -359,17 +355,9 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
     parent::postDelete($storage_controller, $entities);
 
     $tempstore = \Drupal::service('user.tempstore')->get('views');
-    $tags = array();
-
     foreach ($entities as $entity) {
-      $id = $entity->id();
-      $tempstore->delete($id);
-      $tags['view'][$id] = $id;
+      $tempstore->delete($entity->id());
     }
-
-    // Clear cache tags for these views.
-    // @todo Remove if views implements a view_builder controller.
-    Cache::deleteTags($tags);
   }
 
   /**
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
index 009e8e4..80fe7b3 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
@@ -1212,4 +1212,18 @@ public function calculateDependencies() {
   public function getConfigDependencyName() {
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTag() {
+    $this->storage->getCacheTag();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getListCacheTags() {
+    $this->storage->getListCacheTags();
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
index 9deeaa6..97ba84b 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
@@ -78,6 +78,13 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
   protected $id;
 
   /**
+   * The mocked cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheBackend;
+
+  /**
    * {@inheritdoc}
    */
   public static function getInfo() {
@@ -119,10 +126,14 @@ public function setUp() {
       ->with('en')
       ->will($this->returnValue(new Language(array('id' => 'en'))));
 
+    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
     $container->set('uuid', $this->uuid);
     $container->set('language_manager', $this->languageManager);
+    $container->set('cache.test', $this->cacheBackend);
+    $container->setParameter('cache_bins', array('cache.test' => 'test'));
     \Drupal::setContainer($container);
 
     $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Config\Entity\ConfigEntityBase', array($values, $this->entityTypeId));
@@ -325,6 +336,10 @@ public function testEnable() {
    * @depends testSetStatus
    */
   public function testDisable() {
+    $this->cacheBackend->expects($this->once())
+      ->method('invalidateTags')
+      ->with(array($this->entityTypeId => array($this->id)));
+
     $this->entity->setStatus(TRUE);
     $this->assertSame($this->entity, $this->entity->disable());
     $this->assertFalse($this->entity->status());
@@ -415,6 +430,7 @@ public function testToArray() {
       $this->assertSame($this->entity->get($name), $properties[$name]);
     }
   }
+
 }
 
 class TestConfigurablePlugin extends PluginBase implements ConfigurablePluginInterface {
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
index be09ad8..7310f54 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
@@ -68,6 +68,13 @@ class EntityUnitTest extends UnitTestCase {
   protected $languageManager;
 
   /**
+   * The mocked cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheBackend;
+
+  /**
    * The entity values.
    *
    * @var array
@@ -112,11 +119,14 @@ public function setUp() {
       ->with('en')
       ->will($this->returnValue(new Language(array('id' => 'en'))));
 
+    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
     $container->set('uuid', $this->uuid);
     $container->set('language_manager', $this->languageManager);
-
+    $container->set('cache.test', $this->cacheBackend);
+    $container->setParameter('cache_bins', array('cache.test' => 'test'));
     \Drupal::setContainer($container);
 
     $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\Entity', array($this->values, $this->entityTypeId));
@@ -280,9 +290,26 @@ public function testPreSave() {
    * @covers ::postSave
    */
   public function testPostSave() {
+    $this->cacheBackend->expects($this->at(0))
+      ->method('invalidateTags')
+      ->with(array(
+        $this->entityTypeId . 's' => TRUE, // List cache tag.
+      ));
+    $this->cacheBackend->expects($this->at(1))
+      ->method('invalidateTags')
+      ->with(array(
+        $this->entityTypeId . 's' => TRUE, // List cache tag.
+        $this->entityTypeId => array($this->values['id']), // Own cache tag.
+      ));
+
     // This method is internal, so check for errors on calling it only.
     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageControllerInterface');
-    $this->entity->postSave($storage);
+
+    // A creation should trigger the invalidation of the "list" cache tag.
+    $this->entity->postSave($storage, FALSE);
+    // An update should trigger the invalidation of both the "list" and the
+    // "own" cache tags.
+    $this->entity->postSave($storage, TRUE);
   }
 
   /**
@@ -317,16 +344,20 @@ public function testPreDelete() {
    * @covers ::postDelete
    */
   public function testPostDelete() {
+    $this->cacheBackend->expects($this->once())
+      ->method('invalidateTags')
+      ->with(array($this->entityTypeId => array($this->values['id'])));
     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageControllerInterface');
 
     $entity = $this->getMockBuilder('\Drupal\Core\Entity\Entity')
+      ->setConstructorArgs(array($this->values, $this->entityTypeId))
       ->setMethods(array('onSaveOrDelete'))
-      ->disableOriginalConstructor()
       ->getMock();
     $entity->expects($this->once())
       ->method('onSaveOrDelete');
 
-    $this->entity->postDelete($storage, array($entity));
+    $entities = array($this->values['id'] => $entity);
+    $this->entity->postDelete($storage, $entities);
   }
 
   /**
