Example 3: Specialized Query Interface

It is not an uncommon complaint that the advanced finder is hard to use. It can generate a query for anything in the database of course, but in some cases it can be easier to create a specialize query interface.

You have a couple of choices on how to do this. You could:

  • Create a completely general query interface that is just easier to use and looks nicer.

  • Use a "fake" entity to create a query by example form. You can do this if the input controls and operators you want to use are highly fixed and very tightly specified.

  • Create a hybrid where you constrain the types of things you can query but retain flexibility similar to but not as robust as a general query interface.

For this example, we will the last approach and target FetchXML query generate because FetchXML is currently richer than other ways to create queries in Dynamics. It's almost like SQL but restricted somewhat and uses XML syntax.

This also shows the basic idea that freedom at one level is matched by constraints at other levels.

Architecture

The basic architecture we need is:

  • Query (Lowest) Layer: Captures data needed to generate and run FetchXml queries. We don't use XML documents directly in this layer as we want something that is more friendly to upper layers and manipulating XML documents is usually down at the boundaries of an application versus as a storage format within the application.

  • Domain (Middle) Layer: The middle layer supports the UI and translates data coming from the UI to a representation of the lowest layer. It can either contain artifacts from the lowest layer or extend them depending on what's the best match. You do have a choice on how you structure this layer to be closer to the lower level or the UI layer. For example, the middle layer may capture query parameters that act as view models for specific display areas in the UI.

  • UI: The UI may present different controls to the user for different form factors. It needs to use the Domain layer as data in the UI. The UI controls can be hardcoded if the set of parameters to work with is restricted. For example, users can only query the lastname of a Contact. Or it can be metadata driven and use data from the Domain layer as well as Dynamics metadata and other configuration metadata to generate the UI. Highly restricted interfaces are much easier to code and represent in the middle layer because there are fewer options and you can move the "metadata" into "code" in order to structure data needed by the lowest layer.

The tradeoff between a hardcoded UI and a metadata driven UI is one of generality. If you want a completed general UI, then it must be metadata driven because it has to cover all the ways that a query can be generated. It is difficult to create an easier-to-use query interface in this case. This is why the Dynamics "finder" interface is so had to use--it has to completely general queries. As the range of queries is restricted and the UI simplified, you can start creating structures in the Domain and Query layer that support flexibility in the UI layer but are much easier to work with.

Our basic model is a query parameter has to know which "attribute" it is related to, what the operator is e.g. = or between and some values that go with the operator. Along with some metadata, we can generate a FetchXML query from a parameter. Each parameter may generate one or more fetchxml fragments as we could dream-up an operator that does not exist in fetchxml but makes sense to the user e.g. pick 2 of 3--or something like that. Multiple parameters may exist for the same attribute.

Our system has to support creating UI controls for both the operator and the values. The final UI will shape how parameters are shown on the screen e.g. grouped together or apart, but control generation can came from a factory function. A factory should know how to generate controls or entering query parameters for strings, dates or numbers. A factory parameterizes UI creation. While we could think about dependency injection to wire this altogether, simple functional approaches are sufficient and often replace the need for dependency injection technologies.

Last updated