Crystal Reports provides direct access or native drivers for some databases. These drivers are written specifically for a particular database and are often the best choice. However, because hundreds of types of databases exist, Crystal Decisions can't possibly write direct access drivers for all of them. So often, users turn to using standard data access layers such as ODBC or OLEDB to connect to their databases. Often, the vendor of a database will provide an ODBC driver or OLEDB provider so that other applications can access the vendor's database. Sometimes though, even this is not enough. Customers have data that they would like to report off of that is not accessible by any Crystal Reports data source driver or via ODBC or OLEDB. To this, customers often turn to the COM Data Source driver or the Java Data Source driver. This section describes the COM version of the driver, but much of the theory applies to the Java Data Source driver as well.
Because COM is a popular technology, Crystal Decisions decided to leverage it to create an extensible data source driver mechanism. This COM Data Source driver doesn't connect to a database rather it gets data from a COM object written by you. This means that if you are somewhat savvy in the Visual Basic world, you can write your own "mini data source driver" (called a COM Data Provider) that enables access to data that would otherwise be unavailable. To better understand the concept on writing your own COM Data Provider, let's look at a few scenarios in which this can be beneficial. Legacy Mainframe DataAlthough new technologies are surfacing at an alarming rate, many companies still have data held in legacy mainframe systems. Often, the nature of these systems doesn't allow for any kind of relational data access, and thus lowers the value of the system. However, these systems can often output text-based files containing the data held in them. These text-based files are often more complicated than a set of simple comma separated values and thus require a "bridge" between the files and a data access and reporting tool like Crystal Reports. Writing a COM Data Provider can serve just this purpose. The Data Provider would read the text files, parse out the required data, and return it to Crystal Reports for use in numerous reports. Complex QueriesOften, companies have a database that is accessible via standard Crystal Reports data access methods. However, the process of connecting to the database and performing a query can be quite complex. Sometimes this is because of database servers constantly changing, queries becoming more complex, and other business processes. By writing a COM Data Provider, a clever person can abstract the location and complexity of the database interaction away from the user designing a report. The user simply connects to the Data Provider, and the rest of the logic is done transparently in the background. Runtime Manipulation of DataPerforming a simple query against a database that returns a set of records is often all that is needed. However, sometimes logic needs to be incorporated into the query that cannot be expressed in the database query language (using SQL). Other times, per-user manipulation of data needs to be performed, such as removing all salaries stored in a database for all other users than the currently logged in user for confidentiality purposes (often called data-level security). This runtime manipulation can be performed by a COM Data Provider. These three scenarios outline just a few of the reasons why you might want to use the COM Data Source driver and create your own COM Data Provider. The following sections describe the technical details of doing this. Creating a COM Data ProviderCOM Data Providers can be written in any development language or platform with the capability of creating COM objects. Most commonly, they are created in either Visual Basic or Visual C++. The following example uses Visual Basic, but it can easily be translated to other development languages:
Returning an ADO RecordsetThere are generally two ways to obtain an ADO recordset: performing a database query and constructing it yourself. The following code example illustrates how to perform a database query and obtain a recordset in this case using a query against the Xtreme sample database. Public Function CustomerOrders() As ADODB.Recordset Dim rs As New ADODB.Recordset Dim sql as String sql = "SELECT * FROM Customer, Orders WHERE Customer.`Customer ID`" sql = sql & " = Orders.`Customer ID`", "DSN=Xtreme Sample Database 9" rs.Open sql Set CustomerOrders = rs End Function The question you might be asking yourself is how this query could be parameterized. The COM Data Source driver handles this nicely. It will map any arguments you have defined to your method into report parameters. The following code example illustrates a Data Provider function that has a parameter: Public Function Customers(CountryParam As String) As ADODB.Recordset Dim rs As New ADODB.Recordset rs.Open "SELECT * FROM Customer WHERE Country = '" & CountryParam & "'", _ "DSN=Xtreme Sample Database 9" Set Customers = rs End Function When a Data Provider with a parameterized method is used from the report designer, the user will be prompted for a parameter value. As was mentioned previously, one way to obtain a recordset is to perform a query. Listing 21.1 illustrates how to construct a recordset on-the-fly and read data out of a text file. Listing 21.1 A COM Data Provider that Parses Data from a CSV FilePublic Function CSVText(FileName As String) As ADODB.Recordset Dim rs As New ADODB.Recordset ' Open the text file Dim FileSystem As New IWshRuntimeLibrary.FileSystemObject Dim fileText As IWshRuntimeLibrary.TextStream Set fileText = FileSystem.OpenTextFile(FileName) ' Read the first line of text to grab the field names Dim buffer As String buffer = fileText.ReadLine() Dim fields() As String fields = Split(buffer, ",") Dim i For i = LBound(fields) To UBound(fields) ' Add a field in the recordset for each field in the csv file rs.fields.Append fields(i), adBSTR Next rs.Open ' Read the contents of the file While Not fileText.AtEndOfStream buffer = fileText.ReadLine() rs.AddNew For i = LBound(fields) To UBound(fields) ' Grab the field values fields = Split(buffer, ",") rs(i).Value = fields(i) Next rs.Update Wend Set CSVText = rs End Function This code could be used as is or adopted to meet the needs of other kinds of files or data sources. Using the COM Data Source driver gives you complete flexibility and control over the data source. |