Sql server 如何通过Azure CI/CD部署SQL脚本?

Sql server 如何通过Azure CI/CD部署SQL脚本?,sql-server,azure-devops,Sql Server,Azure Devops,我们有很多SQL脚本(.SQL文件),我们正在从一台服务器手动部署到另一台服务器 所有SQL脚本都通过Azure中的SQL项目进行管理 如何通过Azure CI/CD部署SQL脚本?管理数据库架构以及如何在您的环境中提升它们,应同等考虑应用程序和基础架构的部署。这里有一些高层次的指导方针 源代码管理-您需要将脚本置于源代码管理中。如果您使用的是Azure DevOps,请使用Git存储库来存储生成的所有脚本。此外,我们在源代码管理中需要它们,这样我们的CI/CD工具就可以使用它们。如果你是DBA

我们有很多SQL脚本(.SQL文件),我们正在从一台服务器手动部署到另一台服务器

所有SQL脚本都通过Azure中的SQL项目进行管理


如何通过Azure CI/CD部署SQL脚本?

管理数据库架构以及如何在您的环境中提升它们,应同等考虑应用程序和基础架构的部署。这里有一些高层次的指导方针

  • 源代码管理-您需要将脚本置于源代码管理中。如果您使用的是Azure DevOps,请使用Git存储库来存储生成的所有脚本。此外,我们在源代码管理中需要它们,这样我们的CI/CD工具就可以使用它们。如果你是DBA,而且这听起来很奇怪,很抱歉,你必须习惯它

    1b启用拉取请求——将源存储库配置为仅接受拉取请求中的更改。这可以确保在将模式更改接受到存储库中之前对其进行审查。改进团队的知识共享,提高整体质量,因为它可以在部署错误之前发现错误

  • 安全性-锁定数据库,使随机用户无法部署随机更改。创建用于应用数据库架构更改的专用帐户,并仅向持续交付工具提供凭据。在这个模型下,如果它不在源代码管理中,它就不存在。我们的CI/CD工具将负责为我们部署这些更改

  • 使用工具-并停止手动操作!我们的团队决定使用一个名为的开源框架来管理对数据库模式的更改。我们选择db migrate是因为它是开源的,可以在许多不同的平台上工作。Microsoft使用数据库迁移所基于的

    迁移工作方式:基本上,每次需要修改数据库时,都会创建一个“迁移”,其中包括SQL脚本更改。当该工具针对您的数据库运行时,它会在数据库中创建一个表来跟踪以前运行过的迁移,因此它只运行任何新的迁移。简而言之,迁移应该是非破坏性的,以防止数据丢失,并且脚本一旦应用于任何数据库,就应该被认为是只读的。(在使用迁移sql脚本后,不应更改该脚本;应创建新的迁移)

  • 持续集成-每当将新迁移签入源代码管理时,您的CI服务器都会将脚本打包为下一阶段的工件

  • 连续交付-连续交付系统接受构建工件,并针对每个目标环境运行db迁移工具(node.js)。CD工具使用一个专用的SQL用户帐户,允许对数据库架构进行更改。如#2所示,这应该是部署更改的唯一方式

  • 更新:使用实体框架

    从上面列出的方法可以看出,我显然非常有兴趣将数据库模式更改限制在特定的用户帐户内,以防止意外地使用具有无限制访问权限的用户帐户部署应用程序。您的里程数可能会有所不同,但您可以使用以下几种方法:

  • 用于执行迁移-此工具随EntityFramework提供,可以作为部署步骤调用代码优先迁移。这与我在上面使用db migrate的方式非常相似

  • 在应用程序启动期间为迁移编写脚本-您可以在web应用程序初始化逻辑中编写代码以执行迁移。这可能是最容易实现的,但需要使用具有无限制访问权限的数据库帐户运行应用程序。此外,如果数据库迁移出现问题,则在部署应用程序之前,您不会知道。从CI/CD的角度来看,我希望在完全破坏站点之前,部署失败并可能回滚

  • 配置发布配置文件-您可以将发布配置文件配置为“更新数据库”。这有效地将databaseInitializer添加到web.config,以便在应用程序启动时更新数据库。这也有类似的缺点,但至少允许您使用不同的用户帐户来应用数据库更改

  • 请注意,完全可以将存储过程作为资源嵌入到迁移中,然后从迁移中调用原始sql语句:

    基本迁移:

    /// <summary>
    /// Custom DbMigration with helper methods
    /// </summary>
    public abstract class BaseDbMigration : DbMigration
    {
        /// <summary>
        /// Apply a SQL statement stored in an embedded resource
        /// </summary>
        /// <param name="resourceName"></param>
        protected void SqlFromEmbeddedResource(string resourceName)
        {
            Assembly assembly = typeof(BaseDbMigration).Assembly;
    
            string baseNamespace = typeof(BaseDbMigration).Namespace;
    
            resourceName = baseNamespace + "." + resourceName;
    
            bool exists = assembly.GetManifestResourceNames().Where(r => r == resourceName).SingleOrDefault() != null;
    
            if (exists)
            {
                string sql = null;
    
                using (var stream = assembly.GetManifestResourceStream(resourceName))
                {
                    var reader = new StreamReader(stream);
                    sql = reader.ReadToEnd();
                }
    
                base.Sql(sql);
            }
        }
    }
    
    /// <summary>
    /// Migration: Deploy Stored Proc
    /// </summary>
    public partial class CalculateTotalsV1 : BaseDbMigration
    {
        /// <summary>
        /// </summary>
        public override void Up()
        {
            base.SqlFromEmbeddedResource("sp_CalculateTotals.v1.Up.sql");
        }
    
        /// <summary>
        /// </summary>
        public override void Down()
        {
            base.SqlFromEmbeddedResource("sp_CalculateTotals.v1.Down.sql");
        }
    }
    
    //
    ///使用helper方法自定义DbMigration
    /// 
    基于公共抽象类的DbMigration:DbMigration
    {
    /// 
    ///应用存储在嵌入式资源中的SQL语句
    /// 
    /// 
    受保护的无效SqlFromEmbeddedResource(字符串resourceName)
    {
    Assembly Assembly=typeof(BaseDbMigration).Assembly;
    字符串baseNamespace=typeof(BaseDbMigration).Namespace;
    resourceName=baseNamespace+“+”resourceName;
    bool exists=assembly.GetManifestResourceNames()。其中(r=>r==resourceName)。SingleOrDefault()!=null;
    如果(存在)
    {
    字符串sql=null;
    使用(var stream=assembly.GetManifestResourceStream(resourceName))
    {
    变量读取器=新的流读取器(流);
    sql=reader.ReadToEnd();
    }
    Sql(Sql);
    }
    }
    }
    
    迁移示例:

    /// <summary>
    /// Custom DbMigration with helper methods
    /// </summary>
    public abstract class BaseDbMigration : DbMigration
    {
        /// <summary>
        /// Apply a SQL statement stored in an embedded resource
        /// </summary>
        /// <param name="resourceName"></param>
        protected void SqlFromEmbeddedResource(string resourceName)
        {
            Assembly assembly = typeof(BaseDbMigration).Assembly;
    
            string baseNamespace = typeof(BaseDbMigration).Namespace;
    
            resourceName = baseNamespace + "." + resourceName;
    
            bool exists = assembly.GetManifestResourceNames().Where(r => r == resourceName).SingleOrDefault() != null;
    
            if (exists)
            {
                string sql = null;
    
                using (var stream = assembly.GetManifestResourceStream(resourceName))
                {
                    var reader = new StreamReader(stream);
                    sql = reader.ReadToEnd();
                }
    
                base.Sql(sql);
            }
        }
    }
    
    /// <summary>
    /// Migration: Deploy Stored Proc
    /// </summary>
    public partial class CalculateTotalsV1 : BaseDbMigration
    {
        /// <summary>
        /// </summary>
        public override void Up()
        {
            base.SqlFromEmbeddedResource("sp_CalculateTotals.v1.Up.sql");
        }
    
        /// <summary>
        /// </summary>
        public override void Down()
        {
            base.SqlFromEmbeddedResource("sp_CalculateTotals.v1.Down.sql");
        }
    }
    
    //
    ///迁移:部署存储过程
    /// 
    公共部分类CalculateTotalsV1:BaseDbMigration
    {
    /// 
    /// 
    公共覆盖作废()
    {
    base.SqlFromEmbeddedResource(“sp_CalculateTotals.v1.Up.sql”);
    }
    /// 
    /// 
    公共覆盖无效向下()
    {
    base.sqlfromEmbeddedResource