how to make T4 text template database driven - c#

I am experimenting with code generation atm, I've writen a small template that does what I need it to do, but I need to hardcode the properties myself, I want to extract class and property names from a database where the tables are the classes and index fields are the properties. Idea is to write a filter class based on table indexes. The code I have now assumes that I have 1 table and 3 fields to use as filter criteria.
Currently don't have any code to generate the compiled version as VS does this internally.
Here is what I have so far:
<## template debug="false" hostspecific="false" language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## parameter name="namespacename" type="System.String" #>
<## parameter name="filterobjectname" type="System.String" #>
<## parameter name="property1" type="System.String" #>
<## parameter name="property2" type="System.String" #>
<## parameter name="property3" type="System.String" #>
<## output extension=".cs" #>
using AzzWork.Business;
using Ventura.Business.Entities;
<# string _namespacename; if(this.namespacename==null){_namespacename = "Ventura.Business";}else{_namespacename = this.namespacename;} #>
<# string _classname; if(this.filterobjectname==null){_classname = "BusinessObject";}else{_classname = this.filterobjectname;}#>
<# string _property1; if(this.property1==null){_property1 = "LastName";}else{_property1 = property1;}#>
<# string _property1c = _property1.Substring(0, 1).ToLower() + _property1.Substring(1);#>
<# string _property2; if(this.property2==null){_property2 = "Email";}else{_property2 = property2;}#>
<# string _property2c = _property2.Substring(0, 1).ToLower() + _property2.Substring(1);#>
<# string _property3; if(this.property3==null){_property3 = "FirstName";}else{_property3 = property3;}#>
<# string _property3c = _property3.Substring(0, 1).ToLower() + _property3.Substring(1);#>
<# string seperator1; if((_property1!=null && _property2!=null) || (_property1!=null && _property2==null && _property3!=null)){seperator1=", ";}else{seperator1="";}#>
<# string seperator2; if(_property2!=null && _property3!=null){seperator2=", ";}else{seperator2="";} #>
namespace <#= _namespacename #>
{
public static class <#= _classname #>FilterFactory{
internal static AzzFilterCollection<<#= _classname #>> GetFilterFor<#= _classname #>(<#if(_property1!=null){#>string <#=_property1c #>Part<#}#><#=seperator1#><#if(_property2!=null){#>string <#=_property2c #>Part<#}#><#=seperator2#><#if(_property3!=null){#>string <#=_property3c #>Part<#}#>)
=> new AzzFilterCollection<<#=_classname#>>()
<#if(_property1!=null){ #>
.AddFilterFor<#= _property1 #>(<#=_property1c #>Part)
<#}#>
<#if(_property2!=null){ #>
.AddFilterFor<#= _property2 #>(<#=_property2c #>Part)
<#}#>
<#if(_property3!=null){ #>
.AddFilterFor<#= _property3 #>(<#=_property3c #>Part)<#}#>;
<#if (_property1!=null){#>
internal static AzzFilterCollection<<#=_classname #>> AddFilterFor<#= _property1 #>(this AzzFilterCollection<<#= _classname#>> filterCollection, string <#= _property1c #>Part)
{
if (<#= _property1c #>Part == null)
return filterCollection;
var filter = new AzzFilter<<#=_classname#>>(<#=_classname#>.ColumnNames.<#=_property1#>, <#=_property1c#>Part);
return filterCollection.AddFilter(filter);
}
<#}#>
<#if (_property2!=null){#>internal static AzzFilterCollection<<#=_classname #>> AddFilterFor<#= _property2 #>(this AzzFilterCollection<<#= _classname#>> filterCollection, string <#= _property2c #>Part)
{
if (<#= _property2c #>Part == null)
return filterCollection;
var filter = new AzzFilter<<#=_classname#>>(<#=_classname#>.ColumnNames.<#=_property2#>, <#=_property2c#>Part);
return filterCollection.AddFilter(filter);
}
<#}#>
<#if (_property3!=null){#>
internal static AzzFilterCollection<<#=_classname #>> AddFilterFor<#= _property3 #>(this AzzFilterCollection<<#= _classname#>> filterCollection, string <#= _property3c #>Part)
{
if (<#= _property3c #>Part == null)
return filterCollection;
var filter = new AzzFilter<<#=_classname#>>(<#=_classname#>.ColumnNames.<#=_property3#>, <#=_property3c#>Part);
return filterCollection.AddFilter(filter);
}
<#}#>
}
}

I guess you're looking at something for SQL.
Does this helps you?
This, This (same),
Also this

Related

Can not use DisplayAttribute in T4 template

In my custom T4 template I have to read DisplayAttribute of a type. But I'm keep getting different errors after struggling with several solutions.
Here is part of my .tt file :
<## template debug="true" hostspecific="true" language="C#" compilerOptions="/langversion:10" #>
<## assembly name="System" #>
<## assembly name="System.Runtime" #>
<## assembly name="System.Core" #>
<## assembly name="System.ComponentModel.Annotations" #>
<## import namespace="System" #>
<## import namespace="System.Reflection" #>
<## import namespace="System.Runtime" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## import namespace="System.ComponentModel.DataAnnotations" #>
<## output extension=".cs" #>
<#
static string GetEnumValueDisplayName(Enum value)
{
Type type = value.GetType();
MemberInfo[] memInfo = type.GetMember(value.ToString());
DisplayAttribute? dispalyAttribute = null;
if (memInfo != null && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
if (attrs != null && attrs.Length > 0)
dispalyAttribute = attrs[0] as DisplayAttribute;
}
return dispalyAttribute?.Name ?? "";
}
#>
<#
foreach(myEnum en in Enum.GetValues(typeof(MyEnum)))
{#>
public static class <#=#en.ToString()#> { public const string Value = "<#=#en.ToString()#>";public const string Text = "<#=#GetEnumValueDisplayName(en)#>";}
<#}#>
And here is my compile time error :
Compiling transformation: The type name 'DisplayAttribute' could not
be found in the namespace 'System.ComponentModel.DataAnnotations'.
This type has been forwarded to assembly
'System.ComponentModel.DataAnnotations, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35' Consider adding a
reference to that assembly.
I am using .net 6 with Visual Studio 2022(17.0.4).
I am using .net 6
That would be a problem. The VS T4 engine only supports .NET Framework at this point, which means that it cannot load any .NET Core libraries during the transformation. We have started working on a .NET Core version of the engine but there is no ETA on when this would become available. Any updates would be communicated through this Suggestion Ticket.
Remove <## assembly name="System.ComponentModel.Annotations" #> and add using System.ComponentModel.DataAnnotations; to the regular using statements.

T4 parameter directive using .Net Core

I have an issue in "CallContext.LogicalGetData" method using .net Core when i try to send parameter to runtime text template t4 (net core)
Below tt file :
<## template language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## parameter name="firstName" type="System.String" #>
<## parameter name="lastName" type="System.String" #>
and Cs call method :
var pt1 = new ParamTemplate1();
pt1.Session = new Dictionary<string, object>();
pt1.Session["firstName"] = "David";
pt1.Session["lastName"] = "Giard";
pt1.Initialize();
var outputText1 = pt1.TransformText();
Console.WriteLine(outputText1);
Hello <#=firstName #> <#=lastName #>!
the problem is du to " System.Runtime.Remoting" library is not supported in .net core
Any ideas or workaround ??
Thanks.
Sorry for the late reply, but if you still want to use the t4 templates, you can replace the parameters directives with properties at the end of the t4 document:
<#+
public string FirstName { get; set; }
public string LastName { get; set; }
#>
And then call it:
var pt1 = new ParamTemplate1
{
FirstName = "David",
LastName = "Giard"
};
var outputText1 = pt1.TransformText();
Console.WriteLine(outputText1);

Parsing specific part of XML file

I have a .gpx XML file with the following sample:
<trk>
<name>Test</name>
<trkseg>
<trkpt lon="-84.89032996818423" lat="32.75810896418989">
<ele>225.0</ele>
<time>2011-04-02T11:57:48.000Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:cad>0</gpxtpx:cad>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
</trkseg>
</trk>
I'm using Linq to XML to parse this but I'm having a difficult time parsing the extensions section. Here's the code I'm using:
var gpxDoc = LoadFromStream(document);
var gpx = GetGpxNameSpace();
var gpxtpx = XNamespace.Get("gpxtpx");
var tracks = from track in gpxDoc.Descendants(gpx + "trk")
select new
{
Name = DefaultStringValue(track, gpx, "name"),
Description = DefaultStringValue(track, gpx, "desc"),
Segments = (from trkSegment in track.Descendants(gpx + "trkseg")
select new
{
TrackSegment = trkSegment,
Points = (from trackpoint in trkSegment.Descendants(gpx + "trkpt")
select new
{
Lat = Double(trackpoint.Attribute("lat").Value),
Lng = Double(trackpoint.Attribute("lon").Value),
Ele = DefaultDoubleValue(trackpoint, gpx, "ele"),
Time = DefaultDateTimeValue(trackpoint, gpx, "time"),
Extensions = (
from ext in trackpoint.Descendants(gpx + "extensions").Descendants(gpxtpx + "TrackPointExtension")
select new
{
Cad = DefaultIntValue(ext, gpxtpx, "cad")
}).SingleOrDefault()
})
})
};
Here's the relevant helper code:
private static double? DefaultIntValue(XContainer element, XNamespace ns, string elementName)
{
var xElement = element.Element(ns + elementName);
return xElement != null ? Convert.ToInt32(xElement.Value) : (int?)null;
}
private XNamespace GetGpxNameSpace()
{
var gpx = XNamespace.Get("http://www.topografix.com/GPX/1/1");
return gpx;
}
The actual error I'm getting is
The following error occurred: Object reference not set to an instance of an object.
and it bombs on this code:
Extensions = (from ext in trackpoint.Descendants(gpx + "extensions").Descendants(gpxtpx + "TrackPointExtension")
select new
{
Cad = DefaultIntValue(ext, gpxtpx, "cad")
}).SingleOrDefault();
I just don't know how to fix it.
Since you never declare the namespace (xmlns:gpxtpx="http://www.topografix.com/GPX/1/1") it is never going to match. The xml fragment you provided is not well formed due to the lack of the namespace.
If the fragment posted is snipped from a larger document, consider switching to XML API's rather than string manipulation. If that is the entirety of the XML you receive from an outside system, add it to a root node which you can declare the schema in:
<root xmlns:gpxtpx="http://www.topografix.com/GPX/1/1">
<!-- put your xml fragment here -->
</root>

c#Linq To XML Same element names

I have a small problem. I have an XML file where all the level 1 Elements have the same Element name and are distinguishable by a Name attribute. I cannot change the XML file.
Here is an example of the XML file:
<XProperty Name="DeviceInfo" FileVersion="1" xmlns="x-schema:XPropertySchema.xml" xmlns:dt="urn:schemas-microsoft-com:datatypes">
<Value dt:dt="i4">34</Value>
<Reserved>0</Reserved>
<XProperty Name="Manufacturer">
</XProperty>
<XProperty Name="ModelName">
<Value>Advantage N-SL</Value>
<Reserved>0</Reserved>
</XProperty>
<XProperty Name="SerialNumber">
<Value>N40000</Value>
<Reserved>0</Reserved>
</XProperty>
Now, what I need, in c# & Linq to XML, is some code that will set the value of Value sub-node of a specific XProperty node. For example, for the XProperty node with Name 'SerialNumber' set the value of the Value sub-node to 'XYZ'.
Second Example:
The value to change could also be a few descendants down (this is variable)
xml:
<XProperty Name="Versions">
<XProperty Name="Parameters" Persistent="yes">
<ShortDescription>Name</ShortDescription>
<LongDescription>Version object</LongDescription>
<Reserved>0</Reserved>
<XProperty Name="Index" Persistent="yes">
<ShortDescription>VersionIndex</ShortDescription>
<Value dt:dt="i4">1</Value>
<Reserved>0</Reserved>
</XProperty>
</XProperty>
</XProperty>
I need to change the value of Index.
The Class that contains what element to change:
public class XmlChanger
{
public string File { get; set; }
public string Key { get; set; }
public XmlChanger ChildKey { get; set; }
public string ChildKeyString { get; set; }
}
So With the same code I need example 1 to work and also example 2.
In some cases the Value sub-node might not exist, in which case I need to add it.
Any idea what the best way to do this is?
ps. I'm having some problem explaining but I hope someone get's what the meaning of it is.
Thanks in advance!
Use something like this:
var doc = XDocument.Parse(xml);
var element = doc.Root.Elements().
Where(x => x.Name.LocalName == "XProperty" &&
x.Attributes().Any(
y => y.Name == "Name" &&
y.Value == "DeviceInfo")
).SingleOrDefault();
if(element != null)
{
// change it
}
else
{
// add a new one
doc.Root.Add(new XElement(doc.Root.Name.Namespace+ "XProperty",
new XAttribute("Name", "DeviceInfo2")));
}
I used this XML definition:
var xml = #"<xml xmlns=""x-schema:XPropertySchema.xml"" xmlns:dt=""urn:schemas-microsoft-com:datatypes"">
<XProperty Name=""DeviceInfo"" FileVersion=""1"">
<Value dt:dt=""i4"">34</Value>
<Reserved>0</Reserved>
</XProperty>
<XProperty Name=""Manufacturer"">
</XProperty>
<XProperty Name=""ModelName"">
<Value>Advantage N-SL</Value>
<Reserved>0</Reserved>
</XProperty>
<XProperty Name=""SerialNumber"">
<Value>N40000</Value>
<Reserved>0</Reserved>
</XProperty>
</xml>";
I created this code in LINQPad and used a string variable to hold the xml file. In this scenario, I need Parse. If you want to load the XML directly from a file, XDocument.Load is the right way
var docx = XDocument.Load("someUri");
var elements = docx.Elements(XName.Get("XProperty")).Where(x => x.Attributes().Count(a => a.Name == XName.Get("Name") && a.Value == "SerialNumber") > 0);
foreach (var e in elements)
{
if (e.Elements().Count(x => x.Name == XName.Get("Value")) == 1)
{
e.Elements().Single(x => x.Name == XName.Get("Value")).Value = "XYZ";
}
else
{
e.Add(new XElement(XName.Get("Value"), "XYZ"));
}
}
I've not made concessions for the Namespace that your XML fragment contains, so you're going to have to add this to the XName.Get() method.

How to create a method that encapsulates the T4 template text section?

Instead of this .tt:
<## template debug="false" hostspecific="true" language="C#" #>
<## import namespace="System.IO" #>
<## output extension=".txt" #>
<## assembly name="System"#>
<# message = "hello world" ; #>
blah blah blah etc. very complex example with embedded expression like
<#=message#>
I'd like to have an output function that would return the output blah blah etc.:
<## template debug="false" hostspecific="true" language="C#" #>
<## import namespace="System.IO" #>
<## output extension=".txt" #>
<## assembly name="System"#>
<#output();#>
<#+ output() { #>
blah blah blah etc. very complex example with embedded expression like
<#=message#>
<#}
#>
Of course the syntax above is not correct. How to do this ?
This is an alternative solution not using class feature blocks <#+ ... #>.
Using a lambda expression inside usual statement blocks <# ... #> allows defining a local function as follows:
<## template language="C#" #>
<## output extension=".txt" #>
<# Action output = () => { #>
loooooooong text <#= "message" #>
<# }; #>
<# output(); #>
This template produces the output below:
loooooooong text message
Actually you're very close with what you've got there.
I find it helps to remember that the template is essentially a C#/VB class under the hood, so when you use a <#+ #> block, you're really just adding a member to the class.
Once you've started using the <#+ #> notation, you have to keep using it, as you're still adding stuff to the class at the member level, not adding the the TransformText() method which regular <# #> tags do.
The correct syntax would be
<#+ public void output() { #>
blah blah blah etc. very complex example with embedded expression like <#=message#>
<#+ }
#>

Categories