Click here to Skip to main content
5,786,882 members and growing! (21,069 online)
Email Password   helpLost your password?
Development Lifecycle » Code Generation » General     Intermediate License: The Code Project Open License (CPOL)

netTierGenerator

By Dmitry Zubrilin

3-tier application framework & codogeneration tool - the way for rapid & effective development.
C# (C# 1.0, C# 2.0, C# 3.0, C#), .NET (.NET, .NET 2.0, .NET 3.5, .NET 3.0), CEO, Architect, Dev

Posted: 30 Nov 2008
Updated: 30 Nov 2008
Views: 4,839
Bookmarked: 40 times
Note: This is an unedited reader contribution
Announcements
Loading...



Search    
Advanced Search
Sitemap
14 votes for this Article.
Popularity: 5.36 Rating: 4.68 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
1 vote, 7.1%
3
2 votes, 14.3%
4
11 votes, 78.6%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

netTierGenerator download
netTierGenerator sample download

Introduction

There are a lot of different ORMs and code generator utilities (e.g. NHibernate, netTiers, Entity Framework etc). Some of them are based on template driven engines as well some of them are based on own solution frameworks. Utility that I would like to present in this article is based on own solution framework. So it is not only an object relational mapping tool. It rather focuses on presenting good development environment for any project.

Solution framework

In a few words my solution framework can be described as classic 3-Layered framework. It consists of Data Access Layer (DAL), Business Layer (BLL) and some rules for Presentation Layer (GUI). Mainly this framework is followed by common best practices published by Microsoft in the article “Application Architecture for .NET“. Graphically it can be displayed as a following picture.

But from my point of view the greatest advantage of this utility is a presence of the intermediate meta layer between code generation utility and backend data storage (I will call it MetaLayer). This layer is a set of XML documents describing the backed data storage structure and gives to developers an easy way to extend basic functionality (declaring new business entity classes, new data discovery methods).

Solution framework layers

The most important term in the solution is a “Service”. The solution operates these services like a vertical bricks which give ability to work with a concrete functional piece of the system through all layers (for example for a dictionary of countries there will be CountryInfo dto object, CountryServiceDAL, ICountryServiceDAL, CountryService at BLL). It is important that each service is implemented a stateless class. Layers are constructed in such a way that the DAL service methods should be called only from correspondent business logic services. Data access layers are implemented as set of Data Providers which are accessed via the Data Factory. This allows obtaining perfect abstraction from concrete DAL implementation. All layers are implemented as separated projects (assemblies). So typical solution will contain at least following projects: Common, Configuration management, MetaLayer, Business Entities Model, Data Access Layer Interfaces, Data Access Layer implementation, DAL Factory and Business logic. I would like to stay more detailed on each layer tasks and solutions.

Common

This is a place where all common code is located (application wide lookups, utility classes etc). There are some important features which are tightly incorporated into application solution & code generator utility:

  1. Application wide lookups can be used for mapping in business entities;
  2. Business entities validation engine and modification history tracking engine are located here.

Configuration management

This is an implementation of configuration management through “*.config” files. I have described its detailed implementation in Configuration Management article some time ago. Main benefit of this project is a functionality that allows obtaining configuration values using a very easy code:

string applicationName = CustomSettings.Current.ApplicationName;

Business Entity Model

Set of data transfer objects is implemented as a separate project. Besides of being simple containers for backend data DTO objects maintain following functionality: data validation at application level, IClonable, IEquatable<> . The validation functionality is borrowed from netTiers project. Its core is implemented as a set of standalone static methods. And each DTO class uses them in a following way.

private static void AddDatabaseChemaRules()
{
  GoodInfoModel.VALIDATION_RULES.AddRule(CommonRules.NotNull, "Name");
  GoodInfoModel.VALIDATION_RULES.AddRule(CommonRules.StringMaxLength, new CommonRules.MaxLengthRuleArgs("Name", 50));
  GoodInfoModel.VALIDATION_RULES.AddRule(CommonRules.NotNull, "Cost");
  GoodInfoModel.VALIDATION_RULES.AddRule(CommonRules.NotNull, "Quantity");
}

