在C#中比较两个SQL Server数据库架构

我正在发布我的Windows应用程序的更新版本。 新版本中存在数据库架构更改。 我也不想丢失数据。

所以我采取的方法是在保留数据库的同时替换dll。 为了升级数据库,我计划比较旧数据库的数据库模式并进行必要的更改。

那么如何比较旧数据库结构(模式)与新数据库结构(模式)以及如何检测更改并进行更正。 到目前为止我尝试过的是尝试使用GetSchema方法获取数据库模式。

但由于新模式是预定义模式,因此如何将新模式注入到程序中并与安装旧模式的站点中的现有模式进行比较。

我们使用RedGate的SQL Compare ,但它不是特别便宜。

SQL比较

这使我们可以比较两个数据库的结构,并创建一个SQL脚本来更新其中一个数据库以匹配另一个。

这是一种比较数据库的免费方法。

下面是我敲定的SQL Server脚本,它将数据库的存储过程,视图和表的内容输出到“ 输出”窗口。

你通过调用它来运行它:

 exec [dbo].[ScriptStoredProcedures] 

在我的许多项目中,我将运行此脚本,将文本复制到Visual Studio项目中的文件中,因此我可以签入我们的数据库在特定时间查看的副本。

(是的,您也可以在Visual Studio中拥有数据库项目,但这是另一种方法。)

