
Dr.Dobb's journal.2005.12
.PDF
An Inside Job.
9.0
available NOW!
Want to enhance your application’s performance? Looking to transition faster to 64 bits or make the move to multi-core, ahead of your competition? If only you knew someone on the inside. Someone who really knows the hardware. Turns out you do.
Intel® Software Development Products. Tools for software from the makers of hardware.
“The new Intel® compiler version 9.0 ran all of the Polyhedron Benchmarks without requiring any changes and showed worthwhile speed improvements. The 64 bit version ran very nicely, even on a non-Intel processor.”
John Appleyard, Polyhedron*
Performance
Intel® Software Development Products put Intel’s inside knowledge of high-performance computing platforms in the hands of leading application developers. Their search for exceptional new levels of performance is simplified with features such as auto-parallelization, OpenMP, debuggable release build, and more.
Compatibility
•Compatible with development tools most programmers already use for Windows*-based application development. Source and binary compatible with Microsoft Visual C++ 6.0 and .NET.
•Source and object compatibility with GCC version 3.2 and above, and other widely used utilities in the Linux development tool chain. Intel® C++ Compiler also conforms to the C++ ABI standard.
•A powerful combination of a Compaq Visual Fortran (CVF) front-end and Intel® Fortran back-end, along with substantial CVF source code compatibility.
Support
Intel® Premier Support and product upgrades included for 1 year.
multi-
NEW
Version!
®
To order or to request additional information
Call: 800-423-9990
Email: intel@programmers.com
FREE trials available at: programmersparadise.com/intel
YOU SAVE UP TO $106! |
Paradise # |
Retail |
Discount |
|
|
|
|
Intel® C++ Compiler 9.0 for Windows |
I23 0BK0 |
$399.00 |
$369.99 |
Intel® C++ Compiler 9.0 for Linux |
I23 0BKJ |
$399.00 |
$369.99 |
|
|
|
|
Intel® Visual Fortran Compiler 9.0 Std Ed. Win |
I23 0BLM |
$499.00 |
$461.99 |
Intel® Visual Fortran Compiler 9.0 Pro Ed. Win |
I23 0BM6 |
$1,399.00 |
$1,292.99 |
|
|
|
|
Intel® Fortran Compiler 9.0 for Linux |
I23 0BL3 |
$699.00 |
$646.99 |
Intel® Threading Tools 2.2 |
I23 0BNT |
$699.00 |
$646.99 |
|
|
|
|
Intel® IPP 4.1 for Windows |
I23 0B8E |
$199.00 |
$184.99 |
© 2005 Intel Corporation, Intel, the Intel logo, Pentium, Itanium, Intel Xeon and VTune are trademarks or registered trademarks of Intel Corporation or its subsidiaries in the United States and other countries. *Other names and brands maybe claimed as the property of others.