Data Access Layer

There are different ways to present data for application needs. One of them is used by Microsoft in ADO.NET that is based on mapping data on table level by means of DataTables and DataAdapters. Another way is used by different ORMs like NHibernate, netTiers etc. This way is based on mapping data on table row level. Suggested solution framework maps data like many another ORMs on table row level. Implementation of DAL can be decoupled as following items:

  1. Set of DAL Services which implement concrete functionality.
  2. Set of interface contracts which cover the set of DAL objects.
  3. DAL Factory which creates certain instances of DAL Service classes at run-time.

Actual implementation of concrete DAL functionality can be different. I have used an approach from Data Access Application Block v1. You can see implementation in the attached source code.

Rules for backend database

I use some strict rules that I follow during database developing.

  1. Naming convention for database objects: a). Tables – tblNamespace.Essence (f.ex. tblAdministration.User, tblAdministration.Role, tblDictionary.Country); b). Views – vwNamespace.Essence (f.ex. vwDictionary.Country). Where “tbl/vw” prefixes means table or view, “Administration/Dictionary” means something like namespace, “User/Role/Country” suffixes means object name.
  2. Use backend database as data storage only, do not perform any business activity there. In other words avoid using triggers, stored procedures for business goals. Use database only to maintain data, indexes and data integrity.
  3. Use Guid as primary keys (do not use integers with identity).

Business Layer

There are several different problems which must be solved at this tier.

  1. Most important things are implementation of session management and especially transaction management.
  2. Actual problem is ability to cache gathered data from backend database (or whatever else).
  3. It will be very useful if regular developer will be deprived the ability to make usual errors.

The problem with session and transaction management is solved in the way used in many other ORMs. Session & transaction class instance is maintained at business layer in a thread static manner. So it is not needed to maintain session between method calls. Also there are few strict rules to work with session and transaction at the BLL only. Caching is implemented based on MS Enterprise Library Caching as standalone service. Cache implementation heavily uses anonymous methods – new feature for .NET Framework 2. So that it is very easy to implement caching data.

I have implemented usage of DAL tier in BLL level in the way that MS suggests in their public project PetShop 4. BLL classes use IDAL interfaces as internal static members and use DAL Factory to instantiate concrete implementations.

private static readonly IAgreementServiceDal dal = DalManager.CreateInstance("Economy.AgreementServiceDal") as IAgreementServiceDal;

Afterword regular BLL method obtaining some data looks like following sample.

public AgreementInfoModel GetAgreementInfoById(Guid id)
{
    string key = String.Concat(AgreementService.AGREEMENT_BY_ID, id.ToString());
    return CacheService.GetData(
            key,
            AgreementService.AGREEMENT_BY_ID_SINC_KEY,
            TimeSpan.FromSeconds(AgreementService.AGREEMENT_BY_ID_CACHE_INTERVAL),
            delegate
            {
                AgreementInfoModel agreement = null;
                using (Session session = base.OpenSession())
                {
                    agreement = dal.GetAgreementInfoById(session.Current, id);
                    if (agreement != null)
                    {
                        agreement.CurrentAmount = dal.GetAgreementTransferAmountByAgreementId(session.Current, agreement.Id, DateTime.Today);
                    }
                }
                return agreement;
            }
        );
}

Regular BLL method performing some activity looks like following sample.

public void DeleteAgreementInfoById(AgreementToInfoModel agreementTO)
{
    using (Session session = base.OpenSession())
    using (Transaction tx = session.BeginTransaction())        
    {
        if (agreement.ImageId != Guid.Empty)
        {
            ServiceFacade.ImageService.DeleteImageInfoById(agreement.ImageId);
        }
        dal.DeleteAgreementInfoById(session.Current, agreement.Id);
        tx.Commit();
    }
}

