Enable Search
Enabling search involves two major steps:
Creating a query to the database based on the bits and pieces in the search url
Getting a count and actual data from the database with that query, and map it to a SearchResult
The next paragraphs will walk you through these steps.
1. Create a query
Firely Server Facade is meant to be used across all kinds of database paradigms and schemas. Or even against underlying web services or stored procedures. This means Firely Server cannot prescribe the way your query should be expressed. After all, it could be an http call to a webservice, or a json command to MongoDB.
In our case we will build a LINQ query against our ViSi model, that is translated by Entity Framework to a SQL query.
Because this is a quite common case, Firely Server provides a basis for it in the package Vonk.Facade.Relational
.
Go back to the NuGet Package Manager Console and run
Install-Package Vonk.Facade.Relational
Note
If you did this previously for the other Firely Server packages, you can install the latest beta release of this package as well by adding
-IncludePrerelease
to the install command.
Adding classes for the query
You usually create a query class per ResourceType. The Query object is used to capture the elements of the search that are provided to the QueryFactory.
In this exercise we start with resource type Patient, and will create a PatientQuery
and PatientQueryFactory
class.
Because PatientQuery has no specific content of its own, we will include both in one file.
Add a new class
PatientQueryFactory
to the root of the projectAdd using statements for
Vonk.Facade.Relational
,Microsoft.EntityFrameworkCore
, and<your project>.Models
Above the actual
PatientQueryFactory
class insert thePatientQuery
class:public class PatientQuery: RelationalQuery<ViSiPatient> {}
Now flesh out the
PatientQueryFactory
:public class PatientQueryFactory: RelationalQueryFactory<ViSiPatient, PatientQuery> {}
Adding a constructor
You have to provide a constructor for the factory class. With this you tell Firely Server for which resource type this QueryFactory is valid. The DbContext is used for retrieving DbSets for related entities, as we will see later:
public PatientQueryFactory(DbContext onContext) : base("Patient", onContext) { }
Deciding on a FHIR version
You need to explicitly tell Firely Server for which FHIR version(s) you wish to return resources. If you don’t override EntryInformationModel
, any search will fail with a 501 Not Implemented
. The following override will allow searches for any possible FHIR version to be handled by your facade:
public override PatientQuery EntryInformationModel(string informationModel)
{
return default(PatientQuery);
}
If you wish to implement search only for a single FHIR version or for a limited set of versions you can override the method like this:
public override PatientQuery EntryInformationModel(string informationModel)
{
if (informationModel == VonkConstants.Model.FhirR4)
{
return default(PatientQuery);
}
throw new NotImplementedException($"FHIR version {informationModel} is not supported");
}
Handling the search request
Each of the searchparameters in the search request triggers a call to the Filter
method. This method takes a
parameterName
and IFilterValue
as its arguments.
The parameterName
is the searchparameter as it was used in the search url. This name corresponds with the code field in a SearchParameter resource.
The IFilterValue value
is one of 10 possible implementations, one for each type of SearchParameter. See IFilterValue implementations
for a short description of these possibilities.
By default the Filter
method dispatches the call to a suitable overload of AddValueFilter
, based on the actual type of the value
parameter.
It is up to you to override the ones you support any parameters for.
Override the method
PatientQuery AddValueFilter(string parameterName, TokenValue value)
in thePatientQueryFactory
class to implement support for the_id
parameter, which is a token type parameter.The
_id
parameter must be matched against the ViSiPatient.Id property. So we have to:Parse the Token.Code to an integer (ViSiPatient.Id is of type int)
Create a query with a predicate on ViSiPatient.Id.
This is how:
if (parameterName == "_id") { if (!int.TryParse(value.Code, out int patientId)) { throw new ArgumentException("Patient Id must be an integer value."); } else { return PredicateQuery(vp => vp.Id == patientId); } } return base.AddValueFilter(parameterName, value);
Note
The ArgumentException
in this code will automatically result in setting the argument status to error, so the Firely Server
will send a response with an error code and OperationOutcome. See the information about the IArgumentCollection
and IArgument
classes in IVonkContext.
That’s it for now, we will add support for another parameter later.
IFilterValue implementations
There are 10 possible implementations you can use as value for the IFilterValue parameter in the Query. The first 7 are the general search parameter types: StringValue, DateTimeValue, TokenValue, NumberValue, QuantityValue, UriValue and ReferenceValue.
Besides that there are two special values for chaining and reverse chaining: ReferenceToValue and ReferenceFromValue.
And finally there is a special value for when Firely Server does not know the SearchParameter and hence not the type of it: RawValue.