Database Model Changes & NConstruct Builder

Category: Using NConstruct Builder
Target NConstruct Versions: 2.x
Document Release Date: November, 2009

Table of Contents

1. Problem Description
    Database model changes implications

2. Solution
    Separating generated and customized code
        Data Transfer Object
        Entity
    Updating object model

3. Practical Examples
    Case 1: A new field in existing table
        Updated classes and files
            ShippersDTO.Generated.cs class
            ShippersEntity.Generated.cs class
            ShippersEntity.hbm.xml file
            ShippersEntity.cs class
    Case 2: A new table with a relation to an existing table
        New classes and files
        Updated classes and files
            ShippersDTO.Generated.cs class
            ShippersEntity.Generated.cs class
            ShippersEntity.hbm.xml file
            ShippersEntity.cs class

4. Conclusion

 

1. Problem Description

Database model changes implications

There is probably no project without changes in its database model after first deployment. Consequently, object model has to be change, too.

The best practice for database model changes is to create one or more SQL alter script which can be applied on your development computer(s) and afterward on your customer's server as well.

Updating object model in your .NET application is another story. Usually, developer has to carefully translate each change from underlying database model into application's object model. In multitier application architecture, developer has usually to update other layers of the application:

If code generation can create all or most code for object-relational mapping classes and files, the same technique can be used for updating object model if database model has been changed.

This, in most projects iterative process, can be represented by the following simple diagram:

Updating model process

One of the major problems which have to be solved through the object model updating is to preserve the source code which has been changed or customized after the first code generation is used. Sometimes it's impossible to solve this problem completely, but if a solution can handle most of this work, it can save a lot of developer's time and reduces chances for creating many annoying bugs which emerges from mapping classes and/or NHibernate HBM Xml files.

Let's have a closer look about using NConstruct Builder in such cases.

 

2. Solution

Separating generated and customized code

Most of the source code NConstruct Builder generates is located in [your application name].Server project. Typical generated folder structure for the Server project looks like this in the picture:

Northwind Server in Solution Explorer 

If we focus on one table in the database model, for example - Shippers table, we can see there are four generated files in the Server project:

Information for generating those files is held in a Shippers table in the database model:

Original Database Model

To support object model changes and to separate generated source code from your own code, NConstruct Builder takes advantage of one very useful .NET feature: partial classes. Where it's necessary or it might become necessary, NConstruct Builder generates two partial classes or at least insert a "Generated" keyword in the class' name.

Data Transfer Object

First file, ShippersDTO.Generated.cs, is just a simple Data Transfer Object (or DTO, see also http://msdn.microsoft.com/en-us/library/ms978717.aspx" or http://en.wikipedia.org/wiki/Data_transfer_object).

As DTO pattern definition states, DTO class is used to transfer data between software application s).

As DTO pattern definition states, DTO class is used to transfer data between software application subsystems. DTO classes don't have any behavior except for storage and retrieval of its own data. NConstruct uses DTO classes for table-like data presentation (e.g. grid UI control). As DTO classes do not (and should not) have any business logic, there is no need to create two partial classes. NConstruct Builder doesn't create two files but it just adds a "Generated" keyword in the file's name and "partial" class definition.

Entity

For a data access layer based on NHibernate framework, two files per one mapping must be created. One of them is simple business object class, and another is NHibernate mapping file.

Without using the partial class feature, it would be enough to have two files in our Shippers table example: Shippers entity object class (e.g. ShippersEntity.cs) and Shippers NHibernate XML file (e.g. ShippersEntity.hbm.xml).

But as we saw, there is a possibility that someone would like to add some additional code in the object class. So, instead of one class, NConstruct Builder generates two classes:

ShippersEntity.cs consists of only namespace and partial class definition:

namespace Northwind.Server.Data.Northwind.Entity
{

    public partial class ShippersEntity
    {}
}

This is the right place for a developer to add any additional methods or other custom code.

ShippersEntity.Generated.cs should therefore contain only generated code - only metadata information extracted from the database. If database model is changed, developer (or database administrator) applies SQL alter script on the database, run again code generation tool and overwrite or replace only generated files with the old ones. With such approach, all custom code in the data access layer remains intact whenever database model is changed.

Updating object model

NConstruct Builder has an option to generate only mapping and some other classes which might need updates if a database model is changed.

