确定数据库是否等于"等于"到达DacPackage

时间:2015-02-17 17:11:25

标签: c# sql-server sql-server-data-tools

有没有办法使用the SQL Server 2012 Microsoft.SqlServer.Dac Namespace来确定数据库是否具有与DacPackage object描述的模式相同的模式?我查看了DacPackage的API文档以及DacServices,但没有运气;我错过了什么吗?

3 个答案:

答案 0 :(得分:2)

是的,自2012年以来我一直在使用以下技术而没有问题。

  1. 计算达卡饼的指纹。
  2. 将指纹存储在目标数据库中。
  3.   

    .dacpac只是一个包含元数据等内容的zip文件   模型信息。

    以下是您在.dacpac中找到的内容的屏幕抓取:

    screenshot of dacpac and its content

    文件 model.xml 的XML结构如下

    <DataSchemaModel>
        <Header>
            ... developer specific stuff is in here
        </Header>
        <Model>
            .. database model definition is in here
        </Model>
    </<DataSchemaModel>
    
      

    我们需要做的是从<Model>...</Model>中提取内容   并将其视为架构的指纹

    “但是等等!”你说。 “Origin.xml 具有以下节点:”

    <Checksums>
        <Checksum Uri="/model.xml">EB1B87793DB57B3BB5D4D9826D5566B42FA956EDF711BB96F713D06BA3D309DE</Checksum>
    </Checksums>
    

    根据我的经验,无论模型中的模式更改如何,此<Checksum>节点都会更改。

    让我们开始吧。 计算dacpac的指纹

    using System.IO;
    using System.IO.Packaging;
    using System.Security.Cryptography;
    static string DacPacFingerprint(byte[] dacPacBytes)
    {
        using (var ms = new MemoryStream(dacPacBytes))
        using (var package = ZipPackage.Open(ms))
        {
            var modelFile = package.GetPart(new Uri("/model.xml", UriKind.Relative));
            using (var streamReader = new System.IO.StreamReader(modelFile.GetStream()))
            {
                var xmlDoc = new XmlDocument() { InnerXml = streamReader.ReadToEnd() };
                foreach (XmlNode childNode in xmlDoc.DocumentElement.ChildNodes)
                {
                    if (childNode.Name == "Header")
                    {
                        // skip the Header node as described
                        xmlDoc.DocumentElement.RemoveChild(childNode);
                        break;
                    }
                }
                using (var crypto = new SHA512CryptoServiceProvider())
                {
                    byte[] retVal = crypto.ComputeHash(Encoding.UTF8.GetBytes(xmlDoc.InnerXml));
                    return BitConverter.ToString(retVal).Replace("-", "");// hex string
                }
            }
        }
    }
    

    现在可以使用此指纹,应用dacpac的伪代码可以是:

    void main()
    {
        var dacpacBytes = File.ReadAllBytes("<path-to-dacpac>");
        var dacpacFingerPrint = DacPacFingerprint(dacpacBytes);// see above
        var databaseFingerPrint = Database.GetFingerprint();//however you choose to do this
        if(databaseFingerPrint != dacpacFingerPrint)
        {
            DeployDacpac(...);//however you choose to do this
            Database.SetFingerprint(dacpacFingerPrint);//however you choose to do this
        }
    }
    

答案 1 :(得分:1)

这是我提出的问题,但我并不是真的为此疯狂。如果有人能够指出任何错误,边缘情况或更好的方法,我会非常感激。

...
DacServices dacSvc = new DacServices(connectionString);
string deployScript = dacSvc.GenerateDeployScript(myDacpac, @"aDb", deployOptions);

if (DatabaseEqualsDacPackage(deployScript))
{
  Console.WriteLine("The database and the DacPackage are equal");
}
...
bool DatabaseEqualsDacPackage(string deployScript)
{
  string equalStr = string.Format("GO{0}USE [$(DatabaseName)];{0}{0}{0}GO{0}PRINT N'Update complete.'{0}GO", Environment.NewLine);
  return deployScript.Contains(equalStr);
}
...

我真正不喜欢这种方法的原因是它完全依赖于生成的部署脚本的格式,因此极其脆弱。非常欢迎提出问题,意见和建议。

答案 2 :(得分:1)

@Aaron Hudon的答案并未说明后脚本的更改。有时,您只是将新条目添加到类型表中而不更改模型。在我们的情况下,我们希望将其计为新的dacpac。这是我对他的代码的修改,以说明这一点

private static string DacPacFingerprint(string path)
{
    using (var stream = File.OpenRead(path))
    using (var package = Package.Open(stream))
    {
        var extractors = new IDacPacDataExtractor [] {new ModelExtractor(), new PostScriptExtractor()};
        string content = string.Join("_", extractors.Select(e =>
        {
            var modelFile = package.GetPart(new Uri($"/{e.Filename}", UriKind.Relative));
            using (var streamReader = new StreamReader(modelFile.GetStream()))
            {
                return e.ExtractData(streamReader);
            }
        }));

        using (var crypto = new MD5CryptoServiceProvider())
        {
            byte[] retVal = crypto.ComputeHash(Encoding.UTF8.GetBytes(content));
            return BitConverter.ToString(retVal).Replace("-", "");// hex string
        }
    }
}


private class ModelExtractor : IDacPacDataExtractor
{
    public string Filename { get; } = "model.xml";
    public string ExtractData(StreamReader streamReader)
    {
        var xmlDoc = new XmlDocument() { InnerXml = streamReader.ReadToEnd() };
        foreach (XmlNode childNode in xmlDoc.DocumentElement.ChildNodes)
        {
            if (childNode.Name == "Header")
            {
                // skip the Header node as described
                xmlDoc.DocumentElement.RemoveChild(childNode);
                break;
            }
        }

        return xmlDoc.InnerXml;
    }
}

private class PostScriptExtractor : IDacPacDataExtractor
{
    public string Filename { get; } = "postdeploy.sql";
    public string ExtractData(StreamReader stream)
    {
        return stream.ReadToEnd();
    }
}

private interface IDacPacDataExtractor
{
    string Filename { get; }
    string ExtractData(StreamReader stream);
}