(continued from page 18)
circumstances, such as storing data for an actual general ledger application. Although it still fills those same requirements today, the ledger structure’s usefulness is no longer quite so rare. As implied by the ledger moniker, a ledger table acts like an accounting book with the application of credits and debits (see Figure 3). With proper use of the ledger archetype, only inserts occur on this table. The ledger table is a personal favorite, often referred to as a “sticky” table; like a fly strip, once data lands here, it is meant to stay there forever.
The ledger table can require a significant amount of work. For this technique to function properly, all numbers must “balance” when summed. This means that when items change, the changes must be evaluated before allowed for insertion to the ledger table. If a previous column entry of +5 must be “updated” to a +6 value, rather than updating the +5 to a +6, either a “net change” or “gross change” approach must be used to insert new rows into the ledger table. Using a net change approach, a row is inserted for a value of +1 (see Figure 3). The previous +5 and the new +1 will sum up to the correct +6 value. Under the gross change tactic, two rows are inserted for the single change. First, a row for – 5 is inserted, which logically reverses the previous +5 row. Next, a row for the +6 is inserted (see Figure 4). The ledger table, by its nature, is transactional. Circumstances of spotty or incomplete transactional pictures in the incoming data content mean that greater work must be done by the process creating the ledger table inserts.
Putting It All Together
Any specific application requires incorporating several of the structures described here; see Listing One. Even a single table may take advantage of a combination of approaches. Actually, the row-level or column-level audit functions best in tandem with either a current-only or functional-history structure. Other useful configurations may not be so obvious. For example, a functional-history table may be implemented only partially by using a subset of columns for triggering new row insertion. (For example, a change in the status code causes an update to the stop date and the insert of a new row; but an update of the text inside a comment column may not cause any response at all on this functional-history table.) Because of this partiality of the functional-history structure, perhaps a shadow table is implemented to catch all changes. In this fashion, changes that do not drive a new row insertion are not lost. While this double-table approach does increase overall application complexity, it may form a valid choice under certain circumstances.
History concerns do not have a single one-size-fits-all solution. As the various detailed archetypes demonstrate, a great deal of flexibility exists in describing the nature of a table’s relationship to change over time. Current-only tables may contain noncritical data items or may be used in conjunction with other archetypes, such as row-level audit, to cover time-variant value changes. Applications often use
Figure 4: Ledger structure using a gross change approach.
functional-history tables so that these tables are probably the most typical of our historical data archetypes. Row-lev- el audit structures can allow for a current-only operational table to remain smaller and more quickly responsive to queries. Column-level audit tables offer value for users who wish to browse precise changes within the application itself. And lastly, under the right set of circumstances, ledger tables can maintain an explicit track of fully comprehensive transactional change.
Alternately, overlooking the evaluation of these options can leave systemic blind spots. Such a case could happen if employing a functional-history that only inserts rows on a limited set of changes to handle one user requirement, but without duly considering a different requirement that all changes be tracked for auditing. Therefore, a hole that could have been filled by adding in a row-level audit table was missed. On the other hand, if you created a column-level audit table because some changes needed to be browsed interactively online, but then simply continued to use that one column-level audit structure for all other change tracking, a monster is born. The single column-level audit table would quickly become the largest table in the application (row-wise) and create a hazardous performance bottleneck.
Ideally, within a development group or organization, top priorities should include evolving a strategy that identifies the preferred historical archetypes and deciding a preference of the history archetype options for developers to employ. Documentation can be as simple as describing the history archetypes, or their variations germane to a specific shop’s environment, and enumerating appropriate conditions suitable for the use of each archetype. Providing such a carefully thought out course of action often expedites application development and minimizes history retention implementation problems. When development practices incorporate detailed plans for establishing and maintaining history, these plans help achieve a greater level of consistency and reuse across all of an organization’s applications.
DDJ
Listing One
/* TABLE: Employee_Current_Only */
CREATE TABLE Employee_Current_Only( |
|
|
Employee_ID |
int |
NOT NULL, |
Employee_Name |
varchar(100) |
NOT NULL, |
Employment_Status |
varchar(10) |
NOT NULL, |
Create_Dt |
datetime |
NOT NULL, |
Create_ID |
char(10) |
NOT NULL, |
Altered_Dt |
datetime |
NOT NULL, |
Altered_ID |
char(10) |
NOT NULL, |
CONSTRAINT PK_Employee_Current_Only PRIMARY KEY CLUSTERED (Employee_ID)
)
go
IF OBJECT_ID('Employee_Current_Only') IS NOT NULL
PRINT '<<< CREATED TABLE Employee_Current_Only >>>'
ELSE
PRINT '<<< FAILED CREATING TABLE Employee_Current_Only >>>'
go
/* TABLE: Employee_Functional_History |
*/ |
|
CREATE TABLE Employee_Functional_History( |
||
Employee_ID |
int |
NOT NULL, |
Start_Dt |
datetime |
NOT NULL, |
Employee_Name |
varchar(100) |
NOT NULL, |
Employment_Status |
varchar(10) |
NOT NULL, |
Create_Dt |
datetime |
NOT NULL, |
Create_ID |
char(10) |
NOT NULL, |
Altered_Dt |
datetime |
NOT NULL, |
Altered_ID |
char(10) |
NOT NULL, |
Active_Ind |
char(1) |
NOT NULL, |
Stop_Dt |
datetime |
NULL, |
CONSTRAINT PK_Employee_Functional_History PRIMARY
20 |
Dr. Dobb’s Journal, December 2005 |
http://www.ddj.com |

KEY CLUSTERED (Employee_ID, Start_Dt)
)
go
IF OBJECT_ID('Employee_Functional_History') IS NOT NULL
PRINT '<<< CREATED TABLE Employee_Functional_History >>>'
ELSE
PRINT '<<< FAILED CREATING TABLE Employee_Functional_History >>>'
go
/* TABLE: Employee_Row_Level_Audit */ |
|
|
CREATE TABLE Employee_Row_Level_Audit( |
|
|
Employee_ID |
int |
NOT NULL, |
Start_Dt |
datetime |
NOT NULL, |
Employee_Name |
varchar(100) |
NOT NULL, |
Employment_Status |
varchar(10) |
NOT NULL, |
Create_Dt |
datetime |
NOT NULL, |
Create_ID |
char(10) |
NOT NULL, |
Altered_Dt |
datetime |
NOT NULL, |
Altered_ID |
char(10) |
NOT NULL, |
Active_Ind |
char(1) |
NOT NULL, |
Action_Code |
char(10) |
NOT NULL, |
CONSTRAINT PK_Employee_Row_Level_Audit PRIMARY
KEY CLUSTERED (Employee_ID, Start_Dt)
)
go
/* TABLE: Employee_Column_Level_Audit_Option_1 */ CREATE TABLE Employee_Column_Level_Audit_Option_1(
Employee_ID |
int |
NOT NULL, |
Event_Timestamp |
datetime |
NOT NULL, |
Column_Name |
varchar(80) |
NOT NULL, |
Old_Text |
varchar(100) |
NULL, |
New_Text |
varchar(100) |
NULL, |
Old_Number |
decimal(10, 2) |
NULL, |
New_Number |
decimal(10, 2) |
NULL, |
Old_Date |
datetime |
NULL, |
New_Date |
datetime |
NULL, |
Event_ID |
char(10) |
NOT NULL, |
CONSTRAINT PK_Employee_Column_Level_Audit_Option_1
PRIMARY KEY CLUSTERED (Employee_ID, Event_Timestamp)
)
go
IF OBJECT_ID('Employee_Column_Level_Audit_Option_1') IS NOT NULL
PRINT '<<< CREATED TABLE Employee_Column_Level_Audit_Option_1 >>>'
ELSE
PRINT '<<< FAILED CREATING TABLE Employee_Column_Level_Audit_Option_1 >>>'
go
/* TABLE: Employee_Column_Level_Audit_Option_2 */
CREATE TABLE Employee_Column_Level_Audit_Option_2(
Employee_ID |
int |
NOT NULL, |
Event_Timestamp |
datetime |
NOT NULL, |
Column_Name |
varchar(80) |
NOT NULL, |
Old_Text |
varchar(100) |
NULL, |
New_Text |
varchar(100) |
NULL, |
Old_Number |
varchar(30) |
NOT NULL, |
Event_ID |
char(10) |
NOT NULL, |
CONSTRAINT PK_Employee_Column_Level_Audit_Option_2
PRIMARY KEY CLUSTERED (Employee_ID, Event_Timestamp)
)
go
IF OBJECT_ID('Employee_Column_Level_Audit_Option_2') IS NOT NULL
PRINT '<<< CREATED TABLE Employee_Column_Level_Audit_Option_2 >>>'
ELSE
PRINT '<<< FAILED CREATING TABLE Employee_Column_Level_Audit_Option_2 >>>'
go
/* TABLE: Employee_Paycheck_Ledger |
*/ |
|
CREATE TABLE Employee_Paycheck_Ledger( |
|
|
Employee_Paycheck_Event_Key |
uniqueidentifier |
NOT NULL, |
Employee_ID |
int |
NOT NULL, |
Pay_Date |
datetime |
NOT NULL, |
Pay_Amount |
decimal(10, 2) |
NOT NULL, |
Event_Type |
char(10) |
NOT NULL, |
Created_Dt |
datetime |
NOT NULL, |
Created_ID |
char(10) |
NOT NULL, |
CONSTRAINT PK_Employee_Paycheck_Ledger PRIMARY
KEY CLUSTERED (Employee_Paycheck_Event_Key)
)
go
IF OBJECT_ID('Employee_Paycheck_Ledger') IS NOT NULL
PRINT '<<< CREATED TABLE Employee_Paycheck_Ledger >>>'
ELSE
PRINT '<<< FAILED CREATING TABLE Employee_Paycheck_Ledger >>>'
go
IF OBJECT_ID('Employee_Row_Level_Audit') IS NOT NULL
PRINT '<<< CREATED TABLE Employee_Row_Level_Audit >>>'
ELSE
PRINT '<<< FAILED CREATING TABLE Employee_Row_Level_Audit >>>'
go
DDJ

