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

8
我正在发布我Windows应用程序的新版本,该版本有DB架构更改。同时,我也不想丢失数据。
因此,我的方法是替换dll文件并保留数据库。为升级数据库,我计划比较旧数据库的数据库架构并进行必要的更改。
那么,如何比较旧版和新版的数据库结构(架构)以及如何检测并纠正更改呢? 我尝试过使用GetSchema方法获取数据库架构。
但是,由于新架构是预定义的,我该如何将新架构注入到程序中,并与旧版所安装的站点上的现有结构进行比较呢?

2
如果这是您的应用程序,为什么不能编写一个脚本来进行必要的更改呢?您应该知道旧格式和新格式。 - Gordon Linoff
1
但是该应用程序已经发布了几次,每次都有一些数据库更改。我们希望将所有这些版本升级到最新版本。因此,我们不知道它是尝试从版本1还是版本2或3等升级,这些版本具有不同的数据库架构。 - Ajay Bhasy
4个回答

5

这里提供一种免费比较数据库的方法。

下面是我编写的 SQL Server 脚本,它可以将数据库中存储过程、视图和表的内容输出到输出窗口。

通过调用以下命令来运行它:

exec [dbo].[ScriptStoredProcedures]

在我的许多项目中,我会运行此脚本,将文本复制到Visual Studio项目中的文件中,以便我可以检查我们的数据库在特定时间的外观副本。

(是的,您也可以在Visual Studio中拥有Database Projects,但这是一种替代方法。)

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

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
    --  (i.e. 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

你好,干得好 :) 我发现一个问题:当与SQLServer用户定义类型的相同基础类型一起使用时,这个INNER JOIN sys.types t ON sc.system_type_id = t.system_type_id会在输出中创建重复的列。看起来应该使用列sys.types.user_type_id,因为它对于标准类型具有与system_type_id相同的内容。 - FrankM

4
我们使用来自RedGate的SQL Compare,但它不是特别便宜。 SQL Compare 这使我们可以比较两个数据库的结构,并创建一个SQL脚本来更新其中一个数据库以匹配另一个。

谢谢,但我需要一个编程解决方案,而且可能是免费的。 - Ajay Bhasy
明白了。看看我的另一个解决方案吧。;-) - Mike Gledhill

2
创建您的数据库迁移脚本,并使用诸如Db Up之类的工具运行它们,以跟踪架构更改。SQL脚本将您的数据库从版本1迁移到2、2迁移到3等。模式比较是另一个在先前问题中提到的选项。
  • 修改Customer Tables.sql
  • 更新Settings.sql

在Visual Studio中有一个内置功能叫做Schema Compare,可以通过在VS2015中选择工具->SQL Server->新建模式比较...来访问。 - Marcos Dimitrio

0

有两个建议。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接