3

我正在尝试构建一个生成数据库模型和数据库上下文的项目模板,而不将连接信息存储在源代码中。

我已经成功地将项目模板向导与服务器资源管理器连接起来,并且可以在 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
    }
}
4

1 回答 1

0

没有现成的解决方案来解决现在使用 Visual Studio 获取连接管理器的问题。解决方案在于构建一个自定义模板生成器,该生成器将知道如何与 Visual Studio 扩展进行通信。这本身并不是一件容易的事。

好的,我已经为此制定了答案,但首先是 settings.ttinclude,从项目模板/with 向导输出

<#@ template language="C#" #>
<#@ assembly name="EnvDTE" #>
<#@ 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="EnvDTE" #>
<#+
public class Settings
{
    const string connectionKey = @"home-prime\localdb#7fe04ab3.DbSubSonic.dbo";

    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 IDataConnection Connection
    {
        get
        {
            if (Host is IServiceProvider service)
            {
                if (service.GetService(typeof(ISubSonicCoreService)) is ISubSonicCoreService subsonic)
                {
                    return subsonic.ConnectionManager[connectionKey];
                }
                throw new InvalidOperationException("Unable to resolve ISubSonicCoreService!");
            }
            throw new Exception("Host property returned unexpected value (null)");
        }
    }
}
#>

其次是 t4 生成的类文件,这是一个概念证明,将来实际的连接字符串将永远不会出现在生成的代码中。

using Microsoft.Extensions.DependencyInjection;
using SubSonic;
using System;

namespace TemplateIntegrationTest.DAL
{
    public partial class DataContext1
        : SubSonicContext
    {
        private readonly IServiceCollection services = null;

        public DataContext1(IServiceCollection services)
        {
            this.services = services ?? throw new ArgumentNullException(nameof(services));
        }

        public string ConnectionString => @"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=DbSubSonic;Integrated Security=True";

        #region ISubSonicSetCollection{TEntity} Collection Properties
        #endregion
    }
}

覆盖模板服务 ProcessTemplate 方法

public string ProcessTemplate(string inputFile, string content, ITextTemplatingCallback callback = null, object hierarchy = null)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            string result = "";

            if (this is ITextTemplatingComponents SubSonicComponents)
            {
                SubSonicComponents.Hierarchy = hierarchy;
                SubSonicComponents.Callback = callback;
                SubSonicComponents.InputFile = inputFile;

                SubSonicComponents.Host.SetFileExtension(SearchForLanguage(content, "C#") ? ".cs" : ".vb");

                result = SubSonicComponents.Engine.ProcessTemplate(content, SubSonicComponents.Host);

                // TextTemplatingService which is private and can not be replicated with out implementing from scratch.

                // SqmFacade is a DTE wrapper that can send additional commands to VS
                if (SearchForLanguage(content, "C#"))
                {
                    SqmFacade.T4PreprocessTemplateCS();
                }
                else if (SearchForLanguage(content, "VB"))
                {
                    SqmFacade.T4PreprocessTemplateVB();
                }
            }

            return result;
        }

为了实现这一点,我必须执行以下操作:

  • 在 Visual Studio 中构建一个使用包装的 STextTemplating 服务的模板生成器。
  • 包装的服务必须覆盖 ITextTemplatingEngineHost
  • 覆盖 ProcessTemplate 方法,vs 服务可以实现接口,但它以主机无法覆盖的方式实现。
  • 最后,将我构建的 dll 库放在 GAC 中。显然,T4 引擎仍然存在一个错误,忽略了代码库位置被忽略的事实,它在其他地方查找库但没有找到它们。
于 2020-07-24T03:28:05.127 回答