如果在两个数据库上运行此脚本,则可以比较两个输出以查找差异。

 CREATE PROCEDURE [dbo].[ScriptStoredProcedures] AS BEGIN -- -- Attempt to create a long SQL script, to Drop, then "CREATE PROCEDURE" on all SPs and "CREATE FUNCTION" on all Functions in this database. -- -- You can then run this script on a "target" database, and it'll have the latest Stored Procedures & functions -- created/updated on it. -- -- exec [dbo].[ScriptStoredProcedures] -- SET NOCOUNT ON PRINT '--' PRINT '-- SQL Script, generated by the [ScriptStoredProcedures] Stored Procedure.' PRINT '-- Created on ' + convert(nvarchar, GetDate(), 106) + ' ' + convert(nvarchar, GetDate(), 108) PRINT '--' PRINT '-- This will create/update the Stored Procedures on this database, to bring them up-to-date with the SPs ' PRINT '-- from the database ''' + DB_NAME() + ''' on the server ''' + @@SERVERNAME + '''' PRINT '--' PRINT '--' -- Create a temporary table, where each record contains one line of Stored Procedure/Function script -- (ie If you have a Stored Procedure with 30 lines of script in it, we'll create 30 temporary records -- to store it) CREATE TABLE #tmp ( [inx] INT IDENTITY(1, 1), [text] nvarchar(4000) ) DECLARE @StoredProcedureName NVARCHAR(200) DECLARE @StoredProcedureType NVARCHAR(10) DECLARE @ExecCommand NVARCHAR(200) DECLARE @OneLineOfScript NVARCHAR(4000) -- First, get a list of all Stored Procedures & Functions in this database DECLARE cursorEachStoredProcedure CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR SELECT [name], -- Name of the Stored Procedure or Function [type] -- This will contain "FN" if it's a Function, or "P" if it's a Stored Procedure FROM sysobjects WHERE (OBJECTPROPERTY(id, N'IsProcedure') = 1 OR OBJECTPROPERTY(id, N'IsTableFunction') = 1 OR OBJECTPROPERTY(id, N'IsScalarFunction') = 1 OR OBJECTPROPERTY(id, N'IsView') = 1) AND [name] NOT LIKE 'sp_%' AND [name] NOT LIKE 'fn_%' ORDER BY [type] DESC, -- Sort by Stored Procedures first, then functions [name] -- then show the list of SPs/Functions in name order OPEN cursorEachStoredProcedure FETCH NEXT FROM cursorEachStoredProcedure INTO @StoredProcedureName, @StoredProcedureType -- For each Stored Procedure we've found in our database, create some script to delete the Stored Procedure -- from the target database if it exists, then re-create it. WHILE (@@FETCH_STATUS = 0) BEGIN PRINT '' IF (@StoredProcedureType = 'P') BEGIN PRINT 'PRINT ''Creating stored procedure: ''''' + @StoredProcedureName + '''''''' PRINT '' PRINT 'IF EXISTS(select Name from sysobjects where OBJECTPROPERTY(id, N''IsProcedure'') = 1 AND Name = ''' + @StoredProcedureName + ''')' PRINT 'BEGIN' PRINT ' DROP PROCEDURE [' + @StoredProcedureName + '] ' PRINT 'END' END ELSE IF (@StoredProcedureType = 'V') BEGIN PRINT 'PRINT ''Creating view: ''''' + @StoredProcedureName + '''''''' PRINT '' PRINT 'IF EXISTS(select Name from sysobjects where OBJECTPROPERTY(id, N''IsView'') = 1 AND Name = ''' + @StoredProcedureName + ''')' PRINT 'BEGIN' PRINT ' DROP VIEW [' + @StoredProcedureName + '] ' PRINT 'END' END ELSE BEGIN PRINT 'PRINT ''Creating function: ''''' + @StoredProcedureName + '''''''' PRINT '' PRINT 'IF EXISTS(select Name from sysobjects where (OBJECTPROPERTY(id, N''IsTableFunction'') = 1 OR OBJECTPROPERTY(id, N''IsScalarFunction'') = 1) AND Name = ''' + @StoredProcedureName + ''')' PRINT 'BEGIN' PRINT ' DROP FUNCTION [' + @StoredProcedureName + '] ' PRINT 'END' END PRINT 'GO ' -- Run the "sp_helptext" command, to get the text of this Stored Procedure (one row per *line* of script) -- and store this set of results in a temporary table, so we can step through, line-by-line, and send -- the output to the Messages window. SET @ExecCommand = 'sp_helptext @objname = ''' + @StoredProcedureName + '''' DELETE FROM #tmp INSERT INTO #tmp EXEC(@ExecCommand) -- Step through each line of this Stored Procedure DECLARE cursorEachLineOfScript CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR SELECT [text] FROM #tmp ORDER BY [inx] OPEN cursorEachLineOfScript FETCH NEXT FROM cursorEachLineOfScript INTO @OneLineOfScript WHILE (@@FETCH_STATUS = 0) BEGIN -- For each line of Stored Procedure script, send the text to the Messages window PRINT @OneLineOfScript FETCH NEXT FROM cursorEachLineOfScript INTO @OneLineOfScript END CLOSE cursorEachLineOfScript DEALLOCATE cursorEachLineOfScript PRINT 'GO ' FETCH NEXT FROM cursorEachStoredProcedure INTO @StoredProcedureName, @StoredProcedureType END CLOSE cursorEachStoredProcedure DEALLOCATE cursorEachStoredProcedure DROP TABLE #tmp PRINT 'EXEC [dbo].[spGrantExectoAllStoredProcs]' PRINT 'GO' PRINT '--' PRINT '--' PRINT '-- List of tables (and their fields) in this database' PRINT '--' PRINT '--' PRINT '--' -- First, let's iterate through our list of tables, and find out which fields they contain. DECLARE @tableName nvarchar(200), @fieldName nvarchar(500), @fieldType nvarchar(500), @fieldNullable nvarchar(200) DECLARE cursorTables CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR SELECT st.NAME as 'Table_name' FROM sys.tables st ORDER BY 1 OPEN cursorTables FETCH NEXT FROM cursorTables INTO @tableName WHILE (@@FETCH_STATUS = 0) BEGIN PRINT '-- Table: ' + @tableName DECLARE cursorFields CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR SELECT sc.NAME as 'Field_name', case when t.Name in ('char', 'varchar', 'nvarchar') then t.Name + '(' + cast(sc.max_length/2 as nvarchar) + ')' else case when t.Name in ('numeric') then t.Name + '(' + cast(sc.precision as nvarchar) + ',' + cast(sc.scale as nvarchar) + ')' else t.Name end end as 'Data_type', case when sc.is_nullable=1 then 'null' else 'not null' end as 'Nullable' FROM sys.tables st INNER JOIN sys.columns sc ON st.object_id = sc.object_id INNER JOIN sys.types t ON sc.system_type_id = t.system_type_id WHERE t.Name != 'sysname' AND st.name = @tableName ORDER BY 1, 2 OPEN cursorFields FETCH NEXT FROM cursorFields INTO @fieldName, @fieldType, @fieldNullable WHILE (@@FETCH_STATUS = 0) BEGIN PRINT '-- ' + @fieldName + ' (' + @fieldType + ', ' + @fieldNullable + ')' FETCH NEXT FROM cursorFields INTO @fieldName, @fieldType, @fieldNullable END CLOSE cursorFields DEALLOCATE cursorFields PRINT '--' FETCH NEXT FROM cursorTables INTO @tableName END CLOSE cursorTables DEALLOCATE cursorTables END 

创建数据库迁移脚本并使用Db Up等工具运行它们以跟踪架构更改。 SQL脚本将数据库从版本1迁移到2,2到3等。 架构比较是上一个问题中提到的另一个选项。

  • 修改Customer Tables.sql
  • 更新Settings.sql

两个建议。

  1. 使用Redgate的SQL Comparison SDK(我工作的人)。 这允许您以编程方式访问SQL Compare技术。
  2. 必须具有特定模式版本的特征,您可以检查它以确定它是哪一个? 如果是这样,您可以针对该版本运行相应的脚本以将其带到下一个版本。 您的安装程序只需要包含一系列此类迁移脚本,这些脚本可以连续运行,以使其进入下一个增量版本。 理想情况下,您可以通过扩展属性或版本表中嵌入模式中的版本信息,这些信息会在成功应用迁移脚本后得到更新。