ADVERTISEMENT
> The Microsoft SQL Server 2005 Insider
By BERNARDO ZAMORA
Senior Product Manager
SQL Server Marketing
Microsoft Corporation
Boostyour
line-of-business application
Choosing the data platform for a line of business is a complex decision: Factors such as availability and reliability, database support, performance, existing DBA skills, license cost and TCO have to be carefully considered.
Database products from Microsoft have come a long way – from SQL Server 6.5 to the new SQL Server 2005. Each version has improved on key
features such as reliability, security, business intelligence and ease of use. The newest Microsoft database is positioned to be a complete data platform for enterprise
line-of-business applications. How does it perform in real-life applications?
ENHANCEMENTS FOR PACKAGED APPLICATIONS
Applications benefit from SQL Server 2005 simplified deployments and ease of maintenance. The database engine is aware of changes in the application data usage and automatically optimizes key configuration parameters with no manual intervention. For fine-tuning, modern statistics-based sampling minimizes overhead and allows for in-depth analysis of index and query utilization. With SQL Server 2005, most common operations can be performed online, thus reducing the time the application is not available. Online operations include all index operations, incremental re-indexing with read-consistent scans, and fine-grained online repairs. These enhancements
enable DBAs to focus on high-value tasks such as database architecture while spending less time on routine maintenance, configuration and tuning.
Mission-critical applications have increased scalability and availability through clustering, online operations, database snapshots, improved lock handling, enhanced security features and scaling-out with table partitioning. For the most demanding applications, 64-bit SQL Server 2005 enables virtually unlimited memory utilization, as well as speed improvements through optimizations to the 64-bit database engine. A few months after SQL Server 2005 launches, database mirroring with transparent failover in seconds will be available for production environments.
BUSINESS INTELLIGENCE
With SQL Server 2005, businesses can extend the value of the existing line-of-business application regardless of the underlying platform, through a complete set of data-management tools that enable scalable business intelligence by putting critical, timely information in the hands of all employees. From the CEO to the information worker, employees can quickly and easily harness data to make better decisions faster.
Businesses can extract, move and use information from many different applications through SQL Server Integration Services (SSIS). Companies can utilize existing
MICROSOFT SQL SERVER 2005 [ DECEMBER 2005 ] www.microsoft.com/sql/2005

