假设我有一个包含 2 列的表格:
column1
和 column2
。
现在,我根据表格创建一个函数,如下所示:
CREATE OR ALTER Function [dbo].[Fn_Test] ()
RETURNS TABLE
AS
RETURN
SELECT *
FROM Table1
如果稍后我向表中添加第三列或删除其中一列,我的函数不会更新,我必须再次更改我的函数。如何一次性更新所有函数,或者有没有办法在表发生更改后更新与该表相关的函数?
如果稍后我们向表中添加第三列或删除其中一列,我的函数不会更新,我必须再次更改我的函数。
是的。
如何一次性更新所有函数,或者有没有办法在表更改后更新与该表相关的函数?
您需要携带自己的代码生成工具,最好是在 SSDT (
*.sqlproj
) 项目中,因为类似的事情应该受到源代码控制,并且相关的是:没有人应该制作任何临时的工具对生产/实时数据库中的数据库对象(如过程、函数、视图等)进行编辑,最后因为通过使用 SSDT(或类似的工具,如 SQLAlchemy)意味着您可以使用 T4 之类的东西自动生成 SELECT <column-list>
(对于视图/函数/过程)直接来自您的 CREATE TABLE
定义。
...但是这一切都不是自动的,也不是由 MSSQL Server 为您管理的。当您使用 SSDT 时,您的更改将在单独的发布过程中或通过架构比较功能应用。
在您的情况下,您希望
FUNCTION
与 TABLE
保持同步,那么您需要这样的东西:
MyFunction.sql.tt(这是一个T4模板)
在 T4 模板的
<#+
(模板 class
定义块)中,您可以使用 EnvDTE 和/或 SqlCodeAnalysisRule
来获取 CREATE TABLE
定义并重新生成函数定义:
这是不久前的一篇文章,但我认为仍然相关,它使用 EnvDTE 来获取
CREATE TABLE
定义,我将其用作下面 T4 模板的基础。 另请参阅此质量检查。
注意: 我还没有更新 2014 年文章中的 Visual Studio COM 互操作参考,因此它们对于今天的 VS2022 来说已经过时了,但修复这个问题很简单,所以留作练习致读者:
<#@ template language="C#" debug="true" hostspecific="true" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ assembly name="VSLangProj" #>
<#@ assembly name="C:\Program Files (x86)\Microsoft SQL Server\120\SDK\Assemblies\Microsoft.SqlServer.TransactSql.ScriptDom.dll" #>
<#@ assembly name="C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120\Microsoft.SqlServer.Dac.dll" #>
<#@ assembly name="C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120\Microsoft.SqlServer.Dac.Extensions.dll" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="Microsoft.SqlServer.Dac" #>
<#@ import namespace="Microsoft.SqlServer.Dac.Model" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".sql" #>
<#
using TSqlModel model = this.CreateTSqlModel();
TSqlTable myTable = RequireTable( model, "MyTable" );
#>
CREATE FUNCTION dbo.MyFunction() RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
SELECT
<# foreach( Column c in myTable.Columns ) { /* See https://learn.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.dac.model.column?view=sql-dacfx-162 */ #>
t.<#= c.Name #>,
<# } // foreach Column #>
FROM
dbo.MyTable;
END;
<#+
public TSqlModel CreateTSqlModel()
{
IServiceProvider hostServiceProvider = (IServiceProvider)this.Host;
DTE dte = (DTE)hostServiceProvider.GetService( typeof(DTE) );
TSqlModel model = new TSqlModel(SqlServerVersion.Sql110, new TSqlModelOptions { });
IReadOnlyList<ProjectItem> tableItems = dte.Solution.Cast<Project>()
.SelectMany( p => p.ProjectItems )
.Where( i => i.Name.EndsWith( ".sql", StringComparison.OrdinalIgnoreCase ) )
.Where( i => i.FileCount == 1 && i.FileNames[0].Contains( "Tables" ) ) )
.ToList();
foreach( ProjectItem tableItem in tableItems )
{
String sqlText = File.ReadAllText( tableItem.FileNames[0] );
if( sqlText.Contains( "CREATE TABLE" ) )
{
model.AddObjects( sqlText );
}
}
return model;
}
public static TSqlTable RequireTable( TSqlModel model, String tableName )
{
return model.GetObjects<TSqlTable>( DacQueryScopes.All, ModelSchema.Table )
.Cast<TSqlTable>()
.Where( t => t.Name == tableName )
.Single();
}
#>