GUI usage of tiers below

There is a Service façade which aggregates all BLL services into a single point. This Service façade makes method calls from GUI to tiers below very easy.

protected void btnSave_Click(object sender, EventArgs e)
{
    if (Page.IsValid)
    {
        BranchInfoModel item = this.pc.GetObject() as BranchInfoModel;
        ServiceFacade.BranchService.SaveBranch(item);

        this.ShowListPage();
    }
}
public override object GetObject()
{
    BranchInfoModel item = ServiceFacade.BranchService.GetBranchInfoById(this.ItemId);
    if (item == null)
    {
        item = new BranchInfoModel();
    }
    item.Name = this.tbName.Text;
    item.Email = this.etbEmail.Text;
    return item;
}

NetTierGenerator code generator utility

Solution framework above gives perfect environment for code generation utilities. Suggested code generator handles DTO models, DAL (+ IDAL), and BLL layers. Also it has intermediate level between backed database structure and output code. This allows creating additional methods with database calls at this intermediate level.

There is an important feature of this utility. It generates 2 physical files for each class (one for its generated content and one for developers needs, both these files at each layer contain declaration for single C# class as partial items).

Suggested code generator utility solves following tasks:

  1. It allows placing generated content into target namespace(s).
  2. It maps database table/view to own intermediate declaration structure.
  3. It maps each intermediate XML declaration file to own application service.
  4. Each intermediate XML declaration file can contain mappings to several database tables/views (this allows tie them into a single service).
  5. It allows easily map database table column to C# enumerator.
  6. It allows declaring additional methods which interact with backend database.
  7. It allows manipulating code generation at each layer so that it is pretty easy to overwrite generated content to a custom code.
  8. It has got GUI for creation XML declarations from backend database.
  9. It has got a functionality for obtaining paged data (here I’m using one of the methods described in this article - http://www.codeproject.com/KB/aspnet/PagingLarge.aspx)

Simple XML declaration will look like following sample.

<TierModel Namespace="Economy" ServiceName="City">
    <Declare Type="Solution.Common.Economy.BranchConditionEnum" />
    <Declare Type="Solution.Common.Economy.PaymentTypeEnum" />

    <Include Path="Economy\Image.xml" Type="ImageInfo" /> 
    
    <ItemModel DbTable="tblEconomy_City" ClassName="CityInfo" Caching="True" Parent="">
        <Comment />
        <KeyProperty NeedToGenerate="true" ReadOnly="False">
            <Comment />
            <CSharp CSharpName="id" CSharpType="Guid" />
            <Db DbName="rowguid" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </KeyProperty>
        <Property ReadOnly="False">
            <Comment />
            <CSharp CSharpName="BranchId" CSharpType="Guid" />
            <Db DbName="BranchId" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </Property>
        <Property ReadOnly="False">
            <Comment />
            <CSharp CSharpName="Name" CSharpType="string" Length="100" />
            <Db DbName="Name" DbType="nvarchar" IsNullable="False" Length="100" />
        </Property>
        <SelectMethod NeedToCreate="True" DalAccess="True" BllAccess="True" />
        <InsertMethod NeedToCreate="True" DalAccess="True" BllAccess="True" />
        <UpdateMethod NeedToCreate="True" DalAccess="True" BllAccess="True" />
        <DeleteMethod NeedToCreate="True" DalAccess="True" BllAccess="False" />
    </ItemModel>
    
    <ListItemModel DbView="vwEconomy_City" ClassName="CityListItem" Parent="">
        <Comment />
        <KeyProperty>
            <Comment />
            <CSharp CSharpName="id" CSharpType="Guid" />
            <Db DbName="rowguid" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </KeyProperty>
        <Property>
            <Comment />
            <CSharp CSharpName="BranchId" CSharpType="Guid" />
            <Db DbName="BranchId" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </Property>
        <Property>
            <Comment />
            <CSharp CSharpName="Name" CSharpType="string" Length="100" />
            <Db DbName="Name" DbType="nvarchar" IsNullable="False" Length="100" />
        </Property>
        <Property ReadOnly="False">
            <Comment />
            <CSharp CSharpName="BranchName" CSharpType="string" Length="100" />
            <Db DbName="BranchName" DbType="nvarchar" IsNullable="False" Length="100" />
        </Property>
    </ListItemModel>
    
    <SelectMethod Name="GetCitiesByBranchId" DalAccess="True" BllAccess="True">
        <Comment />
        <Return ReturnType="IList" Type="CityInfo">
            <Comment />
        </Return>
        <Property>
            <Comment />
            <CSharp CSharpName="BranchId" CSharpType="Guid" />
            <Db DbName="BranchId" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </Property>
        <Sql>
            <Query><![CDATA[SELECT * FROM tblEconomy_City WHERE BranchId = @BranchId]]></Query>
        </Sql>
    </SelectMethod>
    <SelectMethod Name="GetListPage" DalAccess="True" BllAccess="True">
        <Comment />
        <Return ReturnType="ListPage" Type="CityListItem">
            <Comment />
        </Return>
        <Sql>
            <Query><![CDATA[SELECT * FROM vwEconomy_City]]></Query>
        </Sql>
    </SelectMethod>

    <UpdateMethod Name="InsertCityImage" DalAccess="True" BllAccess="False">
        <Comment></Comment>
        <Property>
            <Comment />
            <CSharp CSharpName="CityId" CSharpType="Guid" />
            <Db DbName="CityId" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </Property>
        <Property>
            <Comment />
            <CSharp CSharpName="ImageId" CSharpType="Guid" />
            <Db DbName="ImageId" DbType="uniqueidentifier" IsNullable="False" Length="16" />
        </Property>
        <Sql>
            <Query><![CDATA[INSERT INTO tblEconomy_CityImages (CityId, ImageId) VALUES (@CityId, @ImageId)]]></Query>
        </Sql>
    </UpdateMethod>
</TierModel>

As you can see it is pretty strait forward. And as for me it is very attractive that developer can determine SQL statement at this point. Because each database engine has own strong and weak points and it is not a good idea to try to write/generate SQL code for all the database engines in the same unified way (e.g. MS SQL server contain a number of unique SQL statements like EXISTS, recursive CTE, HierarcyId etc).

Here is description for declaration elements of the above XML in details.

TierModel

This is a root node which determines service name and its placement at each tier (sample above will be placed at namespace Economy at every layer with the name CityService).

Declare

This node allows declaring additional using(s) for a service classes so that further we can use types from declared namespace.

Include

This node allows using types declared in another XML declaration(s) (e.g. in this sample we can reference to a model ImageInfo).

ItemModel

This is a base node type for code generator utility at whole. It maps database table to a single DTO class. It allows declaring key property which will be used in generated CRUD operations. It allows mapping each table column to own DTO class property and also it allows pointing details for generated validation code.

ListItemModel

This node is aimed to declare DTO classes for lists (main difference from item model is that list item model can contain fields from other essences, for example CityListItem contain BranchName field). As well there is no CRUD operations for them but their key properties are used for maintaining strict ordering for lists at run-time.

SelectMethod

This node allows declaring custom methods. These methods can return either C# types or types declared in intermediate XML. Also it contains properties which further will be propagated to method signature and SQL statement parameters. Also we can bring a custom implementation for a method at every layer by manipulating DalAccess/BLLAccess attributes. As well it contain SQL node where we can set up a SQL statement. This is the point where potentially we can split implementation for different backend databases if needed.

UpdateMethod

It is mostly the same as SelectMethod except one difference these methods are not aimed to return any value.

How to use NetTierGenerator

Here I will show how to use this tool in a best way. To achieve this I will use a sample windows application.

MS SQL Server backend database

I will use simple database structure that contains few tables. All of them are compatible to small set of rules. Tables from “Administration” namespace will not be used in GUI application itself they are included only to show some useful trick of NetTierGenerator & suggested application framework.

  1. Table/view name must reflect namespace and item name.
  2. Avoid using of database triggers and stored procedures (just in case SQL Server capable to store execution plans for most of requested queries, see sys.dm_exec_query_stats).
    Use database for its direct goals – store data, maintain data indexes and data integrity.
  3. Always try to use uniqueidentifier columns for table primary keys because it is very helpful for many reasons (e.g. maintain database replications etc).

First steps with NetTierGenerator

NetTierGenerator has 2 executables: first one is the GUI application and second one is the console application. GUI application is aimed to allow quickly creating new definitions from backend database. Console application is aimed to allow quickly apply modifications in definitions.

  • First it needed to define a certain place for NetTierGenerator binaries and adjust its settings. Here is solution physical folders structure with a folder “Tools” that contains NetTierGenerator binaries.

  • Modify “App.config” file of NetTierGenerator to reflect your current database settings.
  • Next it needed to adjust NetTierGenerator. It can be done by modifying “TierGeneratorSettings.xml” or by using “NetTierGenerator.WinUI.exe”.
  • Next it needed to register both these executables as external tools in Visual Studio IDE. And assign to them keyboard shortcuts.

Map backend database structure to your solution

Now we are ready to create XML mappings. It needed just open the solution in VS IDE. And then start NetTierGeneratorWinGUI.

  1. Select “tblStore_Good” in “Database tables” combobox.
    Then application will automatically adjust “Namespace” and “Service Name” settings for current model.
    Then go to “List Item” tab and select vwStoore_Good.
    Then click “Generate xml only” button.
  2. NetTierGenerator generates xml declaration into “TierModel” folder.
  3. Then open this xml declaration in VS IDE end use Console version of NetTierGenerator.
    Also at this time you can add additional methods you want.
  4. Then system will generate:
    a). “GoodInfo”, “GoodListItem” into Sample.Model project
    b). IGoodServiceDal into Sample.IDAL project
    c). GoodServiceDAL into Sample.MSSqlDal project
    d). GoodService into Sample.BusinessLogic project (also it will modify ServiceFacade class)

