Extending the Query Application Block
The Query Application Block handles three essential components: Queries, Parameters and Commands and currently supports three types of query: a
Data Query to any relational database supported by the DAAB, a
Service Query to a WCF or Web service and a
File Query specifically to an XML file. There are many other ways that data can be stored like the Windows Registry, LDAP sources, CSV files, INI files and other proprietary format flat files, that it is quite likely that the QAB may need to be extended in time. The key to extending the QAB is to create a custom command, optionally a custom parameter type, unless an existing type suffices like the generic
NameValueParameter and a custom query to wrap the command and parameters.
Creating Custom Commands
Creating a custom command based around an existing base class has already been covered in detail on the
Custom Commands page. However, so far we have only covered the situation where you need a one-off special condition outside of the norm. If however, you need something a bit more permanent or regular then you need to consider deriving from the
Command base class, or even a higher class such as
FileCommand perhaps? At the very least the
Command base class handles the
ParameterDictionary for you and also implements the
ICommand interface albeit in abstract form. The real work for you as the extension developer is to handle the mechanics of the
ExecuteForRead() and the
ExecuteForWrite() methods. The following code segment shows the
ICommand interface:
[C#]
public interface ICommand
{
/// <summary>
/// Gets the query parameters.
/// </summary>
/// <value>A dictionary of parameters.</value>
ParameterDictionary Parameters
{
get;
}
/// <summary>
/// Executes a read command and returns the results.
/// </summary>
/// <returns>
/// Dataset containing the results of the read operation
/// </returns>
/// <remarks>
/// Output parameters will update the parameter property values.
/// </remarks>
DataSet ExecuteForRead();
/// <summary>
/// Executes a write command and saves data to the data source.
/// </summary>
/// <remarks>
/// Output parameters will update the parameter property values.
/// </remarks>
void ExecuteForWrite();
}
Note1: The
ExecuteForRead() method must always return a
DataSet, even an empty one, if necessary, in the case when no records match the query criteria. The DataSet should never be
null. The
ExecuteForWrite() has no return and should throw exceptions if it gets into trouble. Don't forget the
ParameterDictionary is provided for all parameters needed for your command. It is the reponsibility of the Query wrapper to populate this parameter set. This dictionary is a set of
IParameter objects (discussed in the next section) keyed with a
string parameter name.
Note2: There is no configuration for custom commands as they are completely managed by their query wrapper. However, if you wish to allow one-off customisations of your new command type
and you have created a brand new base class; then you wll need to copy one of the existing design nodes and modify the filter used in the Type Selector to enable these custom queries to be registered and ultimately linked to queries through the configuration console.
Creating Custom Parameters
For most scenarios the generic
NameValueParameter is all that is required, which simply holds an object with a name, but as you can see from the
DataParameter and the
XmlParameter etc there are situations when something a little more sophisticated is required. When this happens then you should inherit from the abstract
Parameter base class which in turn implements the
IParameter interface:
[C#]
public interface IParameter
{
/// <summary>
/// Gets or sets the name of the parameter.
/// </summary>
/// <value>The name of the parameter.</value>
/// <remarks>Exclude parameter name tokens for generic queries e.g. for SQL parameter formats
/// like @Name the @ symbol prefix can be excluded</remarks>
string Name
{
get;
set;
}
/// <summary>
/// Gets or sets the parameter value.
/// </summary>
/// <value>The parameter value.</value>
object Value
{
get;
set;
}
}
Note: Parameter classes are simple data classes and require little or no processing. You will receive instances of these types as an
IParameter through the
ParameterDictionary so you will need to cast them in your command type to get at any specific properties other than
Name and
Value.
Creating Custom Queries
A Query is essentially a wrapper for a
Command object and performs three fundamental functions. Firstly it instantiates the appropriate
Command object or the configured one, if present, and passes control to its
ExecuteForX methods, secondly it handles the command parameters validating them against configuration and finally it handles any instrumentation and logging. For your custom query, then, You need to inherit from the
QueryBase base class which in turn implements the
IQuery interface as follows:
[C#]
public interface IQuery
{
/// <summary>
/// Gets the query name.
/// </summary>
/// <value>The name.</value>
string Name
{
get;
}
/// <summary>
/// Gets the custom command.
/// </summary>
/// <value>The custom command.</value>
ICustomCommand CustomCommand
{
get;
}
/// <summary>
/// Gets the query parameter set.
/// </summary>
/// <value>A set of parameters.</value>
IParameterSet ParameterSet
{
get;
}
/// <summary>
/// Gets or sets the instrumentation provider.
/// </summary>
/// <value>The instrumentation provider.</value>
QueryInstrumentationProvider InstrumentationProvider
{
get;
}
/// <summary>
/// Executes a read command and returns the results.
/// </summary>
/// <returns>
/// Dataset containing the results of the read operation
/// </returns>
/// <remarks>
/// <para>this method requires the parameter values to be set through the properties.</para>
/// <para>Output parameters will update the parameter property values.</para>
/// </remarks>
DataSet ExecuteForRead();
/// <summary>
/// Executes a read command and returns the results.
/// </summary>
/// <param name="parameterValues">The parameter values.</param>
/// <returns>
/// Dataset containing the results of the read operation
/// </returns>
/// <remarks>
/// <para>This method takes the parameter values from the given dictionary.</para>
/// <para>Output parameters will update the parameter property values.</para>
/// </remarks>
DataSet ExecuteForRead(IDictionary<string, object> parameterValues);
/// <summary>
/// Executes a write command and saves data to the data source.
/// </summary>
/// <remarks>
/// <para>this method requires the parameter values to be set through the properties.</para>
/// <para>Output parameters will update the parameter property values.</para>
/// </remarks>
void ExecuteForWrite();
/// <summary>
/// Executes a write command and saves data to the data source.
/// </summary>
/// <param name="parameterValues">The parameter values.</param>
/// <remarks>
/// <para>This method takes the parameter values from the given dictionary.</para>
/// <para>Output parameters will update the parameter property values.</para>
/// </remarks>
void ExecuteForWrite(IDictionary<string, object> parameterValues);
}
Note1: If a Custom Command is present then this must be used in preference to your built-in wrapped
Command. The
Command property of the
ICustomCommand will provide you with a reflected instance of the custom command.
Note2: The
QueryFactory is responsible for building the
ParameterSet from configuration. The
Parameters property of this set gives you the
ParameterDictionary that you need to pass to your command.
Note3: See the
Instrumentation page for details on instrumentation, however, it is the responsibility of your query to maintain counters and log errors etc. The
QueryBase class provides two convenient methods;
UpdateReadCounters() and
UpdateWriteCounters() to handle the mechanics for you. You simply need to call these within the appropriate
ExecuteForX method.
Note4: The
ExecuteForRead() and
ExecuteForWrite() methods are intended to be wrappers only to the equivalent methods in your
Command object. It is best not to put any additional processing here apart from instrumentation calls, exception handling and object instantiation (via the
BuildCommand() method. See Note5). The overloaded method calls are to provide parameter value population and query execution in one step.
Note5: The
QueryBase class provides one other protected abstract method;
BuildCommand(). This method is intended for the instantiation of your
Command object and where you will pass through the parameters and any other configured data to your
Command. You then call this method in each of your
ExecuteForX methods to get at your
Command.
Configuring your Custom Extension
See the section on
Configuration for details on how to configure a
CustomQuery or a
CustomParameter. Note that a Custom Command does not need to be configured unless you want to allow one-off customisations and then you will need to interface into the
EntLibContrib.Query.Configuration.Design project. The important thing to note is that all the parameters that you need to pass to your custom query or parameter constructor, will be configured through the
Attributes collection:
Note: these custom attribute values are static. If you need different attribute value choices then you will need to implement a custom query or parameter for each attribute value choice. Note that you will need one attribute to be able to hold a
ParameterSet name and perhaps one for a
CustomCommand name if this is to be allowed.
Finally you must register your custom type. Click on the
ellipsis button next to the
TypeName property to load the
type selector, you will not be able to load any assembly that does not contain the appropriate custom type:

Now you are done configuring, you should be able to execute queries using your custom query provider and custom parameter types, if specified, in exactly the same way as any other query provider.