Drupal - How load only some node fields?

I did some profiling with a query of 100,000 nodes, fetching the field myfield from all nodes of type mybundle.

Good: A dynamic query with addField()

$connection = \Drupal::database();
$query = $connection->select('node__field_myfield');
$query->condition('node__field_myfield.bundle', 'mybundle');
$query->addField('node__field_myfield', 'field_myfield_value');
$result = $query->execute();
$values = $result->fetchAll();

This runs in about 250 milliseconds.

Bad: An entity query with loadMultiple()

$query = \Drupal::entityQuery('node');
$query->condition('type', 'mybundle');
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$values = [];
$nids = $query->execute();
$nodes = $node_storage->loadMultiple($nids);
foreach($nodes as $node) {
  $values[] = $node->get('field_myfield')->getValue();
}

This took 3 minutes after a few repeated attempts when the cache was fully built. After a cache rebuild, it took more than half an hour.


According to Acquia articles and specifically this:

In Drupal 7, we often accessed fields directly, using hooks to modify fields when they were loaded, changed, or saved. In Drupal 8, there is a focus on entities instead of fields. Because of this, if we want to modify fields we need to get the fields out of the entities.

So in Drupal 8 you can't directly access any fields without first loading the whole entity as the following example (taken from the Acquia article) demonstrates:

/**
* @var $entity \Drupal\Core\Entity\Entity
*/
$entity = $entity_storage->load($entity_id);


/**
* @var $field \Drupal\Core\Field\FieldItemList
*/
$field = $entity->get($field_name);

// We get the string value from the field.
$value = $field->value;

// Get the summary from a body field.
$summary = $body_field->summary;

$field->value = 'changed value';

Unfortunately the specific thing you want doesn't exist in the API. All field data is loaded in bulk via ContentEntityStorageBase::doLoadRevisionFieldItems, and the standard SqlContentEntityStorage doesn't contain any methods for extracting individual fields. If you haven't already done so, it might be worth benchmarking this to see if there's a real problem. A lot of work was done on the cache system for D8, things might not be as slow as you think.

Implementing this manually would be pretty easy, just re-implement SqlContentEntityStorage::buildQuery in a new service class and change it to only load data for the field(s) passed in as an arguments from the table mapping. But you don't get any cache goodness with that, which I suspect is at least part of the reason loading individual fields was removed in the first place.

Tags:

Entities

8