System will generate by 2 files per each class. One for NetTierGenerator needs these files will be regenerated each time. And second one for user defined code.

How to use previously generated stuff

There is a single point that allows access to all methods in BLL – ServiceFacade. Developer does not need to create instances of BLL services a presentation layer.

Lists support

One of the most common goals is the displaying lists. Sample application uses DataGridView control in a Virtual mode to display data in the grid with enabled vertical scrolling. It receives small portion of data each time. Here this is code that performs this task from presentation layer (sorting and filtering could be easily adjusted at this point too).

this.currentGoodListQuery = new ListQuery();
this.currentGoodListQuery.RowsPerPage = this.dgvOrderList.DisplayedRowCount(false);
this.currentGoodListQuery.FirstRowIndex = this.dgvOrderList.FirstDisplayedScrollingRowIndex;

this.currentGoodListPage = ServiceFacade.GoodService.GetListPage(this.currentGoodListQuery);
this.dgvOrderList.RowCount = this.currentGoodListPage.TotalRowCount;

Raw level access & CRUD

This application framework gives very easy way of using CRUD operations. Here is the sample code that populates DTO instance with input data and stores it into backend database.

private void SaveInfoModel()
{
    if (this.goodInfoModel == null)
    {
        this.goodInfoModel = new GoodInfoModel();
    }

    this.goodInfoModel.Name = this.tbName.Text;
    this.goodInfoModel.Cost = FormatHelper.ParseDecimal(this.tbCost.Text);
    this.goodInfoModel.Quantity = FormatHelper.ParseInt32(this.tbQuantity.Text);

    if (this.goodInfoModel.ID == Guid.Empty)
    {
        ServiceFacade.GoodService.InsertGoodInfo(this.goodInfoModel);
    }
    else
    {
        ServiceFacade.GoodService.UpdateGoodInfo(this.goodInfoModel);
    }
}

