|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
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 IntroductionThere 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 frameworkIn 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 layersThe 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. CommonThis 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:
Configuration managementThis 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 ModelSet 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 LayerThere 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:
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 databaseI use some strict rules that I follow during database developing.
Business LayerThere are several different problems which must be solved at this tier.
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
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 belowThere 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 utilitySolution 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:
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. TierModelThis 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). DeclareThis node allows declaring additional using(s) for a service classes so that further we can use types from declared namespace. IncludeThis node allows using types declared in another XML declaration(s) (e.g. in this sample we can reference to a model ImageInfo). ItemModelThis 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. ListItemModelThis 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. SelectMethodThis 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. UpdateMethodIt is mostly the same as SelectMethod except one difference these methods are not aimed to return any value. How to use NetTierGeneratorHere 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 databaseI 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.
First steps with NetTierGeneratorNetTierGenerator 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.
Map backend database structure to your solutionNow we are ready to create XML mappings. It needed just open the solution in VS IDE. And then start NetTierGeneratorWinGUI.
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 stuffThere 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 supportOne 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 & CRUDThis 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 codeLet imagine that we need to customize some code implementation. There are several different code modification can be applied. Override generated codeFor 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:
Add user defined functionFor 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. ConclusionAt 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:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||