If you’ve read this series up to this point, you pretty much have everything you need to implement dynamic queries in your own application. Just a few notes to wrap up the series and I will get on to other topics.
Creating a Search Service Facade
Depending on your requirements, it may be perfectly reasonable to create a tight coupling between DTO and search implementation. In cases where the return object of the search is strongly typed (i.e. not a dataset/datatable), this actually may be a necessity. However, there are a number of reasons why creating a simpler “catch-all” service interface might be desirable. You may want to actually develop the parameter/DTO implementation and query implementation separately, so your UI developer can write their code before the query has been written. You may simply want to hide the complexity of the Data Access Layer from other layers of the application. Or your search service may actually be part of a “real” service layer (SOA).
In any case, you may want to utilize a simple service facade for search execution that will take in any of your DTO types and determine the correct type of query to perform. The basic idea is that the consumer using the search service only needs knowledge of the parameter/DTO object and the service interface itself. Any coupling between the DTO and the query implementation exists only behind the service facade. So the developer calling the service needs only to set the desired values on the DTO and call SearchService.ExecuteSearch(myDTO).
A simple, first-pass approach might look something like this:
This looks a little ugly because I didn’t use an interface for my service (or DTO for that matter). A more permanent implementation might use a factory to select the query, and could look something like this (consider this pseudo-code!):
Since I started using this technique in ~2005, I have implemented it on at least five systems with consistent success. The key to that success? Basing the design of the framework on the specific needs of each project.
- In one of the implementations I worked on, virtually every query against the database needed to be filtered by client Id, so it made sense to create an abstract base query class, with an abstract method FilterByClientId which had to be overridden in each derived class.
- In another project the requirement to filter on multiple values in the same field (with different comparison operators) came late in the development cycle, and we had to scramble to update the framework to support this.
- In a third project, the query specification needed to be persisted to the database, so the DTO was actually a representation of a set of parameter values that could be used repeatedly to formulate the same query (this was for a reporting system).
Because this technique employs OOP concepts like encapsulation and loose coupling, responding to changing requirements is made much easier, and some measure of flexibility is built right in.
I’m not sure of the EXACT topic of my next post yet, but I have done a lot of work with an Inversion of Control (IOC) framework in the last year, and I have come to believe that the benefits of using these frameworks are huge. So my next post(s) will share some code and/or thoughts around utilizing an IOC container.