ADVERTISEMENT
data from, for example, DB2, Oracle, Informix, OLDB, text/excel files; perform complex data transformations; and load results to any other database. Features such as fuzzy logic and a 50 percent performance increase over DTS enable customers to use SSIS as standalone tool to create and maintain data warehouses.
SQL Server 2005 allows companies to offer datamining capabilities to end users without requiring complex training. Users can create cubes and analyze application information through a simple, intelligent interface to suggest and create the dimensions, and a new datamining engine allows users to view data – whether it is relational or multidimensional.
Taking advantage of application data is also simplified via the new Report Builder. Business users can build ad hoc reports from data views without having to understand the underlying data structure.
SAP IS READY FOR SQL SERVER 2005
One of the key applications that Microsoft uses to test and tune SQL Server is mySAP ERP. As a result of the collaborative work with SAP since 1993, 65 percent of all new SAP implementations are on Windows Server, and more than 40 percent of all new SAP implementations are on SQL Server. SAP and Microsoft are currently co-developing the next-generation interface for the information worker: accessing mySAP application data through the Microsoft Office user interface.
Implementations using mySAP can have more than 45,000 tables. SQL Server 2005 does not need modifications, special versions, or tuning to handle the complexity and scale.
Customers can deploy the largest mySAP ERP loads on SQL Server 2005 64-bit edition. Nearly unlimited memory and 64-bit database engine optimizations enable fast and responsive multiterabyte Unicode and SAP Business Warehouse deployments.
SIEBEL ENHANCEMENTS WITH SQL SERVER 2005
More than 80 percent of all customers run Windows Server on the Siebel application layer, and more than 40 percent of all the new Siebel installations run on SQL Server. Siebel has leading SQL Server platform scalability and performance benchmarks, from 5,000 to 30,000 concurrent users. Siebel supports all customer environments, from 32-bit to 64-bit very large implementations.
Siebel customers will see performance improvements under SQL Server 2005 for large implementations. The virtual space has increased to 4GB of memory in the 32-bit platform (some of which is used by the operating system) and to nearly unlimited for x64 implementations, thus increasing the space available for ODBC cursors – a critical limitation for Siebel as it opens hundreds of
© 2005 Microsoft Corporation. All rights reserved. Microsoft is a registered trademark of Microsoft Corporation in the United States and/or other countries.
simultaneous cursors. New T-SQL statements provide a 3x performance boost for Siebel queries.
IMPROVEMENTS FOR MICROSOFT DYNAMICS™
All future versions of Microsoft Dynamics AX, Microsoft Dynamics GP, Microsoft Dynamics NAV, Microsoft Dynamics SL and Microsoft Dynamics CRM are being designed to take advantage of the data-management, enhanced performance, business intelligence and highavailability features of SQL Server 2005.
Of particular relevance is that these products are being enhanced with the new reporting capabilities of SQL Server 2005, including enabling Report Builder and SQL Server Reporting Services (SSRS) as a reporting platform with the next releases. Users will benefit from using Report Builder for ad hoc reporting with no modification to the product.
Other SQL Server 2005 capabilities include stronger security features, such as automatic encryption and strong password management, as well as a unified dimensional model (UDM) – a new tool that lets users analyze data, do BI reports, data mining, etc, from both relational and multidimensional data in the same report/cube/analysis.
MISSION-CRITICAL RESULTS
The Winter Corp September 2005 report on the largest databases in the world lists SQL Server in three entries in the top ten largest OLTP systems category, SQL Server as the third largest OLTP system in the world with 50 billion rows – larger than any Oracle system – and includes a SQL Server 20TB data warehouse. SQL Server customers such as Nasdaq are running multiterabyte installations with more than 99.998 percent availability and more than 32,000 transactions per second (peak). A recent SAP benchmark on SQL Server 2005 demonstrated that a single, standard four-processor server can support more than 18,000 concurrent users.
These examples show that SQL Server 2005 is ready to support and enhance the most-demanding applications.
>For more information visit: www.microsoft.com/sql www.microsoft-sap.com www.siebelonmicrosoft.com
www.microsoft.com/businesssolutions/default.mspx
www.microsoft.com/sql/2005 [ DECEMBER 2005 ] MICROSOFT SQL SERVER 2005

