【发布时间】:2014-12-16 06:29:12
【问题描述】:
我的程序使用 SQL Server 管理对象 (SMO) 为 Microsoft SQL Server 数据库生成 DDL 脚本。但是,根据服务器和数据库,我收到的表的默认约束输出不一致。有时它们与CREATE TABLE 语句内联,有时它们是独立的ALTER TABLE 语句。我意识到两者都是有效且正确的 SQL 语句,但如果没有一致性,它会阻止多个数据库的输出之间的自动比较,并阻止将输出添加到源代码控制以跟踪数据库模式的更改。 如何确保默认约束的脚本输出的一致性?
示例程序
代码应该是直截了当的。打开服务器和数据库,然后为每个数据库对象生成单独的脚本文件以及一个包含整个数据库脚本的文件。我已经省略了很多错误检查和似乎已经生成一致输出的数据库对象。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using System.Data.SqlClient;
using System.IO;
using System.Configuration;
using System.Runtime.Serialization;
using System.Data;
namespace Stackoverflow.Sample
{
class Program
{
public static void CreateScripts(SqlConnectionStringBuilder source, string destination)
{
Server sv = new Server(source.DataSource);
sv.ConnectionContext.LoginSecure = false;
sv.ConnectionContext.Login = source.UserID;
sv.ConnectionContext.Password = source.Password;
sv.ConnectionContext.ConnectionString = source.ConnectionString;
Database db = sv.Databases[source.InitialCatalog];
ScriptingOptions options = new ScriptingOptions();
options.ScriptData = false;
options.ScriptDrops = false;
options.ScriptSchema = true;
options.EnforceScriptingOptions = true;
options.Indexes = true;
options.IncludeHeaders = true;
options.ClusteredIndexes = true;
options.WithDependencies = false;
options.IncludeHeaders = false;
options.DriAll = true;
StringBuilder sbAll = new StringBuilder();
Dictionary<string, TriggerCollection> tableTriggers = new Dictionary<string, TriggerCollection>();
Dictionary<string, TriggerCollection> viewTriggers = new Dictionary<string, TriggerCollection>();
// Code omitted for Functions
// Tables
foreach (Table table in db.Tables)
{
StringBuilder sbTable = new StringBuilder();
foreach (string line in db.Tables[table.Name].Script(options))
{
sbAll.Append(line + "\r\n");
sbTable.Append(line + "\r\n");
Console.WriteLine(line);
}
// Write file with DDL of individual object
File.WriteAllText(Path.Combine(destination, table.Name + ".sql"), sbTable.ToString());
if (table.Triggers.Count > 0)
tableTriggers.Add(table.Name, table.Triggers);
}
// Code omitted for Views, Stored Procedures, Table Triggers, View Triggers, Database Triggers, etc
// Write file with full DDL of everything above
string[] statements = sbAll.ToString().Split(new string[] { "\r\nGO\r\n" }, StringSplitOptions.RemoveEmptyEntries);
File.WriteAllLines(Path.Combine(destination, "Full.sql"), statements);
}
}
}
内联语句的示例输出
当 SMO 生成带有默认约束的内联语句的脚本时的输出样例。
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
CREATE TABLE [dbo].[Products](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NULL,
[Name_En] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Name_Fr] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Type] [int] NOT NULL CONSTRAINT [DF_Products_Type] DEFAULT ((0)),
[ManagedType] [int] NOT NULL CONSTRAINT [DF_Products_ManagedType] DEFAULT ((0)),
[ProductFamilyID] [bigint] NOT NULL,
[ImplementationID] [bigint] NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[Products] WITH CHECK ADD CONSTRAINT [FK_Products_Implementations] FOREIGN KEY([ImplementationID])
REFERENCES [dbo].[Implementations] ([ID])
ALTER TABLE [dbo].[Products] CHECK CONSTRAINT [FK_Products_Implementations]
ALTER TABLE [dbo].[Products] WITH CHECK ADD CONSTRAINT [FK_Products_ProductFamilies] FOREIGN KEY([ProductFamilyID])
REFERENCES [dbo].[ProductFamilies] ([ID])
ALTER TABLE [dbo].[Products] CHECK CONSTRAINT [FK_Products_ProductFamilies]
独立语句的示例输出
当 SMO 生成带有默认约束的独立语句的脚本时的输出样例。
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
CREATE TABLE [dbo].[Products](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[StartDate] [date] NOT NULL,
[EndDate] [date] NULL,
[Name_En] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Name_Fr] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Type] [int] NOT NULL,
[ManagedType] [int] NOT NULL,
[ProductFamilyID] [bigint] NOT NULL,
[ImplementationID] [bigint] NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[Products] ADD CONSTRAINT [DF_Products_Type] DEFAULT ((0)) FOR [Type]
ALTER TABLE [dbo].[Products] ADD CONSTRAINT [DF_Products_ManagedType] DEFAULT ((0)) FOR [ManagedType]
ALTER TABLE [dbo].[Products] WITH CHECK ADD CONSTRAINT [FK_Products_Implementations] FOREIGN KEY([ImplementationID])
REFERENCES [dbo].[Implementations] ([ID])
ALTER TABLE [dbo].[Products] CHECK CONSTRAINT [FK_Products_Implementations]
ALTER TABLE [dbo].[Products] WITH CHECK ADD CONSTRAINT [FK_Products_ProductFamilies] FOREIGN KEY([ProductFamilyID])
REFERENCES [dbo].[ProductFamilies] ([ID])
ALTER TABLE [dbo].[Products] CHECK CONSTRAINT [FK_Products_ProductFamilies]
似乎从不会在单个数据库中混合使用,但可以在单个服务器上为每个数据库获得不同的输出样式。没有注意到它随着时间的推移而改变数据库,但也许我只是没有尝试在足够长的时间内为数据库生成脚本。我已经将数据库备份并恢复到另一台服务器和不同名称的同一台服务器上,它似乎随机决定选择一种输出样式。因此,当单个数据库恢复可能表现出随机行为时,这似乎不是数据库设置。
目前用于测试的所有服务器都安装了 SQL Server 2012,并且始终在安装了 SQL Server Management Studio 2012 的同一工作站上运行代码。我在 MSDN 上查看了 ScriptingOptions 的属性,但没有发现任何突出的解决方案。
【问题讨论】:
-
内联表单只能在同时添加列的情况下使用。如果稍后应用默认值,则必须由独立表单完成。也许您所看到的只是反映了每个数据库如何最终处于当前状态的现实。
-
我不是在说恶作剧,@Damien_The_Unbeliever,但是元数据(即 sys.default_constraints)中的差异如何体现?也就是说,该工具如何知道差异并采取相应措施?
-
@Damien_The_Unbeliever 有趣的想法。我和 Ben 有同样的问题要问你,当前状态的差异将存储在哪里?我在 sys.default_constraints 中检查了 [DF_Products_Type] 和 [DF_Products_ManagedType],在我的问题中生成示例输出的数据库之间的唯一区别是 object_id、parent_object_id、create_date 和 modify_date。我还检查了 sys.tables 中的 [Products],唯一的区别是 object_id、create_date 和 modify_date。在每一行中,create_date 等于 modify_date,并且任何行之间没有 create_date 匹配。
-
在使用“任务 > 生成脚本”时,此问题似乎也会影响 Sql Server Management Studio (ssms)。我猜 ssms 在后台使用 smo,但在网上找不到确认。
标签: c# sql-server smo