1. gWizard Architecture
There are a number of approaches to object-relational mappings, the essential
differences centring on which end of the mapping "drives" the process, and
the end product (code generation versus dynamic configuration).
Each approach has it's drawbacks. If the database tables are used to drive the
code generation process, additional information must typically be supplied to
identify such things as derived class and lookup fields.
Also many object purist would argue that design should proceed from the object
model to the database implementation, not the other way around. However
driving code generation from the object end is also problematic as it
requires an object description such as UML XMI, Java bytecode or Microsoft MSIL.
Java bytecode and MSIL tie the code generator to a particular technology.
UML XMI is complex and expensive to implement, and it does not naturally
describe some constructs such as lookup fields well.
Ideally an object-relational mapper should be able to drive the process
from either end, importing both relational and object meta-data in various
formats, resolving any conflicts and storing the combined data in a repository. (See below)
Ideally it should also be free and open source.
Connacht implements table driven code generation for pragmatic reasons:
(1) the author and many others have easy access to database design tools, or have
existing databases that could benefit from automatically generated business objects,
and (2) the authors resources are limited and he feels that it is too easy to get
bogged down with many of the technicalities of the other approaches (e.g. UML XMI).
Version 0.9b: gWzard does not allow users to create the own object
definitions. All object definition must be imported from (synchronised
with) a database. The aspects of the object description that can be edited
are discussed below.
Version 0.9b: gWizard does not attempt to model inheritance hierarchies
in any way.
2. Usage
2.1 Operations Supported at the Command Line - How To
Invoke the GUI
Type the following at the command line :
gWizard /P
Generate Code
To generate code for a specific object, type the following at the
command line :
gWizard /O object_name
2.2 Operations Supported by the GUI - How To
Synchronise with Database
Generate Code
3. Internal Data Structures
The diagram below shows the data structures used to describe business
objects and code generators.
3.1 Persistence
The structures above are serialised to XML by the Project.Save()
operation and recreated by Project.Load(), with the exception of the
CodeGenerators which are disconnected from the project before serialisation
and reconnected afterwards. Serialisation is performed using .NET XML
serialization services on the project object.
3.2 Database Synchronisation
Object descriptions can be synchronised with (imported from) the
database at the project and object level using the SynchroniseWithDB()
methods.
3.3 Editing Object Descriptions
3.3.1 Attributes
Version 0.9b: The object attributes cannot be
edited and must be generated by synchronising the object with the
database.
3.3.2 Primary Key
Version 0.9b: The primary key associated with an object cannot be
edited and must be generated by synchronising the object with the
database.
3.3.3 Foreign Keys
Version 0.9b: Foreign keys associated with an object cannot be edited
and must be generated by Synchronising the object with the database.
3.3.4 Excludes
It is sometimes necessary to exclude special fields from automatically
generated code. Examples include user passwords and timestamps that record
specific operations. An attributes is excluded from code generation
on an object or per generator basis by dragging the attribute and dropping
it on the appropiate Exclude node.
3.3.5 Lookups
Business objects are often not perfect reflections of their database
images. For example, a Contract business object may contain both the
unique ID (foreign key) of a Company and the Company name (text).
The Company name would not appear in the Contracts table in the database
(it would be moved to the Companies table as part of the usual
normalisation process).
Fields such as Company name are referred to in GWizard as lookup
fields. Lookup fields can be implemented at the business object or
database level. The object description used by GWizard is capable of
supporting both.
Version 0.9b: The GWizard code generators assume that lookup fields are
implemented in the database (stored procedures). There are some potential
scalability problems with this approach but it is unlikely that the
majority of users (the author :-)) will ever encounter them.
3.4 Editing Other Information
3.4.1 Context Fields
These are fields that may be required to identify whether or not
operation should go ahead, and/or to determine which database records are
visible. For example, an accounting application may filter on CompanyID.
Accounting records that are not
Version 0.9b: The built-in code generators pass the context fields to
all object methods and stored procedures.
3.5 Editing Summary
The following table may be of assistance if gWizard does not behave as
you expect.
3.6 Versioning
The versioning strategy is delegated completely to the code generators.
A code generator may or may not require specific fields to be identified
in which case it should create appropriate entries in the RHS list view,
or assume an existing database naming convention.
3.7 Code Generation
3.7.1 Code Generator Interfaces
Code Generators are specificed using the data structure below. The Name
and Module fields correspond to the .NET class and assebly names. The
CodeGeneratorRef.Load() method loads the specified code generator and
points the CodeGenerator field to it. The CodeGeneratorRef.Unload() free
releases all reference to the code generator and nulls the CodeGenerator
field. The CodeGeneratorRef.GetDescription() method queries the
CodeGenerator for a description if loaded, otherwise it generates a
composite name from the Name and Module fields.
public class CodeGeneratorRef
{
public string Name;
public string Module = "GWizard";
public string GetDescription() {...}
public void Load() {...}
void Unload() {...}
public CodeGenerator CodeGenerator = null;
}
Code Generators must derive from the abstract CodeGenerator class shown
below. An abstract class is used rather than an interface as .NET
serilisation services throw exceptions if a pointer to an interface is
encountered.
public abstract class CodeGenerator
{
public abstract CodeGenerationInfo GetSettingsStruct(CodeGeneratorRef p, ObjectInfo objectInfo);
public abstract void GenerateCode(ObjectInfo objectInfo, CodeGenerationInfo settings, ArrayList context, Logger logger);
public abstract string Description {get;}
}
Code Generator configuration information must be derived from the
CodeGenerationInfo class shown
below.
public class Property
{
public enum PropertyType {eString, eInteger, eBoolean, eDateTime, eSqlDBType}
public Property() {}
public Property(string name, PropertyType type, string szvalue) {...}
public string name;
public PropertyType type;
public string szvalue;
public bool readOnly = false;
}
public abstract class CodeGenerationInfo
{
public CodeGenerationInfo() {}
public CodeGenerationInfo(CodeGeneratorRef codeGenerator, ObjectInfo objectInfo)
{
this.p = codeGenerator;
this.ObjectInfo = objectInfo;
}
public CodeGeneratorRef p = null;
public ObjectInfo ObjectInfo = null;
public ArrayList Exclude = new ArrayList();
public abstract void CreateListView(ProjectView projectView);
public abstract void SetValue(Property property);
public Property GetProperty(string propertyName)
{
if(properties == null)
return null;
for(int i=0; i<properties.Length; i++)
{
if(propertyName.Equals(properties[i].name))
return properties[i];
}
Debug.Assert(false); // Not found.
return null;
}
public Property[] properties = null;
}
3.7.2 Database Interface
Database Adapters must implement the GWizard.IMeta interface shown below.
interface IMeta
{
void GetTableMetaData(string connectionString, ObjectInfo objectInfo);
ArrayList GetTables(string connectionString);
}
IMeta.GetTables
Synopsis: Must return a list of all user table names.
Parameters:
connectionString - should contain enough information for the adapter to
connect to the database and query for users table names.
IMeta.GetTableMetaData
Synopsis: Queries the database for meta information such as column descriptions,
primary keys, foreign keys and identity columns, and returns the information as a
ObjectInfo structure.
Parameters:
connectionString - should contain enough information for the adapter to
connect to the database and query for meta-data.
objectInfo - an existing ObjectInfo object. The objectInfo.table data member specifies
the database table for which meta-information is required.
|