diff --git a/core/core.services.yml b/core/core.services.yml
index 55d5b34..bb3620a 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -150,7 +150,7 @@ services:
       - { name: persist }
   plugin.manager.entity:
     class: Drupal\Core\Entity\EntityManager
-    arguments: ['@container.namespaces', '@service_container']
+    arguments: ['@container.namespaces', '@service_container', '@module_handler', '@cache.cache', '@language_manager']
   plugin.manager.archiver:
     class: Drupal\Core\Archiver\ArchiverManager
     arguments: ['@container.namespaces']
diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 1912fe1..e8503dc 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -473,26 +473,26 @@ function hook_entity_form_display_alter(\Drupal\entity\Plugin\Core\Entity\Entity
 }
 
 /**
- * Define custom entity properties.
+ * Define custom entity fields.
  *
  * @param string $entity_type
- *   The entity type for which to define entity properties.
+ *   The entity type for which to define entity fields.
  *
  * @return array
- *   An array of property information having the following optional entries:
- *   - definitions: An array of property definitions to add all entities of this
- *     type, keyed by property name. See
- *     Drupal\Core\TypedData\TypedDataManager::create() for a list of supported
- *     keys in property definitions.
- *   - optional: An array of property definitions for optional properties keyed
- *     by property name. Optional properties are properties that only exist for
- *     certain bundles of the entity type.
- *   - bundle map: An array keyed by bundle name containing the names of
- *     optional properties that entities of this bundle have.
- *
- * @see Drupal\Core\TypedData\TypedDataManager::create()
+ *   An array of entity field information having the following optional entries:
+ *   - definitions: An array of field definitions to add all entities of this
+ *     type, keyed by field name. See
+ *     \Drupal\Core\Entity\EntityManager::getFieldDefinitions() for a list of
+ *     supported keys in field definitions.
+ *   - optional: An array of field definitions for optional entity fields, keyed
+ *     by field name. Optional fields are fields that only exist for certain
+ *     bundles of the entity type.
+ *   - bundle map: An array keyed by bundle name, containing the names of
+ *     optional fields that entities of this bundle have.
+ *
  * @see hook_entity_field_info_alter()
- * @see Drupal\Core\Entity\StorageControllerInterface::getPropertyDefinitions()
+ * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions()
+ * @see \Drupal\Core\TypedData\TypedDataManager::create()
  */
 function hook_entity_field_info($entity_type) {
   if (mymodule_uses_entity_type($entity_type)) {
@@ -521,12 +521,12 @@ function hook_entity_field_info($entity_type) {
 }
 
 /**
- * Alter defined entity properties.
+ * Alter defined entity fields.
  *
  * @param array $info
- *   The property info array as returned by hook_entity_field_info().
+ *   The entity field info array as returned by hook_entity_field_info().
  * @param string $entity_type
- *   The entity type for which entity properties are defined.
+ *   The entity type for which entity fields are defined.
  *
  * @see hook_entity_field_info()
  */
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 1ce4456..3d5837d 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -519,9 +519,10 @@ protected function postDelete($entities) {
   }
 
   /**
-   * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions().
+   * {@inheritdoc}
    */
-  public function getFieldDefinitions(array $constraints) {
+  public function baseFieldDefinitions() {
+    // @todo: Define abstract once all entity types have been converted.
     return array();
   }
 
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index bb8e2bb..6b70b6b 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -51,22 +51,6 @@ class DatabaseStorageController implements EntityStorageControllerInterface, Ent
   protected $entityInfo;
 
   /**
-   * An array of field information, i.e. containing definitions.
-   *
-   * @var array
-   *
-   * @see hook_entity_field_info()
-   */
-  protected $entityFieldInfo;
-
-  /**
-   * Static cache of field definitions per bundle.
-   *
-   * @var array
-   */
-  protected $fieldDefinitions;
-
-  /**
    * Additional arguments to pass to hook_TYPE_load().
    *
    * Set before calling Drupal\Core\Entity\DatabaseStorageController::attachLoad().
@@ -700,64 +684,10 @@ protected function invokeHook($hook, EntityInterface $entity) {
   }
 
   /**
-   * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions().
-   */
-  public function getFieldDefinitions(array $constraints) {
-    if (!isset($this->entityFieldInfo)) {
-      // First, try to load from cache.
-      $cid = 'entity_field_definitions:' . $this->entityType . ':' . language(Language::TYPE_INTERFACE)->langcode;
-      if ($cache = cache()->get($cid)) {
-        $this->entityFieldInfo = $cache->data;
-      }
-      else {
-        $this->entityFieldInfo = array(
-          'definitions' => $this->baseFieldDefinitions(),
-          // Contains definitions of optional (per-bundle) fields.
-          'optional' => array(),
-          // An array keyed by bundle name containing the optional fields added
-          // by the bundle.
-          'bundle map' => array(),
-        );
-
-        // Invoke hooks.
-        $result = module_invoke_all($this->entityType . '_property_info');
-        $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result);
-        $result = module_invoke_all('entity_field_info', $this->entityType);
-        $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result);
-
-        $hooks = array('entity_field_info', $this->entityType . '_property_info');
-        drupal_alter($hooks, $this->entityFieldInfo, $this->entityType);
-
-        // Enforce fields to be multiple by default.
-        foreach ($this->entityFieldInfo['definitions'] as &$definition) {
-          $definition['list'] = TRUE;
-        }
-        foreach ($this->entityFieldInfo['optional'] as &$definition) {
-          $definition['list'] = TRUE;
-        }
-        cache()->set($cid, $this->entityFieldInfo, CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
-      }
-    }
-
-    $bundle = !empty($constraints['Bundle']) ? $constraints['Bundle'] : FALSE;
-
-    // Add in per-bundle fields.
-    if (!isset($this->fieldDefinitions[$bundle])) {
-      $this->fieldDefinitions[$bundle] = $this->entityFieldInfo['definitions'];
-
-      if ($bundle && isset($this->entityFieldInfo['bundle map'][$bundle])) {
-        $this->fieldDefinitions[$bundle] += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$bundle]));
-      }
-    }
-    return $this->fieldDefinitions[$bundle];
-  }
-
-  /**
-   * Defines the base properties of the entity type.
-   *
-   * @todo: Define abstract once all entity types have been converted.
+   * {@inheritdoc}
    */
   public function baseFieldDefinitions() {
+    // @todo: Define abstract once all entity types have been converted.
     return array();
   }
 
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index 91ec62d..234cccf 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -301,7 +301,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
       }
 
       $data = $query->execute();
