public <I extends TraceObjectInterface> Collection<I> getObjectsIntersecting(Lifespan lifespan,
			AddressRange range, String key, Class<I> iface) {
		try (LockHold hold = trace.lockRead()) {
			Set<TraceObjectSchema> schemas;
			synchronized (schemasByInterface) {
				schemas =
					schemasByInterface.computeIfAbsent(iface, this::collectSchemasForInterface);
			}
			Map<String, List<TraceObjectSchema>> schemasByAliasTo =
				schemas.stream().collect(Collectors.groupingBy(s -> s.checkAliasedAttribute(key)));
			return schemasByAliasTo.entrySet().stream().flatMap(ent -> {
				return getValuesIntersecting(lifespan, range, ent.getKey()).stream()
						.map(v -> v.getParent())
						.filter(o -> ent.getValue().contains(o.getSchema()));
			}).map(o -> o.queryInterface(iface)).collect(Collectors.toSet());
		}
	}
NB. An object in one of these queries had to have an edge. Deleting that object will also
delete referring edges, so the cache will get invalidated. No need to repeat in
doDeleteObject.
Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java