ice.NET Queries - Architecture
There are architecural commonalities between all kinds of queries supported by the
ice.NET platform. Actually the core platform implements a framework for query processing
and the ice.NET SDK provides several useful query templates (Find, Expand, Join) that
are built on top of the framework specification and can be directly used by the
application developer.
Query Processing
The following diagram illustrates how queries are processed in principle:
Query processing starts with a query specification. This specification defines the type
and the static structure of the query. It contains all information that does not
change between multiple executions of the query.
By running the query specification through the query compilation an intermediate
data structure is created: the compiled query. The compiled query contains all information
of the query specification in a preprocessed form that is suitable for query execution.
All calculations that can be done independently from specific executions of the
query are performed during the compilation process. The compiled query can be used
for multiple executions. During the compilation process information about the data model
must be available to resolve type information used in the query specification.
The actual execution of the query is performed by the query processor. It consumes
additional information that can vary between different executions of the same compiled
query: the query parameters. The query parameters typically contain specific values
for constraints defined in the query specification. Successful execution of the
query produces a query result, typically a table of data that conforms to the fields
defined in the query specification.
The Query Framework
The Query API is quite simple and is directly defined in the IDatabasRepository
interface. It consists of two core methods that implement the query compiler and the
query processor illustrated in the diagram above:
ICompiledQuery CompileQuery(IQuerySpecification pQuerySpec);
IQueryResult ExecuteQuery(ICompiledQuery pQuery, IQueryParameters pParameters);
An additional method provides the possibility to execute a query specification
directly, without explicitly compiling it. This method processes
the compilation step internally:
IQueryResult ExecuteQuery(IQuerySpecification pQuerySpec,
IQueryParameters pParameters);
The concepts mentioned above (query specification, compiled query, query parameters,
query result) are represented by general interfaces in the API. These interfaces have
been implemented for specific types of queries (Find, Expand, etc.).
Using the Query API
Queries can be defined and processed by using the Query API directly. To do this,
a query specification must be instantiated. For the built-in query types the
IceNetSdk class provides factory methods. The specification data can be set
by proprties:
ExpandQuerySpecification pSpec = IceNetSdk.CreateExpandQuerySpecification();
pSpec.Name = "CustomerBookingsNewerThan";
pSpec.AnchorType = ExpandAnchorType.Object;
pSpec.CheckAuthorization = false;
ExpandStep pStep = new ExpandStep();
pSpec.Steps.Add(pStep);
pStep.RelTypeName = "PDTec.ICR.CustomerBookings";
pStep.RelDirection = RelDirection.Forward;
ExpandConstraint pConstraint = new ExpandConstraint();
pStep.Constraints.Add(pConstraint);
pConstraint.Name = "NewerThan";
pConstraint.ItemType = ExpandItemType.Object;
pConstraint.FieldType = ExpandFieldType.Attribute;
pConstraint.AttrDefDeclTypeName = "PDTec.ICR.Booking";
pConstraint.AttrDefName = "FromDate";
pConstraint.ConstraintType = ExpandConstraintType.Greater;
pConstraint.Parameter0Name = "NewerThan";
ExpandField pField1 = new ExpandField();
pSpec.Fields.Add(pField1);
pField1.Name = "FromDate";
pField1.StepRefIndex = 0;
pField1.ItemType = ExpandItemType.Object;
pField1.FieldType = ExpandFieldType.Attribute;
pField1.AttrDefDeclTypeName = "PDTec.ICR.Booking";
pField1.AttrDefName = "FromDate";
ExpandField pField2 = new ExpandField();
pSpec.Fields.Add(pField2);
pField2.Name = "ToDate";
pField2.StepRefIndex = 0;
pField2.ItemType = ExpandItemType.Object;
pField2.FieldType = ExpandFieldType.Attribute;
pField2.AttrDefDeclTypeName = "PDTec.ICR.Booking";
pField2.AttrDefName = "ToDate";
ExpandField pField3 = new ExpandField();
pSpec.Fields.Add(pField3);
pSpec.Fields.Add(new ExpandField());
pField3.Name = "VehicleRegisterNumber";
pField3.AddSteps = new List<ExpandAddStep>();
pField3.AddSteps.Add(new ExpandAddStep());
pField3.AddSteps[0].RelTypeName = "PDTec.ICR.BookedVehicle";
pField3.AddSteps[0].RelDirection = RelDirection.Forward;
pField3.StepRefIndex = 0;
pField3.ItemType = ExpandItemType.Object;
pField3.FieldType = ExpandFieldType.Attribute;
pField3.AttrDefDeclTypeName = "PDTec.ICR.Vehicle";
pField3.AttrDefName = "RegisterNumber";
ExpandField pField4 = new ExpandField();
pSpec.Fields.Add(pField4);
pField4.Name = "VehicleModelName";
pField4.AddSteps = new List<ExpandAddStep>();
pField4.AddSteps.Add(new ExpandAddStep());
pField4.AddSteps[0].RelTypeName = "PDTec.ICR.BookedVehicle";
pField4.AddSteps[0].RelDirection = RelDirection.Forward;
pField4.AddSteps.Add(new ExpandAddStep());
pField4.AddSteps[1].RelTypeName = "PDTec.ICR.VehicleModel";
pField4.AddSteps[1].RelDirection = RelDirection.Forward;
pField4.StepRefIndex = 0;
pField4.ItemType = ExpandItemType.Object;
pField4.FieldType = ExpandFieldType.Name;
Notice: If you are of the opinion that this is an inconvenient way to
specify a query, then you are correct. However, to explain the query architecture
fundamentally, this step is necessary. Later on, alternative approaches will be presented.
Once the query specification has been created, it can be compiled by the
CompileQuery method:
ICompiledQuery pCompiledQuery = Repository.CompileQuery(pSpec);
The compiled query can be the executed. Therefore we must fill a parameter structure
with values that correspond to the specific type of the query and the defined
constraints:
ExpandQueryParameters pParams = (ExpandQueryParameters)pCompiledQuery.GetParameters();
pParams.AnchorObjectId = pCustomer.Id;
ExpandConstraintValue pConstraintValue = new ExpandConstraintValue();
pConstraintValue.Name = "NewerThan";
pConstraintValue.Value = DateTime.UtcNow;;
pParams.ConstraintValues.Add(pConstraintValue);
pParams.MaxResults = 0; // no restriction
DbTable pResultTable = (DbTable)Repository.ExecuteQuery(pCompiledQuery, pParams);
By executing the query the query processor produces a query result, normally
an instance of the DbTable class. A DbTable contains a sequence
of rows (the results) that have a column structure according to the field definitions
in the query specification.
Using the Business Objects Builder
The approach of directly using the Query API has two disadvantages:
- The construction of the query specification is relatively inconvenient.
- The query result consists of a untyped table (row sequence) structure.
To overcome these disadvantages, the Business Objects Builder provides a
way to define the query specifications in an elegant, XML-based syntax and
to execute the query by a typesafe interface. The query specification defined
above can be represented in XML (stored in the <Queries> section of an
.icebob file):
<ExpandQuery Name="CustomerBookingsNewerThan">
<Step RelTypeName="PDTec.ICR.CustomerBookings" RelDirection="Forward">
<Field Name="FromDate" ItemType="Object" FieldType="Attribute" AttrDefName="FromDate" AttrDefDeclTypeName="PDTec.ICR.Booking" />
<Field Name="ToDate" ItemType="Object" FieldType="Attribute" AttrDefName="ToDate" AttrDefDeclTypeName="PDTec.ICR.Booking" />
<Field Name="VehicleRegisterNumber" ItemType="Object" FieldType="Attribute" AttrDefName="RegisterNumber" AttrDefDeclTypeName="PDTec.ICR.Vehicle"
AddStepRelTypeName="PDTec.ICR.BookedVehicle" AddStepRelDirection="Forward" />
<Field Name="VehicleModelName" ItemType="Object" FieldType="Name">
<AddSteps>
<AddStep RelTypeName="PDTec.ICR.BookedVehicle" RelDirection="Forward" />
<AddStep RelTypeName="PDTec.ICR.VehicleModel" RelDirection="Forward" />
</AddSteps>
</Field>
<Constraint Name="NewerThan" ItemType="Object" FieldType="Attribute" AttrDefDeclTypeName="PDTec.ICR.Booking" AttrDefName="FromDate" ConstraintType="Greater" Parameter0Name="NewerThan" />
</Step>
</ExpandQuery>
From this definition the Business Objects Builder generates a pair of
classes that encapsulates the API handling behind a typesafe execution
and result structure:
public class CustomerBookingsNewerThanQueryResult
{
public DateTime FromDate;
public DateTime ToDate;
public string VehicleRegisterNumber;
public string VehicleModelName;
}
public class CustomerBookingsNewerThanQuery
{
public CustomerBookingsNewerThanQuery(
IDatabaseRepository pRepository);
public CustomerBookingsNewerThanQueryResult[] Execute(
string anchorObjectId,
DateTime NewerThan,
int maxResults);
}
Processing the query then requires only a call of the Execute method
and providing the appropriate constraint parameters:
CustomerBookingsNewerThanQueryResult[] aResults =
new CustomerBookingsNewerThanQuery(Repository).Execute(pCustomer.Id, DateTime.UtcNow, 0);