I'm using T4 to generate some scripts as runtime templates. A part of this is to read some text files that include some T4 directive the for each file generate an output using a master template.
Each file may contain text such as:
Create table foo (
rid bigint identity(<#= db.DefaultSeed #>, <#= db.DefaultIncrement #>),
...other table specific stuff
I have a template that includes the object data for the db referenced in the other files and some source file reference.
The main template to run could be like:
<## template language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## include file="Src" #>
Where src would refer to a variable passed to the template.
Then I would like to have a string list of files that could be iterated over to build individual scripts so that the following could be done in say a console program:
public void BuildTables(){
List<string> files = getFiles();//just get a list of files in a folder to process
foreach(var f in files){
var mainTemplate = new MainTemplate(dbSettings);
mainTemplate.Src = f;
String text = mainTemplate.TransformText();
System.IO.File.WriteAllText(outPath + f, text);
}
}
Howerver, it looks like the include directive is unable to accept variables like:
<## include file="<#= Src #>" #>
Is there a way to inject the text into the template file?
Related
This a section in a schema file
imports:
- path: configs/folder1/resources/gcpresource/test/*
I am trying to import all the files in a folder using the template's schema file.
I know that this does not work.
My question is,
what is a better way to import all the files using * or something that's suitable so that deployment manager can import all of the files in a folder without having to explicitly specify them ?
You could do so in python if they are all yaml config files
# my-template.yaml
imports:
- path: omni-importer.py
And in the middleware-like python template do something like:
# omni-importer.py
import yaml
from os import listdir
from os.path import isfile, join
def generate_config(ctx):
mypath = ctx.properties['path']
files = [f for f in listdir(mypath) if isfile(join(mypath, f)) and f.endswith('yaml')]
yamls = map(lambda f: yaml.load(open('test.txt')['resources']), files)
return {
'resources': yamls,
}
This is not working code, but rather a proof of concept
I would like to use the System.Configuration assembly within a T4 template to get the connection string listed in the App.config of the project. However, the compiler does not seem to accept the [ ] in the statement block. How is this done?
<## assembly name="System.Configuration" #>
<## import namespace="System.Configuration"#>
<#
var connectionString = ConfigurationManager.ConnectionStrings["localconnection"].ConnectionString;
#>
TIA
If you're running T4 at design time (CustomTool: TextTemplatingFilePreprocessor), the template code gets executed as part of VisualStudio process. VisualStudio is loading devenv.exe.config and not your project config (you can check via AppDomain.CurrentDomain.SetupInformation.ConfigurationFile).
That's why you get null ref exception - 'localconnection' connection string is not in devenv.exe.config.
You can load your project config file using ConfigurationManager.OpenMappedExeConfiguration:
<## template debug="false" hostspecific="true" language="C#" #>
<## assembly name="System.Configuration" #>
<## import namespace="System.Configuration"#>
<## import namespace="System.IO" #>
<#
string configPath = Path.Combine(Host.ResolveAssemblyReference("$(ProjectDir)"), "App.config");
var configFileMap = new ExeConfigurationFileMap{ExeConfigFilename = configPath};
var config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
string connectionString = config.ConnectionStrings.ConnectionStrings["localconnection"].ConnectionString;
WriteLine(connectionString);
#>
Note, it must be hostspecific="true" in order to use Host to resolve your project folder.
I want to run the t4 TextTransForm.exe utility on my build server on the command line. I'm aware that the DTE object etc are not available on the command line.
But executing a simple transform on a template which expects a parameter also suggests the parameter directive is not working on the command line.
C:\util>"C:\Program Files (x86)\Common Files\Microsoft Shared\TextTemplating\12.0\TextTransform.exe" test.tt -a !!MyParameter!test
error : Errors were generated when initializing the transformation object. The transformation will not be run. The following Exception was thrown:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.VisualStudio.TextTemplating9edb37733d3e4e5f96a377656fe05b5c.GeneratedTextTransformation.Initialize()
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
at Microsoft.VisualStudio.TextTemplating.TransformationRunner.PerformTransformation()
This is my test.tt template:
<## template language="C#" hostspecific="true" #>
<## parameter name="MyParameter" type="System.String" #>
Parameter in expression block: <#= MyParameter #>
Parameter in statement block: <# Write(MyParameter); #>
Looking at discussions like
Get argument value from TextTransform.exe into the template gave me the impression it would also work on the command line without a specific host or Visual studio installed.
So am I doing something wrong, or will this just not work on the command line?
The parameter directive was the line which caused the error. Just remove it and your can read the value of the parameter with:
this.Host.ResolveParameterValue("","","MyParameter");
So the working .tt looked like this:
<## template language="C#" hostspecific="true" #>
<## import namespace="Microsoft.VisualStudio.TextTemplating" #>
Parameter value is
<#
var MyParameter= this.Host.ResolveParameterValue("","","MyParameter");
Console.WriteLine("MyParameter is: " + MyParameter);
#>
hkstr's answer is almost correct, but the "Host.ResolveParameterValue()" call fails when used inside Visual Studio. The answer is to wrap the call in try...catch or to check for DTE. In my case the DTE had the information I wanted:
<## template debug="false" hostspecific="true" language="C#" #>
<## assembly name="System.Core" #>
<## assembly name="EnvDTE" #>
<## import namespace="EnvDTE" #>
<#
string activeConfiguration = null;
// The IServiceProvider is available in VS but isn't available on the command line.
IServiceProvider serviceProvider = Host as IServiceProvider;
if (serviceProvider != null)
{
DTE dte = (DTE)serviceProvider.GetService(typeof(DTE));
activeConfiguration = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
}
else
{
activeConfiguration = this.Host.ResolveParameterValue("", "", "ActiveConfiguration");
}
Console.WriteLine("ActiveConfiguration is: " + activeConfiguration);
#>
I have written a custom scaffolder for MVC3 using T4 templates to scaffold a delete stored procedure for a database and table passed to the scaffolder as parameters. When I run the scaffolder the output file is created in the correct place, but I get the following error:
Invoke-ScaffoldTemplate : Unable to add 'UpdateCustomerCoupon.sql'. A
file with that name already exists. At line:1 char:23
+ param($c, $a) return . <<<< $c #a
+ CategoryInfo : NotSpecified: (:) [Invoke-ScaffoldTemplate], COMException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.InvokeScaffoldTemplateCmdlet
It doesn't matter if the file exists at the output location or not. I get this error every time.
Here is the PowerShell script for the scaffolder:
[T4Scaffolding.Scaffolder(Description = "Enter a description of DeleteSQL here")][CmdletBinding()]
param(
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$DatabaseName,
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$TableName,
[string]$Project,
[string]$CodeLanguage,
[string[]]$TemplateFolders,
[switch]$Force = $true
)
$outputPath = "Scripts/SQL/$TableName/Delete$TableName"
Add-ProjectItemViaTemplate $outputPath -Template DeleteSQLTemplate `
-Model #{ TableName = $TableName; DatabaseName = $DatabaseName; Project = $Project } `
-SuccessMessage "Added DeleteSQL output at {0}" `
-TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
Write-Host "Scaffolded DeleteSQL"
And here is the T4 Template code:
<## Template Language="C#" HostSpecific="True" Inherits="DynamicTransform" Debug="True" #>
<## Output Extension="sql" #>
<## assembly name="System.Collections" #>
<## assembly name="System.Configuration" #>
<## assembly name="System.Data" #>
<## assembly name="System.Web" #>
<## import namespace="System.Collections.Generic" #>
<## import namespace="System.Configuration" #>
<## import namespace="System.Data" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="System.Web" #>
USE [<#= Model.DatabaseName #>]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[Delete<#= Model.TableName #>] (
#P<#= Model.TableName #>ID AS BIGINT,
#PChangedByUserID BIGINT,
#PChangedByURL VARCHAR(1024),
#PChangedByIpAddress varchar(16)
)
AS
SET NOCOUNT ON
BEGIN TRY
BEGIN TRANSACTION
-- update user history
DECLARE #userHistoryID BIGINT = 0;
DECLARE #details VARCHAR(4096) = '[<#= Model.TableName #>] ID ''' + CAST(#P<#= Model.TableName #>ID AS VARCHAR) + ''' was deleted.'
EXEC InsertUserHistory #PChangedByUserID, #details, #PChangedByURL, #PChangedByIpAddress, #userHistoryID OUTPUT
-- Rollback transaction if user history was not created
IF(#userHistoryID = 0) BEGIN
ROLLBACK
SELECT CAST(-1 AS INT)
RETURN
END
DELETE FROM
[<#= Model.TableName #>]
WHERE
[ID] = #P<#= Model.TableName #>ID
COMMIT
SELECT CAST(1 AS INT)
END TRY
BEGIN CATCH
ROLLBACK
SELECT CAST(-2 AS INT)
END CATCH
RETURN
I invoke the scaffolder by typing "Scaffold DeleteSQL -DatabaseName $DatabaseName -TableName $TableName" into the Package Manager Console.
I have also tried invoking the scaffolder with the -Force option like this: "Scaffold DeleteSQL -DatabaseName $DatabaseName -TableName $TableName -Force". I have also used "-Force true" and "-Force:true" with no luck. Also notice that the $Force parameter is set to true anyway, so I think it should overwrite by default, right?
What do I need to do to get rid of this error?
Thanks for the help.
Looks like the problem had to do with the path the output was generate to.
I changed this line in the PowerShell script:
$outputPath = "Scripts/SQL/$TableName/Delete$TableName"
to this:
$outputPath = "Scripts\SQL\$TableName\Delete$TableName"
The only thing I changed was the slashes from "/" to "\". Now, I don't get the error message anymore.
how can I connect to a local database with T4?, I try these code but it not working
Iam using vs.net 2010 and SQL2008 with windows authentication, Iam trying to connect to my local server, to that database to get their properties
<## template language="C#v3.5" #>
<## output extension=".cs" #>
<## assembly name="System.Data" #>
<## assembly name="System.Xml" #>
<## assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<## assembly name="Microsoft.SqlServer.Smo" #>
<## assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<## import namespace="System.IO" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="Microsoft.SqlServer.Management.Smo" #>
<## import namespace="Microsoft.SqlServer.Management.Common" #>
namespace T4SNUG.Entities
{
<# Server server = new Server(".");
Database db = server.Databases["Chinook"]};#>
Yes, this should work.
Are you seeing an error message or exception in the output window?
I can use the following code in a T4:
<## output extension=".txt" #>
<## assembly name="System.Data" #>
<## assembly name="System.Core.dll" #>
<## assembly name="System.Xml" #>
<## assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<## assembly name="Microsoft.SqlServer.Smo" #>
<## assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<## import namespace="System.IO" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="Microsoft.SqlServer.Management.Smo" #>
<## import namespace="Microsoft.SqlServer.Management.Common" #>
<#
var server = new Server(".\\SQLEXPRESS");
var db = server.Databases["moviereviews"];
foreach(var property in db.GetType()
.GetProperties()
.Where(p=>p.PropertyType == typeof(string)))
{
#>
<#= property.Name #> : <#= property.GetValue(db, null) #>
<#
}
#>
and it gives me:
Name : moviereviews
Collation : SQL_Latin1_General_CP1_CI_AS
DatabaseSnapshotBaseName :
DefaultFileGroup : PRIMARY
DefaultFileStreamFileGroup :
DefaultFullTextCatalog :
DefaultSchema : dbo
MirroringPartner :
MirroringPartnerInstance :
MirroringWitness :
...