T4模板生成枚举类型

19

我正在考虑创建一个 T4 模板,用于生成我的数据库的枚举。 实质上,我想要与 SubSonic 相同的功能,例如 Linq-to-SQL 或 Entity Framework 4 中的 Product.Columns.ProductId。

非常感谢任何帮助。

2个回答

35

我为自己的需要编写了一个将查找表转换为枚举类型的代码:

将以下代码放入 EnumGenerator.ttinclude 文件中:

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".generated.cs" #>
<#@ Assembly Name="System.Data" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#
    string tableName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
    string path = Path.GetDirectoryName(Host.TemplateFile);
    string columnId = tableName + "ID";
    string columnName = "Name";
    string connectionString = "data source=.;initial catalog=DBName;integrated security=SSPI";
#>
using System;
using System.CodeDom.Compiler;

namespace Services.<#= GetSubNamespace() #>
{
    /// <summary>
    /// <#= tableName #> auto generated enumeration
    /// </summary>
    [GeneratedCode("TextTemplatingFileGenerator", "10")]
    public enum <#= tableName #>
    {
<#
    SqlConnection conn = new SqlConnection(connectionString);
    string command = string.Format("select {0}, {1} from {2} order by {0}", columnId, columnName, tableName);
    SqlCommand comm = new SqlCommand(command, conn);

    conn.Open();

    SqlDataReader reader = comm.ExecuteReader();
    bool loop = reader.Read();

    while(loop)
    {
#>      /// <summary>
        /// <#= reader[columnName] #> configuration setting.
        /// </summary>
        <#= Pascalize(reader[columnName]) #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #>
<#
    }
#>  }
}
<#+
    private string Pascalize(object value)
    {
        Regex rx = new Regex(@"(?:[^a-zA-Z0-9]*)(?<first>[a-zA-Z0-9])(?<reminder>[a-zA-Z0-9]*)(?:[^a-zA-Z0-9]*)");
        return rx.Replace(value.ToString(), m => m.Groups["first"].ToString().ToUpper() + m.Groups["reminder"].ToString().ToLower());
    }

    private string GetSubNamespace()
    {
        Regex rx = new Regex(@"(?:.+Services\s)");
        string path = Path.GetDirectoryName(Host.TemplateFile);
        return rx.Replace(path, string.Empty).Replace("\\", ".");
    }
#>

当你想要生成一个枚举类型时,只需创建一个与数据库表同名的tt文件,比如UserType.tt,并将以下代码放入其中:

<#@ include file="..\..\T4 Templates\EnumGenerator.ttinclude" #>

现在我的博客文章中提供了更加先进的模板。


2
太棒了。我本来想自己做这个,但你帮我省去了麻烦。 - 3Dave
2
@David Lively:谢谢。我在多个项目中使用了这个,所以我想也应该分享给其他人。 - Robert Koritnik
@Danny:你确定你在使用这段代码吗?因为当我运行你的三个值时,我得到了AudioVideoSolutionsTransceiverConverterHeadsetAdaptersAccessories,它们看起来都没问题。我还在RegExHero中检查了相同的值,一切似乎都按预期工作。如果你想的话,你也可以测试一下。 - Robert Koritnik
1
@Danny:或者更好的是使用这个 (?:^|[^a-zA-Z]*)(?<first>[a-zA-Z])(?<reminder>[a-zA-Z0-9]*)(?:[^a-zA-Z0-9]*$)?,它将适用于您提供的所有情况。尽管我不想支持单字母序列,但在末尾有非单词字符的情况下存在错误。因此,我还更新了上面答案中的正则表达式。 - Robert Koritnik
已经修正了大部分数值,但仍有几个例外情况:“ABC 1500D”和“ABC D”都映射到“AbcD”,而不是“AbcD”和“Abc1500d”。 “ABC DE-12”保留“-”,而“ABC 123”、“ABC DEF 123”保留一个空格。 - Danny Varod
显示剩余15条评论

3

Pascalize的替代实现:

private static string Pascalize(object value)
{
    if (value == null)
        return null;

    string result = value.ToString();

    if (string.IsNullOrWhiteSpace(result))
        return null;

    result = new String(result.Select(ch => ((ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z') ? ch : ((ch >= 'A' && ch <= 'Z') ? ((char)((int)ch + (int)'a' - (int)'A')) : '-'))).ToArray());
    string[] words = result.Split(new [] {'-'}, StringSplitOptions.RemoveEmptyEntries);
    words = words.Select(w => ((w[0] >= 'a' && w[0] <= 'z') ? (w.Substring(0, 1).ToUpper() + w.Substring(1)) : w)).ToArray();
    result = words.Aggregate((st1, st2) => st1 + st2);
    if (result[0] >= '0' && result[0] <= '9')
        result = '_' + result;

    return result;
}

如果你得到了空结果,我建议使用"Value" + id作为值。
结果必须与先前的值进行比较,如果由于错误数据或省略字符而创建了重复项,则可以在末尾添加“_”。
例如:
int value = Convert.ToInt32(reader[enumTable.ValueId]);
string name = Pascalize(reader[enumTable.ValueName].ToString());
if (string.IsNullOrWhiteSpace(name))
    name = "Value" + value;

while (result.Values.Where(v => v.Name == name).SingleOrDefault() != null)
    name += '_';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接