CRUD interactions

A FhirClient named client has been setup in the previous topic, now let’s do something with it.

Create a new resource

Assume we want to create a new resource instance and want to ask the server to store it for us. This is done using Create.

var pat = new Patient() { /* set up data */ };
var created_pat = client.Create<Patient>(pat);

Tip

See Working with the model for examples on how to fill a resource with values.

As you’d probably expect, this interaction will throw an Exception when things go wrong, in most cases a FhirOperationException. This exception has an Outcome property that contains an OperationOutcome resource, and which you may inspect to find out more information about why the interaction failed. Most FHIR servers will return a human-readable error description in the OperationOutcome to help you out.

If the interaction was successful, the server will return an instance of the resource that was created, containing the id, metadata and a copy of the data you just posted to the server as it was stored. Depending on the server implementation, this could differ from what you’ve sent. For instance, the server could have filled in fields with default values, if the values for those fields were not set in your request.

If you’ve set the PreferredReturn property of the FhirClient to minimal, the server will return the technical id and version number of the newly created resource in the headers of the response and the Create method will return null. See Message Handlers for an example of how to retrieve the information from the returned headers.

Reading an existing resource

To read the data for a given resource instance from a server, you’ll need its technical id. You may have previously stored this after a Create, or you have found its address in a ResourceReference (e.g. Observation.Subject.Reference).

The Read interaction on the FhirClient has two overloads to cover both cases. Furthermore, it accepts both relative paths and absolute paths (as long as they are within the endpoint passed to the constructor of the FhirClient).

// Read the current version of a Patient resource with technical id '31'
var location_A = new Uri("http://server.fire.ly/Patient/31");
var pat_A = client.Read<Patient>(location_A);
// or
var pat_A = client.Read<Patient>("Patient/31");

// Read a specific version of a Patient resource with technical id '32' and version id '4'
var location_B = new Uri("http://server.fire.ly/Patient/32/_history/4");
var pat_B = client.Read<Patient>(location_B);
// or
var pat_B = client.Read<Patient>("Patient/32/_history/4");

Tip

See the paragraph about ResourceIdentity for methods to construct URIs from separate values and other neat helper methods.

Note that Read can be used to get the most recent version of a resource as well as a specific version, and thus covers the two ‘logical’ REST interactions read and vread.

Updating a resource

Once you have retrieved a resource, you may edit its contents and send it back to the server. This is done using the Update interaction. It takes the resource instance previously retrieved as a parameter:

// Add a name to the patient, and update
pat_A.Name.Add(new HumanName().WithGiven("Christopher").AndFamily("Brown"));
var updated_pat = client.Update<Patient>(pat_A);

There’s always a chance that between retrieving the resource and sending an update, someone else has updated the resource as well. Servers supporting version-aware updates may refuse your update in this case and return a HTTP status code 409 (Conflict), which causes the Update interaction to throw a FhirOperationException with the same status code. Clients that are version-aware can indicate this using the optional second parameter versionAware set to true. This will result in a conditional call of the interaction.

Deleting a resource

The Delete interaction on the FhirClient deletes a resource from the server. It is up to the server to decide whether the resource is actually removed from storage, or whether previous versions are still available for retrieval. The Delete interaction has multiple overloads to allow you to delete based on a url or a resource instance:

// Delete based on a url or resource location
var location = new Uri("http://server.fire.ly/Patient/33");
client.Delete(location);
// or
client.Delete("Patient/33");

// You may also delete based on an existing resource instance
client.Delete(pat_A);

The Delete interaction will fail and throw a FhirOperationException if the resource was already deleted or if the resource did not exist before deletion, and the server returned an error indicating that.

Note that sending an update to a resource after it has been deleted is not considered an error and may effectively “undelete” it.

Patching resources

The Patch interaction on the FhirClient allows you to send a partial update to a resource on the server - without having to send the entire resource. This is done using the ‘FHIRPath Patch’ format, which allows you to define a set of operations to apply to a resource using a Paramaters resource in the body of the request. Full documentation of FHIRPath Patch can be found in the FHIR specification.