This feature can be combined with partial tables' selection in the Namespace definition step.

Namespaces in the application

In this step, you may select only those tables, where your database has been changed. NConstruct Builder will however include related table(s) if you've changed their relations: table relations and their object's reflections are just as important part of object model as objects definitions are.

If you decide to generate only entity, DTO, action and some other classes after your database model is changed, use "Create entity, DTO, NHibernate *.Hbm.xml, Enumerations, Action, Resource *.resx, and Web Pages.xml files only" checkbox in the last step of NConstruct Builder wizard.

Final output parameters

You may use the same project location value as your project is located, or, if you are not sure about changes you've made in your source code, you may use a different location and overwrite or merge files manually.


3. Practical Examples

Case 1: A new field in existing table

Difficulty level:easy difficult

Prerequisites:

Let's add one new field in our database model. Assume we'd like to store fax number in a new field in Shippers table in the Northwind database.

SQL alter script for upgrading database would look like this one:

use Northwind

go

alter table Shippers add Fax varchar(50) null

go

To test NConstruct Builder doesn't destroy our programming work; add one dummy method in the ShippersEntity.cs class:

namespace Northwind.Server.Data.Northwind.Entity
{

    public
partial class ShippersEntity
    {

        public
void SomeMethod()
        {
           
//No implementation needed.
        }
    }
}

Run NConstruct Builder and select only Shippers table in the Namespace definition step.

In the last step, tick the "Create entity, DTO, NHibernate *.Hbm.xml, Enumerations, Action, Resource *.resx, and Web Pages.xml files only" checkbox and leave the original project location value.

Final output parameters

Click on the Finish button and wait for NConstruct Builder to generate new files.

Open generated Northwind solution again and check what is changed.

Updated classes and files

ShippersDTO.Generated.cs class

Generated DTO class has one new field (_Fax), one new property (Fax) and changed constructors according to the change in the database model:

...

private String _Fax;

public ShippersDTO(Int32 id, String companyname, String fax, String phone) :
this(id, companyname, fax, phone, 0)
{}

public ShippersDTO(Int32 id, String companyname, String fax, String phone,
int count)
{
    Id = id;
    _Companyname = companyname;
    _Fax = fax;
    _Phone = phone;
    Count = count;
}

[VisibleAttribute, SortAttribute(1), RootPropertyName(ShippersEntity.FaxProperty)]
public virtual String Fax
{
   
get {return _Fax;}
    set {_Fax = value;}
}

Note: other, unchanged parts of the ShippersDTO.Generated.cs class source code are not shown in the code snippet above.

ShippersEntity.Generated.cs class

Generated entity class has one new field (_Fax), one new property (Fax) according to the change in the database model:

...

private String _Fax;

....

[VisibleAttribute, SortAttribute(1), FieldLengthAttribute(50), FilterableFieldAttribute]
public virtual String Fax
{
   
get {return _Fax;}
   
set {_Fax = value;}
}

Note: other, unchanged parts of the tity.Generated.cs class source code are not shown in the code snippet above.

ShippersEntity.hbm.xml file

Generated NHibernate XML file has one new property definition according to the change in the database model:

<property name="Fax" column="Fax" access="field.pascalcase-underscore" not-null="false" type="System.String" insert="true" update="true"/>

Note: other, unchanged parts of the ShippersEntity.hbm.xml file are not shown in the code snippet above.

ShippersEntity.cs class

Next thing to verify is our customized class, ShippersEntity.cs. It should still contain manually written method named SomeMethod().

namespace Northwind.Server.Data.Northwind.Entity
{
    public partial class ShippersEntity
    {
       
public void SomeMethod()
        {
       
//No implementation needed.
       
}
    }
}

ShippersEntity.cs class is unchanged as it's meant to be.

Case 2: A new table with a relation to an existing table

Difficulty level: easy difficult

Prerequisites:

In this case, we'll add a new table and create a relation to one existing table in our database model. Assume we'd like to add information about country for shippers: therefore we have to create Countries table with some basic fields and 1-M relation to the Shippers table.

SQL alter script for upgrading database would look like this one:

use Northwind

go

create table Countries (
CountryID
int identity not null ,
Code varchar (10) null ,
[Name] varchar (200) null,
constraint PK_Countries primary key (CountryID))

go

alter table Shippers add CountryID int null

go