-      $field_definition = $this->getFieldDefinitions(array());
+      $field_definition = \Drupal::entityManager()->getFieldDefinitions($this->entityType);
       if ($this->revisionTable) {
         $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
       }
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 24e7890..3c182e2 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -9,6 +9,9 @@
 
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManager;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Plugin\Discovery\AlterDecorator;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
@@ -48,6 +51,43 @@ class EntityManager extends PluginManagerBase {
   protected $controllers = array();
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The cache backend to use.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /**
+   * An array of field information per entity type, i.e. containing definitions.
+   *
+   * @var array
+   *
+   * @see hook_entity_field_info()
+   */
+  protected $entityFieldInfo;
+
+  /**
+   * Static cache of field definitions per bundle and entity type.
+   *
+   * @var array
+   */
+  protected $fieldDefinitions;
+
+  /**
    * Constructs a new Entity plugin manager.
    *
    * @param \Traversable $namespaces
@@ -55,16 +95,27 @@ class EntityManager extends PluginManagerBase {
    *   keyed by the corresponding namespace to look for plugin implementations,
    * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
    *   The service container this object should use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend to use.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
    */
-  public function __construct(\Traversable $namespaces, ContainerInterface $container) {
+  public function __construct(\Traversable $namespaces, ContainerInterface $container, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager) {
     // Allow the plugin definition to be altered by hook_entity_info_alter().
     $annotation_namespaces = array(
       'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib',
     );
+
+    $this->moduleHandler = $module_handler;
+    $this->cache = $cache;
+    $this->languageManager = $language_manager;
+
     $this->discovery = new AnnotatedClassDiscovery('Core/Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
     $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
     $this->discovery = new AlterDecorator($this->discovery, 'entity_info');
-    $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . language(Language::TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
+    $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
 
     $this->factory = new DefaultFactory($this->discovery);
     $this->container = $container;
@@ -264,4 +315,118 @@ public function getAdminPath($entity_type, $bundle) {
     return $admin_path;
   }
 
+  /**
+   * Gets an array of entity field definitions.
+   *
+   * If a bundle is passed, fields specific to this bundle are included. Entity
+   * fields are always multi-valued, so 'list' is TRUE for each returned field
+   * definition.
+   *
+   * @param string $entity_type
+   *   The entity type to get field definitions for.
+   * @param string $bundle
+   *   (optional) The entity bundle for which to get field definitions. If NULL
+   *   is passed, no bundle-specific fields are included. Defaults to NULL.
+   *
+   * @return array
+   *   An array of field definitions of entity fields, keyed by field
+   *   name. In addition to the typed data definition keys as described at
+   *   \Drupal\Core\TypedData\TypedDataManager::create() the following keys are
+   *   supported:
+   *   - queryable: Whether the field is queryable via QueryInterface.
+   *     Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise.
+   *   - translatable: Whether the field is translatable. Defaults to FALSE.
+   *   - configurable: A boolean indicating whether the field is configurable
+   *     via field.module. Defaults to FALSE.
+   *
+   * @see \Drupal\Core\TypedData\TypedDataManager::create()
+   * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitionsByConstraints()
+   */
+  public function getFieldDefinitions($entity_type, $bundle = NULL) {
+    if (!isset($this->entityFieldInfo[$entity_type])) {
+      // First, try to load from cache.
+      $cid = 'entity_field_definitions:' . $entity_type . ':' . $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->langcode;
+      if ($cache = $this->cache->get($cid)) {
+        $this->entityFieldInfo[$entity_type] = $cache->data;
+      }
+      else {
+        $this->entityFieldInfo[$entity_type] = array(
+          'definitions' => $this->getStorageController($entity_type)->baseFieldDefinitions(),
+          // Contains definitions of optional (per-bundle) fields.
+          'optional' => array(),
+          // An array keyed by bundle name containing the optional fields added
+          // by the bundle.
+          'bundle map' => array(),
+        );
+
+        // Invoke hooks.
+        $result = $this->moduleHandler->invokeAll($entity_type . '_field_info');
+        $this->entityFieldInfo[$entity_type] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type], $result);
+        $result = $this->moduleHandler->invokeAll('entity_field_info', array($entity_type));
+        $this->entityFieldInfo[$entity_type] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type], $result);
+
+        $hooks = array('entity_field_info', $entity_type . '_field_info');
+        $this->moduleHandler->alter($hooks, $this->entityFieldInfo[$entity_type], $entity_type);
+
+        // Enforce fields to be multiple by default.
+        foreach ($this->entityFieldInfo[$entity_type]['definitions'] as &$definition) {
+          $definition['list'] = TRUE;
+        }
+        foreach ($this->entityFieldInfo[$entity_type]['optional'] as &$definition) {
+          $definition['list'] = TRUE;
+        }
+        $this->cache->set($cid, $this->entityFieldInfo[$entity_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE, 'entity_field_info' => TRUE));
+      }
+    }
+
+    if (!$bundle) {
+      return $this->entityFieldInfo[$entity_type]['definitions'];
+    }
+    else {
+      // Add in per-bundle fields.
+      if (!isset($this->fieldDefinitions[$entity_type][$bundle])) {
+        $this->fieldDefinitions[$entity_type][$bundle] = $this->entityFieldInfo[$entity_type]['definitions'];
+        if (isset($this->entityFieldInfo[$entity_type]['bundle map'][$bundle])) {
+          $this->fieldDefinitions[$entity_type][$bundle] += array_intersect_key($this->entityFieldInfo[$entity_type]['optional'], array_flip($this->entityFieldInfo[$entity_type]['bundle map'][$bundle]));
+        }
+      }
+      return $this->fieldDefinitions[$entity_type][$bundle];
+    }
+  }
+
+  /**
+   * Gets an array of entity field definitions based on validation constraints.
+   *
+   * @param string $entity_type
+   *   The entity type to get field definitions for.
+   * @param array $constraints
+   *   An array of entity constraints as used for entities in typed data
+   *   definitions, i.e. an array optionally including a 'Bundle' key.
+   *   For example the constraints used by an entity reference could be:
+   *   @code
+   *   array(
+   *     'Bundle' => 'article',
+   *   )
+   *   @endcode
+   *
+   * @return array
+   *   An array of field definitions of entity fields, keyed by field
+   *   name.
+   *
+   * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions()
+   */
+  public function getFieldDefinitionsByConstraints($entity_type, array $constraints) {
+    // @todo: Add support for specifying multiple bundles.
+    return $this->getFieldDefinitions($entity_type, isset($constraints['Bundle']) ? $constraints['Bundle'] : NULL);
+  }
+
+  /**
+   * Clears static and persistent field definition caches.
+   */
+  public function clearCachedFieldDefinitions() {
+    unset($this->entityFieldInfo);
+    unset($this->fieldDefinitions);
+    $this->cache->deleteTags(array('entity_field_info' => TRUE));
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index 2ccbfa8..f13ef02 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -223,10 +223,8 @@ public function getPropertyDefinition($name) {
    */
   public function getPropertyDefinitions() {
     if (!isset($this->fieldDefinitions)) {
-      $this->fieldDefinitions = \Drupal::entityManager()->getStorageController($this->entityType)->getFieldDefinitions(array(
-        'EntityType' => $this->entityType,
-        'Bundle' => $this->bundle,
-      ));
+      $bundle = $this->bundle != $this->entityType ? $this->bundle : NULL;
+      $this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityType, $bundle);
     }
     return $this->fieldDefinitions;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
index 365212a..aec1543 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
@@ -126,38 +126,16 @@ public function delete(array $entities);
   public function save(EntityInterface $entity);
 
   /**
-   * Gets an array of entity field definitions.
-   *
-   * If a 'bundle' key is present in the given entity definition, fields
-   * specific to this bundle are included.
-   * Entity fields are always multi-valued, so 'list' is TRUE for each
-   * returned field definition.
-   *
-   * @param array $constraints
-   *   An array of entity constraints as used for entities in typed data
-   *   definitions, i.e. an array having an 'entity type' and optionally a
-   *   'bundle' key. For example:
-   *   @code
-   *   array(
-   *     'EntityType' => 'node',
-   *     'Bundle' => 'article',
-   *   )
-   *   @endcode
+   * Defines the base fields of the entity type.
    *
    * @return array
-   *   An array of field definitions of entity fields, keyed by field
-   *   name. In addition to the typed data definition keys as described at
-   *   \Drupal::typedData()->create() the follow keys are supported:
-   *   - queryable: Whether the field is queryable via QueryInterface.
-   *     Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise.
-   *   - translatable: Whether the field is translatable. Defaults to FALSE.
-   *   - configurable: A boolean indicating whether the field is configurable
-   *     via field.module. Defaults to FALSE.
-   *
-   * @see Drupal\Core\TypedData\TypedDataManager::create()
-   * @see \Drupal::typedData()
+   *   An array of entity field definitions as specified by
+   *   \Drupal\Core\Entity\EntityManager::getFieldDefinitions(), keyed by field
+   *   name.
+   *
+   * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions()
    */
-  public function getFieldDefinitions(array $constraints);
+  public function baseFieldDefinitions();
 
   /**
    * Gets the name of the service for the query for this entity storage.
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php
index eb6ae7d..f85e7f1 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php
@@ -178,7 +178,7 @@ public function getPropertyDefinition($name) {
    */
   public function getPropertyDefinitions() {
     // @todo: Support getting definitions if multiple bundles are specified.
-    return \Drupal::entityManager()->getStorageController($this->entityType)->getFieldDefinitions($this->definition['constraints']);
+    return \Drupal::entityManager()->getFieldDefinitionsByConstraints($this->entityType, $this->definition['constraints']);
   }
 
   /**
