You are on page 1of 11

usGDO V1.1.3.

0 Tutorial
2006 by Ulrich Schumacher, last updated: 27.08.2006

1/11

Introduction What is usGDO? usGDO stands for (ulrich schumachers) Generated Data Objects. It is a collection of scripts for the great MyGeneration freeware tool. The scripts read the structure of database tables and generate partial C# classes. These classes can persist themselves into the database and can be used as part of the business logic classes of an application. Currently there are three databases supported:
Database Microsoft Access Microsoft SQL Server Oracle Tested with Version Microsoft Access 2003 2000, 2005 10g XE

Because of using the new features of .Net 2.0, usGDO generates source code in C# for .Net 2.0 (.Net 1.1 is not supported). Motivation When writing a database-driven application, you need a way to persist the business objects into the database. There are two major ways to achieve this with .Net 2.0: use Typed DataSets use an O/R-Mapping tool

Although these ways offer a lot of benefits, there are also some drawbacks: The generated code of the Typed DataSet uses a value-based optimistic locking as concurrency strategy. This can produce a significant performance lost in applications that perform lots of inserts, updates und deletes. As an example, this is the SQL statement to perform a delete in the Employees table of the Northwind database:
DELETE FROM `Employees` WHERE ((`EmployeeID` = ?) AND ((? = 1 AND `LastName` IS NULL) OR (`LastName` = ?)) AND ((? = 1 AND `FirstName` IS NULL) OR (`FirstName` = ?)) AND ((? = 1 AND `Title` IS NULL) OR (`Title` = ?)) AND ((? = 1 AND `TitleOfCourtesy` IS NULL) OR (`TitleOfCourtesy` = ?)) AND ((? = 1 AND `BirthDate` IS NULL) OR (`BirthDate` = ?)) AND ((? = 1 AND `HireDate` IS NULL) OR (`HireDate` = ?)) AND ((? = 1 AND `Address` IS NULL) OR (`Address` = ?)) AND ((? = 1 AND `City` IS NULL) OR (`City` = ?)) AND ((? = 1 AND `Region` IS NULL) OR (`Region` = ?)) AND ((? = 1 AND `PostalCode` IS NULL) OR (`PostalCode` = ?)) AND ((? = 1 AND `Country` IS NULL) OR (`Country` = ?)) AND ((? = 1 AND `HomePhone` IS NULL) OR (`HomePhone` = ?)) AND ((? = 1 AND `Extension` IS NULL) OR (`Extension` = ?)) AND ((? = 1 AND `ReportsTo` IS NULL) OR (`ReportsTo` = ?)))

In comparison, usGDO uses the following statement:


delete from [Employees] where ([EmployeeID]=@employeeid) and ([VERSION]=@lastversion)

As you can see in the WHERE-clause, usGDO uses a version-based optimistic locking as concurrency strategy, which is much easier to process for the database engine. In addition there are some other points (e.g. poor usage of Nullable Types in the DataRow class, nested structure of the classes) that reduced my interest to work with Typed DataSets in further projects.

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

2/11

If you use a object relational mapping tool (O/R-Mapper) for persistence purposes, you must live with the inner logic and structure of that tool which is not always easy to understand. Some important C# 2.0 features you need in your project (nullable types for example) perhaps will not be supported. What I wanted was an easy-to-use method with full flexibility to change. Then I come to code generation as another way to solve my problem. From that point there was a short way to MyGeneration, a freeware code generation tool that can read a database schema and generate code from it. So I wrote my own code generation scripts to manage the persistence logic of business objects and usGDO was born. It is somewhere between ORMapping and the tableoriented DataSet way. It is easy to work with but, on the other side, it supports everything you need to build data layers for complex enterprise applications. Features usGDO has the following features: easy to learn no further software prerequisitions no stored procedures needed generates partial C# classes to separate the generated code from the manual code constructors with all required (not-null) fields support of Nullable Types easy insert und update with a Persist method automatic retrieval of new auto primary key values, non auto primary keys also supported Get, TryGet methods for retrieval by primary key supports queries in the database vendors SQL dialect returns a query result as typed list version-based optimistic locking as concurrency strategy support of Transactions generates Data Transfer Objects for serialization purposes (Xml, Soap, Binary) ready for multi-threading (each thread has its own connection/transaction)

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

3/11

DB prerequisites usGDO needs some prerequisites in the database: to use optimistic locking, each table must have a field VERSION (int, not null, default: 0) if you use Oracle with auto primary keys, there must be a sequence TABLENAME_SEQ for each table TABLENAME to get the next auto primary key value

