Adding Some Punch
The data service you created above will only work on a local network. You need to use a different data service if you want to access data over the Internet. You also need to use a different data service if you want to access offline data. However, it is relatively easy to create such data services and use them instead of the SQL Server data service, as all these services will adhere to the interface you created before. Therefore, these data services are completely interchangeable.
There are also a few little oddities about the code sample above. For instance, you created a variable of type IDataService and then assigned it a new instance of a SqlServerDataService object. The burden to decide which data service object is to be used is up to the developer. That may not be a good situation if you wanted to switch between various data services on the fly. What you need instead is a way to automatically instantiate a data service based on configuration settings and system state.
|This architecture allows you to dynamically switch between various ways of accessing data without the rest of the application ever noticing.|
You can take care of this problem by introducing a special object that does nothing but instantiate data services. Call this object your data service factory. Listing 2
shows the implementation of the data service factory.
The factory features a single method called GetDataService()
important here. This method is static (Shared
, in Visual Basic terms), so it can be called without having to create an instance of the DataServiceFactory object first. The implementation is straightforward: The method retrieves a configuration setting and then instantiates the service object based on that setting. Note that it supports more than one setting separated by commas. It will try them one after the other until it finds a valid one. This allows you to define that you want to use the SqlServerDataService if possible, but if that fails, you want to use a different service (which you will create below).
All that is left to make the factory work is to modify the configuration file to the following:
<?xml version="1.0" encoding="utf-8" ?>
<add key="UserId" value="devuser"/>
<add key="Password" value="devuser"/>
<add key="Server" value="(local)"/>
<add key="Database" value="Northwind"/>
In the data query example above, you also create a variable of type IDbCommand, but then assigned it a new instance of a SqlCommand object. This may not be intuitive to developers. Also, the SqlCommand object really has to match the SqlServiceDataService object. If your data service factory created an Oracle data service, the SqlCommand object won't work. However, you can assume that each individual data service knows what type of command object it expects. This is a task you can offload to the data service as well.
Often, when dealing with command objects, you may want to add command parameters. Each IDbCommand has a collection of parameters. Unfortunately, this collection does not feature an Add()
method that would make it easy to add new command parameters as the SqlCommand object does. This makes it unnecessarily difficult to add parameters, and you can ease that pain by adding another method to the interface that also takes care of that task. Here is version 2 of the interface:
public interface IDataService
DataSet ExecuteQuery(IDbCommand command,
void ExecuteNonQuery(IDbCommand command);
object ExecuteScalar(IDbCommand command);
void AddCommandParameter(IDbCommand command,
string name, object value);
As you may have noticed, Listing 1
already implements this version of the interface.
With the changes you have now made, you can modify the data query example to the following:
IDataService svc =
IDbCommand cmd =
"SELECT * FROM Customers");
DataSet dsCustomers =
|I expect powerful data access infrastructure to handle very complex scenarios, yet be easier to use than plain ADO.NET.|
You can also add command parameters as shown in this example:
IDbCommand cmd =
"SELECT * FROM Customers WHERE Name = @Name");
At this point, you have infrastructure that is reasonably easy to use (although I will investigate ways to make things even easier). This is especially true considering how powerful the infrastructure already is. The code sample above can be executed against any database on the LAN, over the Internet, or offline, assuming you write appropriate data services and register them with the factory. The system also has the capability to fail over to other data services, such as using an Internet-enabled data service when the LAN service turns out not to be valid.