Rapid Data Access
Tier Implementation
Automating the creation of domain-specific classes
JOHN CHENG, ABDUL AKBARI, AND HONG RONG
In today’s enterprise software applications, the database plays an important role. Hence, the database access tier becomes a vital component. Luckily there are many libraries, such as ODBC, JDBC, ADO, and the like, that simplify implementation. Nonetheless, finding suitable design patterns and implementing them involves the tedious task of writing domain-specific access classes. This is still the responsibility of developers. Moreover, a considerable amount of time needs to be spent maintaining this layer when the schema is not static, but continues to evolve, especially when the database schema contains a large number of tables. In this article, we present techniques for automating this process. In doing so, we introduce a lightweight tool that you can use to generate the access layer from a template that can be customized to the
various access libraries.
Data Entity and Table Relational Data Mapping
A database access tier needs to manage two tasks:
•Handle the encapsulation of the relational data stored in a database.
John is a senior software engineer and Abdul is a technical manager at Sabre Airline Solutions. Hong is an independent consultant. They can be contacted at johnxjcheng@comcast.net, abdul_ akbari@yahoo.com, and hongrong1@ comcast.net, respectively.
•Implement the four basic operations: insert, select, update, and delete. (These operations are sometimes also known as “CRUD,” short for Create, Read, Update, and Delete.)
It is generally preferable to implement these two tasks with different objects — one to encapsulate data, and the other to handle the access logic. The one that encapsulates data is usually called “Data Entity” (DE) or “Business Entity.” The one that implements access logic is normally called “Data-Access Logic Component” (DALC) or “Data-Access Object.” Another reason for separating them is when the DE needs to be moved from one tier to another in multitiered systems. Separate tiers can directly use the DE, thus avoiding conversion from one format to another. Typically, the DALC is directly dependent on the language, library, and database connection management and does not lend itself well when transferred from one tier to another.
For single-tier apps, combining the two components has advantages. The biggest advantage of doing this is that it is simpler. Within the same component, it is easier to understand and quick to implement. It cuts the number of objects by half, and this number may be large for a typical mediumto large-size database. Actually, many applications choose to do so.
Data entities can also be categorized according to how they are mapped to a table or tables. A data entity can map to a single table with data members corresponding to the columns, or it can map to a join of tables with data members corresponding to columns across the tables. Note that here you care more about how the columns are mapped to the data members, not how instances of each data entity map to the rows of a table. For the entities that directly map to a table, all CRUD operations can be done easily with the help of data-access logic components. For the entities that map to joints, in many cases it’s not possible or desired to perform the operations other than the select
(read). When considering the member-to- column mapping, the database metadata that need to be considered include table name, column name, column ordinal, column type, and column constraints (key, size, nullable, and so on). The code side information that needs to be considered includes: programming language, data type, and member access permission.
“Data entities can also be categorized according to how they are mapped to a table or tables”
Mapping the relationships between tables is another important point that must be considered. On the database side there are one-to-many, many-to-one, and many- to-many relationships. These relationships are established through foreign keys and many-to-many tables. On the code side, these relationships can be represented as parent-to-child, child-to-parent, and peer- to-peer. The business logic tier can use these relationships for navigation from one entity to another.
Problems and Benefits of Implementing the Data-Access Tier
Implementation of the whole data-access tier, including data entities and data-access logic components, is tedious and time consuming. Each substantial table may require one or more classes and interfaces. For a database that contains hundreds or more
24 |
Dr. Dobb’s Journal, December 2005 |
http://www.ddj.com |