MyGeneration settings You have to set the Database Target Mapping in the Connection tab of the Default Settings:
Database Microsoft Access Microsoft SQL Server Oracle Database Target Mapping, DbTarget OleDb SqlClient OracleClient

In the tap Templates you should define the Default Output Path, which is read by the scripts.

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

4/11

Generate the classes After you have run a script the following screen appears:

You can only select one table per run. So you have to run the script again for every table. As an alternative you can create a new MyGeneration project and insert the same script for each table in the database. So you are able to generate all classes with one mouse click! usGDO preselects all fields of a table. This should be the normal way. If you want to create a class with only some of the table fields, you can deselect the other fields here.

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

5/11

Class name usGDO suggests a name of the generated class where the first and all characters of the table name following a _ are uppercase (camel notation), for example: Table CUSTOMER ORDER_ITEM Class Customer OrderItem

You can define a different name of the class here (Its a good convention to make class names singular, so you can enter Employee instead of Employees). Add optimistic locking If you want to use optimistic locking as the concurrency strategy, each table must have a VERSION field (int, not null, default: 0). If you deselect this, there will be no concurrency strategy used (last save wins). Keep database-specific members internal Sets the access modifiers of all database-specific methods to internal. As you should realize all the SQL-related code separate from the GUI (in its own class library) this should be the checked. Otherwise all methods are generated with public access modifiers. Generate Data Transfer Objects Generates an additional class with a suffix DTO as a Data Transfer Object (See Data Transfer Objects for details). Generate PersistenceManager class This must be selected only once to generate the helper class PersistenceManager which is used by the generated data objects.

Hint: You should place the generated sources in a special folder Generated in your project to separate them from the hand coded classes. After you run the script for each table, you should have the following files in your project folder: PersistenceManager.cs Tablename_gen.cs for each Table TABLENAME and optional TablenameDTO_gen.cs

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

6/11