How to customize the code

Let imagine that we need to customize some code implementation. There are several different code modification can be applied.

Override generated code

For example we need to send email for someone when any good is removed. In that particular case we need to extend implementation of DeleteGoodInfo at BLL. To perform this task it needed to make following two steps:

  1. Move DeleteGoodInfoByID from generated file to user specific file and apply code modifications.
  2. Open “Good.xml”, set BllAccess to false in DeleteMethod of GoodInfo and regenerate the code from good xml declaration file.

Add user defined function

For example we need a functionality to obtain a total amount or goods that cost less than specified amount. In this case we need to define own method with a custom behavior. This declaration can look as following sample.

<SelectMethod Name="GetGoodAmountByCost">
    <Comment />
    <Return ReturnType="int">
        <Comment />
    </Return>
    <Property>
        <Comment />
        <CSharp CSharpName="Cost" CSharpType="decimal" />
        <Db DbName="Cost" DbType="numeric" IsNullable="False" Length="9" />
    </Property>
    <Sql><Query>
<![CDATA[SELECT COUNT(ID) AS [count] FROM tblStore_Good WHERE Cost <= @Cost]]>
    </Query></Sql>
</SelectMethod>

After you define this methods in xml declaration you will need to regenerate the code. DAL, IDAL and BLL tiers will be modified accordingly. So that you can use this method with specified parameters right from presenhtation layer.

