Archive for the ‘Кодогенерация’ Category

T4 templates

Posted: Март 22, 2011 in .NET, Кодогенерация

Ранее я уже писал о кодогенерации с помощью CodeSmith. Сегодня рассмотрим встроенный в Visual Studio механизм под названием T4 templates.

Создадим облегченную модель на основе какой-нибудь базы данных. У меня, например, структура таблиц такая

Для этого добавим файл под названием Model.tt со следующим содержимым

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="Microsoft.SqlServer.ConnectionInfo" #>
<#@ Assembly Name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<#@ Assembly Name="Microsoft.SqlServer.Smo" #>
<#@ import namespace="Microsoft.SqlServer.Management.Smo" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>

namespace Entities
{
<#
var server = new Server(@".\SQLEXPRESS");
Database database = server.Databases["Test"];

foreach (Table table in database.Tables){#>
	public class <#= table.ToString().Replace("[dbo].[","").Replace("]","") #>
	{
		<#foreach(Column col in table.Columns){ #>
			public string  <#= col.Name #>;
		<#}#>
	}
			   
<#}#>
}

В файле Model.cs у меня появилось

namespace Entities
{
    public class Clients
    {
        public string ID;
        public string Name;
        public string Password;
        public string Email;
        public string Address;
    }

    public class OrderLines
    {
        public string ID;
        public string ProductID;
        public string Qty;
    }

    public class OrderLinesToOrders
    {
        public string OrderToLineID;
        public string OrderID;
        public string OrderLineID;
    }

    public class Orders
    {
        public string ID;
        public string ClientID;
        public string OrderDate;
    }

    public class Products
    {
        public string ID;
        public string Title;
        public string Barcode;
        public string Price;
    }