Usage Remark: In the code samples I use the Microsoft demo database Northwind. Its structure should be well known by most readers. Because the generated classes are partial classes, you should create new files in your project to contain the business logic, for example: usGDO creates a partial class Employee in the file Employee_gen.cs, so you should add a new class Employee to your project (ensure that is has the same namespace as the generated class): public partial class Employee { // enter your business logic here } So you have one class Employee which is devided into two source files. This is an easy way to separate your code from the generated code. If you re-generate this class later, your code is not lost. Before you connect to the database, you have to set the connection string. This must be done only once at the start of your application: PersistenceManager.ConnectionString = your connection string; Create a new object To create a new object, you can use the constructor, which has all fields as parameters that cannot be null: Employee e = new Employee("Duck", "Donald"); Then you can set further members: e.Birthdate = new DateTime(1934, 6, 9); e.Title = "Mister"; Then you can call the method Persist of the Employee class. e.Persist(); Now a new object is created in the database. Notice that the member Employeeid has the new generated auto primary key value. Update an object To update an existing object, you can also use the Persist method: e.Birthdate = null; e.Persist(); Notice that you can assign null here because Birthdate is of type Nullable<DateTime> as it can be NULL in the database.

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

7/11

Get an object by primary key To get an Employee object that has the ID 9, you can use: Employee employee = Employee.Get(9); If the Employee does not exist, a DataException is thrown. The second way is to use Employee employee; bool found = Employee.TryGet(9, out employee); In this case found is False if the Employee does not exist. Excecute a query First you have to create an IDbCommand object with a SQL-Query: OleDbCommand cmd = new OleDbCommand(String.Format( "select * from {0} where {1}=@lastname", Employee.TableName, Employee.ColumnNames.Lastname)); cmd.Parameters.AddWithValu e("@lastname", "Davolio"); In this case I create an OleDbCommand to use it with Microsoft Access. You should create a Query factory class, which creates all the queries your application needs. This seperates the database specific code from the other code. Then you can pass this IdbCommand object to the static Query method of your generated data object class: IList<Employee> list = Employee.Query(cmd); The result is a typed Ilist which contains result set of the query. Hint: You should usually query all fields of a table (select *). If you dont, you should never use the Persist method of that objects, because the other fields would be overwritten with null in the database. Delete an object To delete an object, you can use ist Delete method: e.Delete(); Persist and delete lists of objects You can also persist and delete lists of data objects. To achieve this you can use the static methods of the PersistenceManager class: PersistenceManager.Persist(list); PersistenceManager.Delete(list); Where list must be of type IList<IPersistable> and can contain different kinds of data objects (all data objects implement IPersistable). You can of course do this in a transaction (see next chapter).

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

8/11

Transactions The PersistenceManager class has three static methods to manage transactions. You can start a new transaction with PersistenceManager.StartTransaction(IsolationLevel.ReadCommitted); As a parameter define the IsolationLevel of the transaction. This enumeration is part of the System.Data namespace. You can commit a running transaction with PersistenceManager.CommitTransaction(); You can abort a transaction with PersistenceManager.RollbackTransaction(); So if you want to create two new Employees in a transaction, you can use

PersistenceManager.StartTransaction(IsolationLevel.ReadCommitted); Employee e1 = new Employee("Duck", "Donald"); e1.Persist(); Employee e2 = new Employee("Duck", "Daisy"); e2.Persist(); PersistenceManager.CommitTransaction();

Fill a DataTable Sometimes its helpful to fill a DataTable directly, e.g. to read data from a View and bind it to a GridView control. OleDbCommand cmd; cmd = new OleDbCommand(String.Format("select * from [{0}] order by [{1}]", Employee.TableName, Employee.ColumnNames.Employeeid)); DataTable table = PersistenceManager.FillDataTable(cmd); ExcecuteNonQuery You can excecute a command directly: OleDbCommand cmd; cmd = new OleDbCommand( String.Format("delete from [{0}] where [{1}] LIKE {2}", Employee.TableName, Employee.ColumnNames.LastName, "D%" )); int rows = PersistenceManager.ExecuteNonQuery(cmd);

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

9/11

ExcecuteScalar You can retrieve a scalar from the database: cmd = new OleDbCommand(String.Format("select [{0}] from [{1}] where [{2}] = 1", Employee.ColumnNames.LastName, Employee.TableName, Employee.ColumnNames.EmployeeId )); String name = (string)PersistenceManager.ExecuteScalar(cmd); Data Transfer Objects If you have to go across application boundaries you will need a way to serialize the data of a data object. That is the moment where you can use Data Transfer Objects. Data Transfer Objects contain only the data of a data object (including the primary key fields) and are serializable. If you select the checkbox Generate Data Transfer Object in the UI, an additional class with suffix DTO is created. The DTO classes are generated in the namespace <Your namespace>.DataTransferObjects so you can put them in their own assembly. You can create a Data Transfer Object with the method GetDTO and apply its data to the data object with the method SetDTO. EmployeeDTO dto = employee.GetDTO(); employee.SetDTO(dto); Xml Serialization First create a Data Transfer Object and then use the XmlSerializer class: Employee e = Employee.Get(1); EmployeeDTO dto = e.GetDTO(); XmlSerializer s = new XmlSerializer(typeof( EmployeeDTO)); FileStream f = File.Create(@"c: \temp\test.xml"); s.Serialize(f, dto); f.Close(); Xml Deserialization Xml deserialization is almost as easy as serialization, with one addition: You should call the method RefreshState of each object to ensure that the internal flags of the data object are refreshed. After that, you can for example call the Persist method to store it. FileStream f = File.OpenRead(@"c: \temp\test.xml"); XmlSerializer s = new XmlSerializer(typeof( EmployeeDTO)); EmployeeDTO dto = (EmployeeDTO)s.Deserialize(f); f.Close(); Employee e = new Employee(); e.SetDTO(dto); e.RefreshState();

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

10/11

Soap-/Binary Serialization The serialization of Data Transfer Objects works with SoapFormatter and BinaryFormatter also. So if you work with Web Services or .Net Remoting, use the Data Transfer Objects as parameters of your methods. As the SoapFormatter does not support serialization of generic types (so in particular Nullables) the nullable types are stored internally as value types with a bool member to indicate a value. CreationComplete, PreDelete, PrePersist There are some special methods that are invoked automatically if they are implemented: CreationComplete (last action of the constructors) PreDelete (first action of the Delete method) PrePersist (first action of the Persist method)

You can define those methods in your manually coded partial classes (case insensitive) with private or public access modifiers. public partial class Employee { private void creationComplete() { // do some default value settings } private void preDelete() { // do some pre-deletion stuff } private void prePersist() { // do some pre-persistence stuff } } Custom Attributes The attribute GDOStringInfo is generated for properties of type String and provides information about the maximum length of the corresponding table column and if the column requires a value. That can be useful for validation of property values before persisting the object. [GDOStringInfo(40, true)] public virtual string Companyname { get { return this.companyname; } set { this.companyname = value; } }

usGDO V1.1.3.0 Tutorial


2006 by Ulrich Schumacher, last updated: 27.08.2006

11/11

Contact You can contact me in the MyGeneration forum (Peer to Peer Collaboration, usGDO Generated Data Objects). Ideas, experiences, critics and of course bug reports are strictly welcome!