Conclusion

At this point solution & code generator above are not aimed to solve all problems of application developer. From my point of view their mission can be described as following:

  1. They help to make application skeleton in a few minutes. As well it allows to keep updated all the application layers after changes to underlying data structure easily
  2. Solution framework defines certain points for implementation of business rules, data mining, GUI. So the solution will not contain mess of these items
  3. Solution & code generator are constructed in conjunction
  4. It has meta level (xml declaration) where we can manage generated code output. In other words this can be named like “meta development”.
  5. It divides developed application into strict layers. It allows to easily managing database transactions. It define strict place for implementation of business rules so you will not have a mess in your code.
  6. It makes all of its activity at design time not at run time.
  7. It makes application development more manageable, easy and fun.
  8. It deprives application developers of common errors.
  9. NetTierGenerator application itself can be adjusted to any specific need.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Dmitry Zubrilin



Occupation: Software Developer (Senior)
Location: Russian Federation Russian Federation

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 12 of 12 (Total in Forum: 12) (Refresh)FirstPrevNext
GeneralMore Work?memberhomerbush8:21 1 Dec '08  
GeneralRe: More Work?memberDmitry Zubrilin9:39 1 Dec '08  
GeneralVery NicememberPaul Conrad8:15 1 Dec '08  
QuestionLooks promissing [modified]memberMarc Sommer7:42 1 Dec '08  
AnswerRe: Looks promissingmemberDmitry Zubrilin9:03 1 Dec '08  
GeneralRe: Looks promissingmemberMarc Sommer9:08 1 Dec '08  
GeneralOptionsmemberDmitri Nesteruk23:57 30 Nov '08  
GeneralLooks cool, but.....mvpSacha Barber23:40 30 Nov '08  
GeneralRe: Looks cool, but.....memberDmitry Zubrilin0:45 1 Dec '08  
GeneralRe: Looks cool, but.....mvpSacha Barber1:00 1 Dec '08  
GeneralRe: Looks cool, but.....memberDmitry Zubrilin10:11 1 Dec '08  
GeneralRe: Looks cool, but.....mvpSacha Barber10:34 1 Dec '08