alter table Shippers add constraint FK_Shippers_Countries foreign key (CountryID) references Countries (CountryID)

go

To test NConstruct Builder doesn't destroy our programming work; add one dummy method in the ShippersEntity.csclass:

namespace Northwind.Server.Data.Northwind.Entity
{

    public
partial class ShippersEntity
    {
       
public void SomeMethod()
        {
       
//No implementation needed.
       
}
    }
}

Run NConstruct Builder and select only Countries and Shippers tables in the Namespace definition step.

In the last step, tick the "Create entity, DTO, NHibernate *.Hbm.xml, Enumerations, Action, Resource *.resx, and Web Pages.xml files only" checkbox and leave the original project location value.

Final output parameters

Click on the Finish button and wait for NConstruct Builder to generate new files.

Open generated solution again and check what's new and what's changed.

New classes and files

A new set of classes has been generated because we've added new table in the Northwind database:

File name Location Purpose
CountriesEntity.Generated.cs Northwind.Server\Data\Northwind\Entity Generated entity class
CountriesEntity.cs Northwind.Server\Data\Northwind\Entity Empty partial class
CountriesEntity.hbm.xml Northwind.Server\Data\Northwind\Entity NHibernate mapping
CountriesDTO.Generated.cs/td> Northwind.Server\Data\Northwind\DTO DTO class for CountiresEntity
CountriesAction.cs Northwind.Client\Presentation\Actions\Northwind Menu action class

However, if we want to find them in our solution, there's none of them. The reason NConstruct Builder doesn't do this automatically is the possibility of manual changes in the project file (Northwind.Server.csproj in this case) and problems with merging new and existing project file.

So, we have to add newly generated files to our projects manually. It's good to start with the core classes - those in the Northwind.Server project:

Updated classes and files

ShippersDTO.Generated.cs class

Generated DTO class has one new field (_CountryName), one new property (CountryName) and changed constructors according to the change in the database model:

...

private String _CountryName;

public ShippersDTO(Int32 id, String companyname, String countryName, String phone) :
this(id, companyname, countryName, phone, 0)
{}

public ShippersDTO(Int32 id, String companyname, String countryName, String phone,
int count)
{
    Id = id;
    _Companyname = companyname;
    _CountryName = countryName;
    _Phone = phone;
    Count = count;
}

[VisibleAttribute, SortAttribute(1), ManyToOnePropertyAttribute(CountriesEntity.NameProperty), RootPropertyName(ShippersEntity.CountryProperty)]
public virtual String CountryName
{

    get
{return _CountryName;}
    set
{_CountryName = value;}
}


Note: other, unchanged parts of the ShippersDTO.Generated.cs class source code are not shown in the code snippet above.

ShippersEntity.Generated.cs class

Generated entity class has one new field (_Country), one new property (Country) according to the change in the database model:

...

private CountriesEntity _Country;

...

[VisibleAttribute, FilterableFieldAttribute, ManyToOnePropertyAttribute("Id", false)]

public virtual CountriesEntity Country
{
   
get {return _Country;}
   
set {_Country = value;}
}

Note: other, unchanged parts of the ShippersEntity.Generated.cs class source code are not shown in the code snippet above.

ShippersEntity.hbm.xml file

Generated NHibernate XML file has one new many-to-one definition according to the change in the database model:

<many-to-one name="Country" access="field.pascalcase-underscore" not-null="false" outer-join="auto" insert="true" update="true">
    <
column name="CountryID"/>
</
many-to-one>

Note: other, unchanged parts of the ShippersEntity.hbm.xml file are not shown in the code snippet above.

ShippersEntity.cs class

Next thing to verify is our customized class, ShippersEntity.cs. It should still contain manually written method named SomeMethod().

namespace Northwind.Server.Data.Northwind.Entity
{

    public
partial class ShippersEntity
    {

        public
void SomeMethod()
        {

        //No implementation needed.

        }
    }
}

ShippersEntity.cs class is unchanged as it's meant to be.

 

4. Conclusion

Application's source code modifications can be accomplished with the aid of NConstruct Builder when a database model changes. This process is fully automated within NConstruct Builder in simpler cases.

Some manual work in Visual Studio IDE for more complex cases can't be avoided, though.

NConstruct future versions might include more automation support for database changes. They will be described in "What's new" documents and in separate technical articles.

 

 

Comments and questions...