(continued from page 24) |
works are available, both commercially |
|||
tables, the task of coding is huge. Further- |
and through open source. |
|||
more, if the database schema continues to |
Some advanced code-generation tools |
|||
evolve, the maintenance on the code side |
provide another important feature — they |
|||
is also considerable. |
let SQL statements be constructed with the |
|||
To simplify the work, developers have |
generated information at runtime, which |
|||
attempted to get help from code genera- |
helps avoid schema mismatch (Listing |
|||
tors that allow tables and columns in the |
Two). For instance, assume you collected |
|||
schema to be mapped to classes and |
the table and column information, such |
|||
members of the generated code (see List- |
as type, name, size, and ordinal, into some |
|||
ing One). Code generation is a big leap |
static class members. Then, if you have |
|||
in reducing manual work. It helps make |
some assistant objects that can utilize this |
|||
the whole data-access tier code more con- |
collected information to build the SQL |
|||
sistent and predicable. Any improvements |
queries for data-access logic, you can say |
|||
worked out on one database-access class |
that your SQL queries are also “bonded” |
|||
can quickly and easily be applied to the |
to the database schema. |
|||
other classes by means of code genera- |
When writing code to generate the data- |
|||
tion. Any mismatch between a database |
access tier, it requires that you first know |
|||
schema and the application that uses this |
what you want; that is, the look and be- |
|||
schema can be detected by a compiler. |
havior of the code. Then, it requires that |
|||
For example, if a column is modified or |
you know how to get what you want — |
|||
removed, the generated code will reflect |
that is, how to write the generator. There |
|||
this change on a class member or func- |
are some problems with this strategy. For |
|||
tion. Thus, any client code that uses this |
one thing, the generator is specific to one |
|||
class member or function will be forced |
project. For another, the logic of the target |
|||
to make the necessary changes. It leaves |
code and the logic of the generator itself |
|||
no unpleasant surprises at runtime. This |
are often mixed together. It requires you |
|||
ease of propagating changes to the code, |
to devote more time to coding and testing, |
|||
along with the error detection, forms a |
and adhere to a more disciplined practice. |
|||
seamless link between the database |
Here we introduce a tool called |
|||
schema and the application code. Sever- |
“Database Access Generator” (DBAG), de- |
|||
al code generators and persistence frame- |
veloped by Hong Rong. (This code gen- |
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
erator is copyrighted by Hong Rong with the Open-Source Software Alliance License [OSSAL]. The full license content is captured in the license.txt file.) The difference between DBAG and similar tools is that it makes extensive use of code-generation templates, which can be easily expanded. They also adapt easily to the various flavors of an access driver within a particular access technology (for instance, the ADO.NET driver from Microsoft versus that from Oracle). DBAG predefines the common database schema information as keywords. These keywords can be used in a customer template, which is then read by the generator to produce code.
The primary goal for DBAG is to create a lightweight persistence framework through code generation. Some of the features of this tool are:
•Code generation is fully templatized.
•Available under open source.
•Can handle complex situations such as joins and relationships.
•Strong type checking.
•Schema matching for class, class members, and SQL queries.
•Null handling.
•Tested with Oracle and SQLServer, but not limited to any particular RDBMS.
Through the use of code-generation templates, multiple databases (Oracle, SQLServer, and the like), libraries (ADO, ODBC, and so on), and languages (such as C# and C++) can be the target. The data entities and data-access logic components are precompiled for higher performance, and the database schema can be validated against the application for greater type safety. The overall framework is simple and lightweight, and designed for ease of regeneration when the schema is updated.
The tool itself is implemented in C#. It only runs on a Windows platform. It requires a suitable OLE DB driver that can provide the most comprehensive metadata. But this doesn’t mean the template and code are subjected to any of these conditions. Once the code generation is complete, any generic driver may be used by the application during runtime. The template can be in any programming language. It can use any data-access library, coding style, and software-design pattern. The tool may even be used to generate database scripts; for example, the scripts to create routine sequence and triggers. The bottom line is that, as long as you can do something once and know how to abstract the task into a template, the generator can fill in the rest.
Code Generation of Classes for Single Tables
Data entities that map to each single table are the most often used components in
26 |
Dr. Dobb’s Journal, December 2005 |
http://www.ddj.com |

a data-access tier. These components are normally implemented as classes in a procedural language, such as C++, Java, and C#. The class members, properties, and methods are often mapped to the columns of the corresponding table. These kinds of classes may or may not implement the DALC functionality as just mentioned. When using the DBAG tool to generate these kind of classes, you need to:
1.Let the tool connect with the database with the selected driver and read the schema information.
2.Provide your mapping preference, such as name mapping convention, type mapping convention, and class member access convention.
3.Provide your specific code template. After that, the generator produces the code you want.
Figure 1 shows the main interface of DBAG. This interface shows the collected table information based on Oracle’s sample scott/tiger schema. The name, type mapping, and access control convention are stored in the DBAG.ini configuration file. Listing Three presents code template segments and the generated C# code fragments from this generator.
DBAG is just a tool. It depends on you telling how to perform the task. The dictation is done through templates. DBAG provide a list of control commands and keywords to be used in your templates. These commands and key words are listed in Listing Four.
A well-designed database requires that each table have an identifier (a natural key). In most cases, an identifier is also a key, but that’s not always the case. There are many tables that have an identifier but no key. Both the identifier and the key are important concepts in a database. Without them, it will be hard or inefficient for a database access tier to perform update and delete operations.
Compared to an identifier, the key is a more widely recognized concept across relational databases. DBAG can get the key information from tables easily and pass it to the template. If you have con-
trol of the database schema, you may want to make sure that every table has at least the primary key defined. If a table does not have a natural key, you may want to create an artificial one. For example, create an Object ID (OID) column that gets its values from the database sequence, autonumber, or identity. In our experience, the artificial key is easier to use in a template because the names and types are more consistent.
Data entity instances hold the data from/to table rows. Data-access logic components decide what rows are affected through the where clause of a SQL command. The data entity structures can be defined during code generation. During runtime, the dynamic where clause can be passed to the data-access logic as an argument.
Generation of Classes for Table Joins
Data entities that map to some joins are also often used in the data-access tier. Depending on your business process, sometimes these entities are preferred over the data entities that map to single tables. To generate classes that represent this kind of entity, you need to define the SQL statement to specify the join. This statement includes a selection part that gives each selected column a unique name and a condition part that specifies the joint criteria. The selection part is crucial because it determines the structure of the generated code. The condition part is not as important. As long as it can compose a syntactically correct SQL statement, it’s okay. At runtime you may provide the real joint relationship and parameters.
The DBAG tool accepts the joint definition statements through the Sql2Class.xml file. On the GUI, DBAG lists the join-to- class maps together with the single-table- to-class maps. The only difference on the join-to-class map is that these maps have no table name values. You may need to consider this in your template.
Generation of Classes for Stored Procedures
A stored procedure may take none, one, or more parameters. It may return some
Figure 1: DBAG schema mapping.
selections, a single value, or nothing. This information can also be collected and made available for generating data entity wrappers. DBAG does not handle the stored procedure wrapper yet because it requires a totally different display interface and quite a different keyword set. However, based on the provided code, it should be easy to add such support.
Sample Application
The example application we provide with this article (available electronically; see “Resource Center,” page 4) implements a data-access layer for the famous Oracle sample database scott/tiger. To demonstrate some of the recommended concepts, we modified it to add an artificial primary key column OID to each table. We also rebuilt the relationships between the tables using the newly added columns. When generating the code, we used Microsoft’s OLE DB driver for Oracle and a template that combines database entity and database access logic. Besides demonstrating the normal database operations, it also demonstrates how to handle joints and how to bind SQL statements to the database schema.
Conclusion
Database access tier is an important component in enterprise applications. It helps make the business process code simple and independent. Although repetitive, it requires a considerable amount of time and attention from developers who otherwise can spend more time on their business logic. Computers are excellent at handling repetitive tasks. They are more powerful in repetitive tasks than humans, and it is wise to use them for generative programming tasks.
DDJ
Listing One
(a) For a single table
DEPT |
|
( |
|
OID |
NOT NULL NUMBER(38), |
DEPTNO |
NOT NULL NUMBER(2), |
DNAME |
VARCHAR2(14), |
LOC |
VARCHAR2(13) |
) |
|
(b) Corresponding class generated in C#
public class Dept : DataAccessBase, IDept, IDataAccessBase
{
...
long _Oid;
long _Deptno; string _Dname; string _Loc;
bool _OidNull = true; bool _DeptnoNull = true; bool _DnameNull = true; bool _LocNull = true;
public long Oid
{
get {return _Oid;}
set {_OidNull = false; _Oid = value;}
}
...
}
(continued on page 28)
http://www.ddj.com |
Dr. Dobb’s Journal, December 2005 |
27 |

(continued from page 27)
(c) For table joins
SELECT
DEPT.DNAME AS DEPARTMENT, EMP.EMPNO AS EMPNO, EMP.ENAME AS EMPNAME, EMP.SAL AS SALARY
FROM DEPT, EMP
WHERE DEPT.OID=EMP.DEPTOID ORDER BY DEPARTMENT
(d) Corresponding class generated in C#
public class DeptEmp : DataAccessBase, IDeptEmp
{
...
string _Department; long _Empno; string _Empname; long _Salary;
public string Department
{
get {return _Department;}
}
...
}
Listing Two
(a) Raw SQL statement
select emp.* from emp, dept where emp.deptoid=dept.oid
and dept.dname='ACCOUNTING';
(b) SQL built with generated information
...
emp.Retrieve(Emp.DEPTOID == Dept.OID & Dept.DNAME == "ACCOUNTING");
...
Listing Three
(a) Code Template
columnNameStr = //!!FOREACH MEMBER!!//
"$COLUMN_NAME$," + //!!END FOREACH MEMBER!!//
//!!REPLACE Options=64; Count=1; Regex=/," \+/"/ !!//
;
(b) Generated C# code
columnNameStr = "OID," + "EMPNO," + "ENAME," + "JOB," + "MGR," + "HIREDATE," + "SAL," + "COMM," + "DEPTOID"
;
(c) Code template
//!!FOREACH MEMBER!!//
bool Is$MEMBER_NAME$Null {get;set;} //!!END FOREACH MEMBER!!//
(d) Generated C# Code
bool IsOidNull {get;set;} bool IsEmpnoNull {get;set;} bool IsEnameNull {get;set;} bool IsJobNull {get;set;} bool IsMgrNull {get;set;} bool IsHiredateNull {get;set;} bool IsSalNull {get;set;} bool IsCommNull {get;set;} bool IsDeptoidNull {get;set;}
(e) Code template
//!!FOREACH MEMBER!!// //!!IF MEMBER_TYPE=long!!//
_$MEMBER_NAME$Null = fSql.IsNextColumnNull(); _$MEMBER_NAME$ = fSql.GetNextColumnInteger();
//!!END IF!!//
//!!IF MEMBER_TYPE=double!!// _$MEMBER_NAME$Null = fSql.IsNextColumnNull(); _$MEMBER_NAME$ = fSql.GetNextColumnDouble();
//!!END IF!!//
//!!IF MEMBER_TYPE=string!!// _$MEMBER_NAME$Null = fSql.IsNextColumnNull(); _$MEMBER_NAME$ = fSql.GetNextColumnString();
//!!END IF!!//
//!!IF MEMBER_TYPE=DateTime!!// _$MEMBER_NAME$Null = fSql.IsNextColumnNull(); _$MEMBER_NAME$ = fSql.GetNextColumnDateTime();
//!!END IF!!//
//!!IF MEMBER_TYPE=byte[]!!// _$MEMBER_NAME$Null = fSql.IsNextColumnNull();
_$MEMBER_NAME$ = fSql.GetNextColumnBytes(1024*200); //!!END IF!!//
//!!END FOREACH MEMBER!!//
(f) Generated C# code
_OidNull = fSql.IsNextColumnNull(); _Oid = fSql.GetNextColumnInteger();
_EmpnoNull = fSql.IsNextColumnNull(); _Empno = fSql.GetNextColumnInteger();
_EnameNull = fSql.IsNextColumnNull(); _Ename = fSql.GetNextColumnString();
_JobNull = fSql.IsNextColumnNull(); _Job = fSql.GetNextColumnString();
_MgrNull = fSql.IsNextColumnNull(); _Mgr = fSql.GetNextColumnInteger();
_HiredateNull = fSql.IsNextColumnNull(); _Hiredate = fSql.GetNextColumnDateTime();
_SalNull = fSql.IsNextColumnNull(); _Sal = fSql.GetNextColumnInteger();
_CommNull = fSql.IsNextColumnNull(); _Comm = fSql.GetNextColumnInteger();
_DeptoidNull = fSql.IsNextColumnNull(); _Deptoid = fSql.GetNextColumnInteger();
Listing Four
//!!FOREACH MAPPED TABLE!!// $CLASS_NAME$ $TABLE_NAME$
$GUID1$
$GUID2$ $MEMBER_COUNT$ $FOREIGN_KEY_COUNT$
//!!END FOREACH MAPPED TABLE!!// //!!FOREACH MAPPED JOINT!!//
$CLASS_NAME$ $GUID1$ $GUID2$ $MEMBER_COUNT$ $SELECT_STRING$ $UPDATE_STRING$ $DELETE_STRING$ $INSERT_STRING$
//!!END FOREACH MAPPED JOINT!!//
//!!FOREACH MEMBER!!// //!!FOREACH PRIMARY_KEY!!// //!!FOREACH FOREIGN_KEY!!//
//!!IF MEMBER_NAME=?!!// //!! ... !!// //!!IF COLUMN_NAME=?!!// //!! ... !!// //!!IF MEMBER_TYPE=?!!// //!! ... !!// //!!IF COLUMN_TYPE=?!!// //!! ... !!// //!!IF KEY_TYPE=?!!// //!! ... !!// //!!IF MEMBER_ACCESS=?!!// //!! ... !!//
//!!END IF!!//
$MEMBER_NAME$
$COLUMN_NAME$ $KEY_TYPE$ $MEMBER_TYPE$ $COLUMN_TYPE$ $MEMBER_ACCESS$ $COLUMN_ORDINAL$ $COLUMN_MAXLENGTH$ $COLUMN_TYPE$
//!!END FOREACH MEMBER!!// //!!END FOREACH PRIMARY_KEY!!// //!!END FOREACH FOREIGN_KEY!!//
//!!FOREACH PARENT!!//
$CLASS_NAME$
$TABLE_NAME$
$PARENT_TABLE$
$PARENT_CLASS$ $COLUMN_TO_PARENT$ $COLUMN_OF_PARENT$ $MEMBER_TO_PARENT$ $MEMBER_OF_PARENT$
//!!END FOREACH PARENT!!//
//!!FOREACH CHILD!!//
$CLASS_NAME$
$TABLE_NAME$
$CHILD_TABLE$
$CHILD_CLASS$ $COLUMN_TO_CHILD$ $COLUMN_OF_CHILD$ $MEMBER_TO_CHILD$ $MEMBER_OF_CHILD$
//!!END FOREACH CHILD!!//
//!!REPLACE Options=?; Count=?; Regex=/???/???/ !!//
DDJ
28 |
Dr. Dobb’s Journal, December 2005 |
http://www.ddj.com |