    public class sysdiagrams
    {
        public string name;
        public string principal_id;
        public string diagram_id;
        public string version;
        public string definition;
    }

}

Понятное дело, что еще есть с чем поработать, но отправная точка уже дана. Для более серьезной работой с T4 шаблонами рекомендую установить tangible T4 Editor.

Office Open XML

Posted: Ноябрь 20, 2010 in .NET, Кодогенерация

Файлы с раширением docx, xlsx, pptx — это zip файлы, в которых хранятся xml файлы с описанием и содержанием соответствующих офисных документов. Про Office Open XML лучше почитать на http://openxmldeveloper.org/default.aspx
До недавних пор я формировал офисные документы используя Office Interop Assemblies. С приходом Office 2007 для разработчиков стал доступен инструмент под названием Document Reflector, входящий в состав Open XML SDK Tool. К сожалению, я узнал про него недавно, но уже сейчас знаю, что буду использовать данный инструмент, когда нужно создать какие-нибудь акты, приказы или отчеты по какому-то уже существующему у заказчика документу. Этот инструмент может за нас написать весь код создания документа, нужно только загрузить его через меню Open File. Также возможно создание кода для отдельных параграфов, свойств, стилей…

Раньше в начале 2000-х, когда еще не было Entity Framework, Hibernate только начал развиваться, а у меня появился мой первый мобильный телефон Siemens (модель не помню), работа с базой данных в среде ADO.NET выглядела примерно так

string connectionString =
	"Data Source=(local);Initial Catalog=Northwind;"
	+ "Integrated Security=true";

// Provide the query string with a parameter placeholder.
string queryString =
	"SELECT ProductID, UnitPrice, ProductName from dbo.products "
		+ "WHERE UnitPrice > @pricePoint "
		+ "ORDER BY UnitPrice DESC;";

// Specify the parameter value.
int paramValue = 5;

// Create and open the connection in a using block. This
// ensures that all resources will be closed and disposed
// when the code exits.
using (SqlConnection connection =
	new SqlConnection(connectionString))
{
	// Create the Command and Parameter objects.
	SqlCommand command = new SqlCommand(queryString, connection);
	command.Parameters.AddWithValue("@pricePoint", paramValue);

	// Open the connection in a try/catch block.
	// Create and execute the DataReader, writing the result
	// set to the console window.
	try
	{
		connection.Open();
		SqlDataReader reader = command.ExecuteReader();
		while (reader.Read())
		{
			Console.WriteLine("\t{0}\t{1}\t{2}",
				reader[0], reader[1], reader[2]);
		}
		reader.Close();
	}
	catch (Exception ex)
	{
		Console.WriteLine(ex.Message);
	}
	Console.ReadLine();

Тогда же появился замечательный инструмент для кодогенерации под название CodeSmith. Стоит он около 100 американских рублей, что делает его вполне доступным для легального использования. Этот замечательный инструмент позволяет создавать код на любом языке. Он предназначен не для создания законченных решений, а для облегчения написания каких-то рутинных действий, как, например, процедуры CRUD на TSQL и их обертки в ввиде Data Objects, Business Objects, Common Objects в среде C#.
Давайте рассмотрим его действие на примере создания объекта District. Это будет простой Common-объект состоящий только из свойств. Исходная таблица выглядит следующим образом

CREATE TABLE [dbo].[Districts](
	[District_ID] [int] NOT NULL PRIMARY KEY CLUSTERED,
	[District_Name] [nvarchar](100) NULL,
	[District_Name_Old] [nvarchar](100) NULL,
	[Date_Create] [smalldatetime] NOT NULL,
	[Date_Begin] [smalldatetime] NOT NULL,
	[Date_End] [smalldatetime] NULL
)

Запустим CodeSmith и создадим новый C# Template. Добавим свойства для имени исходной таблицы, автора, пространства имен, префикса таблицы, и нужно ли убирать окончание, если имя таблицы записано во множественном числе

<%@ Property Name="SourceTable" Type="SchemaExplorer.TableSchema" Category="Context" Description="Table that the object is based on." %>
<%@ Property Name="Author" Type="System.String" Default="Maxim Masunov" Category="Style" Description="Author" %>
<%@ Property Name="BusinessLayerNameSpace" Type="System.String" Default="Common" Category="Style" Description="The Namespace of the Business Layer class" %>
<%@ Property Name="RemoveEndingS" Type="System.Boolean" Default="true" Category="Style" Description="If table name ends in s, class name will not if this is set to true." %>
<%@ Property Name="TablePrefix" Type="System.String" Default="t_" Category="Style" Description="Table Prefix" %>

Определим метод, который по имени таблицы будет создавать имя класса

<script runat="template">
public string GetClassName()
{
	if ((this.RemoveEndingS)
		&&(SourceTable.Name.EndsWith("s"))
		)
	{
		return RemovePrefix(SourceTable.Name.Substring(0,SourceTable.Name.Length -1));
	}
	else
	{
		return RemovePrefix(SourceTable.Name);
	}
}

public string RemovePrefix(string TableName)
{

	return TableName.Replace(TablePrefix,"");
}
</script>

И наконец нам нужно проитерировать все колонки, проставить их тип и добавить методы get, set. Окончательно файл шаблона будет выглядеть так

<%@ CodeTemplate Language="C#" TargetLanguage="C#" Src="" Inherits="CodeSmith.BaseTemplates.SqlCodeTemplate" Debug="False" Description="Template description here." %>
<%@ Property Name="SourceTable" Type="SchemaExplorer.TableSchema" Category="Context" Description="Table that the object is based on." %>
<%@ Property Name="Author" Type="System.String" Default="Maxim Masunov" Category="Style" Description="Author" %>
<%@ Property Name="BusinessLayerNameSpace" Type="System.String" Default="Common" Category="Style" Description="The Namespace of the Business Layer class" %>

<%@ Property Name="TablePrefix" Type="System.String" Default="t_" Category="Style" Description="Table Prefix" %>
<%@ Property Name="RemoveEndingS" Type="System.Boolean" Default="true" Category="Style" Description="If table name ends in s, class name will not if this is set to true." %>


<%@ Assembly Name="CodeSmith.BaseTemplates" %>
<%@ Assembly Name="System.Data" %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Import Namespace="SchemaExplorer" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="CodeSmith.BaseTemplates" %>


<% if (SourceTable.PrimaryKey == null) throw new ApplicationException("SourceTable does not contain a primary key."); %>
<% ColumnSchema primaryKey = SourceTable.PrimaryKey.MemberColumns[0]; %>

// Author:					<%= Author %>
// Created:					<%= DateTime.Now.Year.ToString() %>-<%= DateTime.Now.Month.ToString() %>-<%= DateTime.Now.Day.ToString() %>
// Last Modified:			<%= DateTime.Now.Year.ToString() %>-<%= DateTime.Now.Month.ToString() %>-<%= DateTime.Now.Day.ToString() %>
// 
// The use and distribution terms for this software are covered by the 
// Common Public License 1.0 (http://opensource.org/licenses/cpl.php)  
// which can be found in the file CPL.TXT at the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by 
// the terms of this license.
//
// You must not remove this notice, or any other, from this software.
	
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;

namespace <%= BusinessLayerNameSpace %>
{
	
	public class <%= GetClassName() %> : BaseObject
	{

	<% for (int i = 0; i < SourceTable.Columns.Count; i++) { %>
		public <%= GetCSharpVariableType(SourceTable.Columns[i]) %> <%= SourceTable.Columns[i].Name %> 	{get;set;}
	<% } %>

	}
	
}

<script runat="template">
public string GetClassName()
{
	if ((this.RemoveEndingS)
		&&(SourceTable.Name.EndsWith("s"))
		)
	{
		return RemovePrefix(SourceTable.Name.Substring(0,SourceTable.Name.Length -1));
	}
	else
	{
		return RemovePrefix(SourceTable.Name);
	}
}

public string RemovePrefix(string TableName)
{
	return TableName.Replace(TablePrefix,"");
}
</script>

А вот код, который получился после запуска над таблицей Districts

// Author:					Maxim Masunov
// Created:					2010-9-25
// Last Modified:			2010-9-25
// 
// The use and distribution terms for this software are covered by the 
// Common Public License 1.0 (http://opensource.org/licenses/cpl.php)  
// which can be found in the file CPL.TXT at the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by 
// the terms of this license.
//
// You must not remove this notice, or any other, from this software.
	
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;

namespace Common
{
	public class District : BaseObject
	{
		public int District_ID 	{get;set;}
		public string District_Name 	{get;set;}
		public string District_Name_Old 	{get;set;}
		public DateTime Date_Create 	{get;set;}
		public DateTime Date_Begin 	{get;set;}
		public DateTime Date_End 	{get;set;}
	}
}

Сейчас всю работу за нас выполняет Visual Studio при перетаскивании таблиц на поверхность Entity Model. Там же можно изменить названия полей на ваше усмотрения. Плюс все созданные классы помечаются как partial, что означает возможность их расширения не влезая в автоматически созданный код.