Best practices
Although it is seemingly easy to invoke FhirPath, there are a few details that are easy to get wrong.
Start evaluation from the root
To make the resolve() function work well (e.g. to resolve to entries in a Bundle or to a contained resource), the FhirPath engine needs
to have “seen” all the resources while navigating through the data, which means you need to evaluate Bundles from their roots.
Bundle b = new() {...}
// The engine has worked from the root of the bundle down, so it knows how to resolve to other entries
var active = b.Select("Bundle.entry.ofType(Patient).organization.resolve()");
// The engine was started from the nested Patient node, so it does not know how to find other entries.
var patient = b.Entry.Select(e => e.Resource).OfType<Patient>().First();
var active2 = patient.Select("organization.resolve()");
The %resource and %rootResource variables
Some FhirPath statements use the %resource and %rootResource environment variables. When you evaluate an expression through the POCO extension methods,
the engine infers these automatically from the input: %resource becomes the resource that contains the node you are evaluating (or the root of the input
itself when it is not inside a resource), and %rootResource the outermost resource. So in the common case these variables just work, without you having to
configure anything:
Patient p = new() {...}
var hasName = p.IsTrue("%resource.name.exists()"); // %resource is inferred from p
If you need %resource to point at a different resource than the one the engine would infer, set the Resource (and/or RootResource) property on a
FhirEvaluationContext explicitly — an explicitly-set value overrides the inferred one:
var ctx = new FhirEvaluationContext { Resource = someResource.ToPocoNode(ModelInfo.ModelInspector) };
var hasName = p.IsTrue("%resource.name.exists()", ctx);
The engine can infer these variables because it works on a PocoNode, which navigates the POCO tree and keeps track of parent, contained, and Bundle
entry nodes — the same bookkeeping that makes resolve() work (see the previous section).
Set the ElementResolver on the FhirEvaluationContext
Finally, the engine needs you to supply a delegate when you want resolve() to be able to reach out to instances of Resources (via uri) that it cannot locate itself.
The delegate takes a single string parameter (the uri) and returns a PocoNode:
var ctx = new FhirEvaluationContext { Resource = p.ToPocoNode(ModelInfo.ModelInspector) };
ctx.ElementResolver = myResolver;
PocoNode? myResolver(string uri)
{
var resolved = ...;
return resolved.ToPocoNode(ModelInfo.ModelInspector);
}
Set the TerminologyService on the FhirEvaluationContext
To use the FhirPath function memberOf(valueset), you must set the TerminologyService property on the FhirEvaluationContext.
This gives the FhirPath engine a means to check whether a code is in a value set.
var ctx = new FhirEvaluationContext
{
TerminologyService = new LocalTerminologyService(resolver: ZipSource.CreateValidationSource())
};
var result = new Code("male").Scalar("memberOf('http://hl7.org/fhir/ValueSet/administrative-gender')", ctx);
