Finalizing your project

Add support for the ViSiBloodPressure Observations

First, follow similar steps as above to support ViSiBloodPressure:

  1. Add a mapping method in the ResourceMapper class to map from a ViSiBloodPressure to a FHIR Observation resource, and return that as an IResource.

  2. Create a BloodPressureQuery query class.

  3. Add a BPQueryFactory extending RelationalQueryFactory<ViSiBloodPressure, BloodPressureQuery>.

  4. Implement support for the _id parameter by overriding public virtual BloodPressureQuery AddValueFilter(string parameterName, TokenValue value).

  5. Add the Observation type to the SupportedModel section in Firely Server’s appsettings.instance.json: "RestrictToResources": [ "Patient", "Observation" ]

When you have completed these steps, build your project again and copy the dll to your Firely Server plugins folder. After you (re)start Firely Server, you will be able to request an Observation through your Facade: GET http://localhost:4080/Observation?_id=1 or GET http://localhost:4080/Observation/1.

Since you do not always want to request Observations by their technical id, but more often might want to request Observations from a specific patient, the next part will describe implementing support that. The Patient resource is referenced by the Observation in its subject field. The corresponding search parameter is either subject or patient.

Add support for chaining

To add support for searching on Observation?subject:Patient._id we need to override the AddValueFilter overload receiving a ReferenceToValue parameter in the query factory for BloodPressure (BPQueryFactory).

The ReferenceToValue type contains the possible Targets for the chain search parameter as parsed from the query string. We are currently interested only on the Patient type so we can restrict the implementation to that target. The ReferenceToValue type also has an extension method CreateQuery that expects an implementation of the RelationalQueryFactory of the referenced target. This will generate the query to obtain the resources referenced by it.

Searching on chained parameters involves the following steps:

  1. Retrieve all patient ids based on the chained parameter. You can use the ReferenceToValue.CreateQuery extension method to get the query and run the query with its Execute method.

  2. Create a PredicateQuery with the condition that ViSiBloodPressure.PatientId is included in the ids retrieved at the previous step.

    The final code should look similar to this:

    public override BloodPressureQuery AddValueFilter(string parameterName, ReferenceToValue value)
    {
        if (parameterName == "subject" && value.Targets.Contains("Patient"))
        {
            var patientQuery = value.CreateQuery(new PatientQueryFactory(OnContext));
            var patIds = patientQuery.Execute(OnContext).Select(p => p.Id);
    
            return PredicateQuery(bp => patIds.Contains(bp.PatientId));
        }
        return base.AddValueFilter(parameterName, value);
    }
    

    Note

    patIds is of type IQueryable, so the resulting BloodPressureQuery will still be executed as a single command to the database.

  3. Add support for the Observation.subject search parameter in the Firely Server appsettings similar to how we did it for _id.

At this point you should be able to search for GET http://localhost:4080/Observation?subject:Patient._id=1

Add support for reverse chaining

Adding support for Patient?_has:Observation:subject:_id=1 is similar. You just need to use the AddValueFilter overload receiving a ReferenceFromValue.

The ReferenceFromValue type has a Source property filled in with the source of the search parameter. It also has an extension method CreateQuery that given the corresponding RelationalQueryFactory implementation can generate the query to obtain resources referenced by the reverse chaining.

So you can add reverse chaining with the following code:

public class PatientQueryFactory
{

    public override PatientQuery AddValueFilter(string parameterName, ReferenceFromValue value)
    {
        if (parameterName == "subject" && value.Source == "Observation")
        {
            var obsQuery = value.CreateQuery(new BPQueryFactory(OnContext));
            var obsIds = obsQuery.Execute(OnContext).Select(bp => bp.PatientId);

            return PredicateQuery(p => obsIds.Contains(p.Id));
        }
        return base.AddValueFilter(parameterName, value);
    }
}

Note

The reverse chaining example above uses the Patient resource type as its base, so you will need to implement this in your PatientQueryFactory.

Now you can test if reverse chaining works: http://localhost:4080/Patient?_has:Observation:subject:_id=1

Get the goodies

At this point you get out of the box support for _include and _revinclude (:iterate as well), and combinations of search parameters. You can test the following scenarios:

  1. _include: http://localhost:4080/Observation?_include=Observation:subject

  2. _revinclude: http://localhost:4080/Patient?_revinclude=Observation:subject

  3. combinations of the above

Adding a custom SearchParameter

Your Firely Server will load the standard parameters from the specification on first startup, so the _id SearchParameter from the exercise is already known to Firely Server, as well as any of the other standard search parameters for the resource types.

If you want to implement support for a custom search parameter, you will need to have the definition of that in the form of a SearchParameter resource, and add it to your Firely Server. The Configure Search Parameters section describes how to do that.

Of course you will also need to implement the correct AddValueFilter method in your <resourcetype>QueryFactory to handle the parameter correctly, as is done for the _id parameter in the exercise.

The end?

This concludes the exercise. An example Github repository contains the completed exercise.

Please feel free to try out more options, and ask for help if you get stuck!

The next topic will show you how to enable Create, Update and Delete interactions.

Postscript

If your resource is split across multiple tables in the database, you’ll need to make use of .Include() to have EF load the dependent table. To do so in Firely Server, override the GetEntitySet() method in your RelationalQuery class, for example

protected override IQueryable<ViSiPatient> GetEntitySet(DbContext dbContext)
{
    // load the dependent Address table
    return dbContext.Set<ViSiPatient>().Include(p => p.Address).AsNoTracking();
}