我正在尝试构建一个生成数据库模型和数据库上下文的项目模板,而不将连接信息存储在源代码中。
我已经成功地将项目模板向导与服务器资源管理器连接起来,并且可以在 settings.ttinclude 中设置连接密钥。
问题是我无法从 DTE 解析到 IVsDataExplorerConnectionManager 的接口。
我相信我找错了树,因为这是在 VSIX 项目中获取服务器资源管理器的方法。我希望类似的代码适用于 T4 Visual Studio 模板引擎。
我花了几个小时寻找其他人是否已经做过类似的事情,但我一无所获。任何关于如何在 T4 模板中使用来自服务器资源管理器的连接的想法都将不胜感激。
2020 年 7 月 23 日更新
从那以后,我了解到使用默认自定义工具的 T4 ITextTemplatingEngineHost 不支持使用依赖注入来检索连接管理器。解决方案是实现一个模板文件生成器,它将访问我正在寻找的信息。它也不像实现 EngineHost 服务那么简单。原来 Visual Studio 内部的 TextTemplatingService 可以实现支持文本模板生成器所需的接口。但是,内部服务不使用接口。这使得模板服务非常僵化并且不像我想要的那样健壮。正在进行的解决方案似乎构建了一个新的模板服务,该服务包装了 Visual Studio 服务并覆盖了 TemplatedCodeGenerator 并覆盖了 ProcessTemplate 并替换了包装的服务。
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="$(DevEnvDir)PublicAssemblies\Microsoft.VisualStudio.Data.Services.dll" #>
<#@ assembly name="$(DevEnvDir)PublicAssemblies\Microsoft.VisualStudio.OLE.Interop.dll" #>
<#@ assembly name="$(DevEnvDir)PublicAssemblies\Microsoft.VisualStudio.Shell.15.0.dll" #>
<#@ assembly name="$(DevEnvDir)PublicAssemblies\Microsoft.VisualStudio.Shell.Interop.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Configuration" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Data.Common" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Configuration" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.Shell" #>
<#@ import namespace="Interop = Microsoft.VisualStudio.OLE.Interop" #>
<#@ import namespace="Microsoft.VisualStudio.Data.Services" #>
<#+
public class Settings
{
const string connectionKey = @"$connectionKey$";
readonly Guid connectionExplorerGuid = Guid.Parse("8B6159D9-A634-4549-9EAC-8642744F1042");
public static ITextTemplatingEngineHost Host { get; set; }
public string[] ExcludeTables
{
get
{
return new string[]{
"sysdiagrams",
"BuildVersion",
"aspnet_Applications",
"aspnet_Membership",
"aspnet_Paths",
"aspnet_PersonalizationAllUsers",
"aspnet_PersonalizationPerUser",
"aspnet_Profile",
"aspnet_Roles",
"aspnet_SchemaVersions",
"aspnet_Users",
"aspnet_UsersInRoles",
"aspnet_WebEvent_Events"
};
}
}
public static IVsDataConnection Connection
{
get
{
if (Host is IServiceProvider service)
{
if (service.GetService(typeof(EnvDTE.DTE)) is Interop.IServiceProvider provider)
{
if (PackageUtilities.QueryService<IVsDataExplorerConnectionManager>(provider) is IVsDataExplorerConnectionManager manager)
{
return manager.Connections[connectionKey].Connection;
}
throw new InvalidOperationException("Unable to resolve IVsDataExplorerConnectionManager!");
}
throw new InvalidOperationException("Unable to resolve DTE as Interop.IServiceProvider!");
}
throw new Exception("Host property returned unexpected value (null)");
}
}
}
#>
包含上述内容的测试代码
<#@ template hostspecific="true" language="C#" #>
<#@ include file="Settings.ttinclude" #>
<#
Settings.Host = Host;
#>
using Microsoft.Extensions.DependencyInjection;
using SubSonic;
using System;
namespace $rootnamespace$
{
public partial class $safeitemrootname$
: SubSonicContext
{
private readonly IServiceCollection services = null;
public $safeitemrootname$(IServiceCollection services)
{
this.services = services ?? throw new ArgumentNullException(nameof(services));
}
public string ConnectionString => "<#= Settings.Connection.DisplayConnectionString #>";
#region ISubSonicSetCollection{TEntity} Collection Properties
#endregion
}
}