To use the Patch interaction, you need to create a Parameters resource containing the patch operations you want to apply to the resource. There are two ways to do this:

  1. Create a Parameters resource and add the patch operations to the Parameter list manually.

var patch = new Parameters();
patch.Parameter.Add(new Parameters.ParameterComponent()
{
        Name = "OPERATION",
        Part = new List<Parameters.ParameterComponent>()
        {
                new Parameters.ParameterComponent()
                {
                        Name = "type",
                        Value = new FhirString("replace")
                },
                new Parameters.ParameterComponent()
                {
                        Name = "path",
                        Value = new FhirString("Patient.name[0].given[0]")
                },
                new Parameters.ParameterComponent()
                {
                        Name = "value",
                        Value = new FhirString("John")
                }
        }
});
  1. Create a Parameters resource and use an extension method of your choice to add the patch operations to the Parameter list.

var patch = new Parameters();
patch.AddReplacePatchOperation("Patient.name[0].given[0]", new FhirString("John"));

Both methods will result in the same patch operation being sent to the server: it will replace the first given name of the first name of the patient with “John”.

Next to the AddReplacePatchOperation method, there are also AddAddPatchOperation, AddInsertPatchOperation, AddMovePatchOperation and AddDeletePatchOperation methods available, for the right kind of patch operation you want to perform.

After you have created the Parameters resource, you can call the Patch interaction on the FhirClient:

var patientId = "Patient/1";
var patched_pat = await client.PatchAsync<Patient>(patientId, patch);

The Patch interaction will return the patched resource if the patch was successful. If the patch was not successful, it will throw a FhirOperationException with the error details.

Conditional interactions

The SDK provides support for the conditional versions of the Create, Update, Delete, and Patch interactions. Not all servers will support conditional interactions and can return an HTTP 412 error with an OperationOutcome to indicate that.

All of the conditional interactions make use of search parameters. See the page of the resource type you want to work with in the HL7 FHIR specification to check which search parameters are available for that type. Then, setup the conditions.

For example, if we want to base the interaction on the identifier element of a resource, we can setup that search parameter with a value:

var conditions = new SearchParams();
conditions.Add("identifier", "http://ids.acme.org|123456");

Tip

See Searching for resources for more explanation about SearchParams and example search syntax.

For the Create interaction you can have the server check if an equivalent resource already exists, based on the search parameters:

var created_pat_A = client.Create<Patient>(pat, conditions);

If no matches are found, the resource will be created. If one match is found, the server will not create the resource and will return an HTTP 200 (OK). In both cases created_pat_A will contain the resource that was sent back by the server, unless you set the FhirClient to ask for the minimal representation. When multiple resources match the conditions, the server will return an error.

To perform a conditional Update, the code is similar to that of the Create interaction above. Again, setup a SearchParams object and add it to your request:

// using the same conditions as in the previous example
var updated_pat_A = client.Update<Patient>(pat, conditions);

If a match is found, the update is performed on that match. If no matches are found, the server will perform the interaction as if it were a Create. When multiple resources match, the server will return an error.

The conditional Patch interaction works in the same way as the Update interaction. The only difference is that the Patch uses a Parameters resource to define the patch operations to apply to the resource, as described in the patch section above.

// using the same conditions as in the previous example
// and a patch parameters defined in the patch section
var patched_pat_A = client.Patch<Patient>(conditions, patchparams);

If a match is found, the patch is performed on that match. If no matches are found, the server will return a 404 Not Found. If multiple matches are found, the server will return a 412 Precondition Failed response.

The conditional Delete takes a string as first argument, indicating the resource type. The search parameters are passed as second argument:

client.Delete("Patient", conditions);

When no match is found, the server will return an error. If one match is found, that resource will be deleted. The server may choose to delete all resources if multiple instances match, or it may return an error.


Refreshing data

Whenever you have held a resource for some time, its data may have changed on the server because of changes made by others. At any time, you can refresh your local copy of the data by using the Refresh call, passing it the resource instance as returned by a previous Read, Create, or Update:

var refreshed_pat = client.Refresh<Patient>(pat_A);

This call will go to the server and fetch the latest version and metadata of the resource as pointed to by the Id property in the resource instance passed as the parameter.