确定数据库是否 "Equal" 到 DacPackage

Determine if a Database is "Equal" to a DacPackage

有没有办法使用the SQL Server 2012 Microsoft.SqlServer.Dac Namespace to determine if a database has an identical schema to that described by a DacPackage object? I've looked at the API docs for DacPackage as well as DacServices,但是运气不好;我错过了什么吗?

这是我的想法,但我并不是很喜欢它。如果有人能指出任何错误、边缘情况或更好的方法,我将非常感激。

...
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);
}
...

我真正不喜欢这种方法的地方在于它完全依赖于生成的部署脚本的格式,因此非常脆弱。欢迎提问、评论和建议。

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

  1. 计算 dacpac 的指纹。
  2. 将该指纹存储在目标数据库中。

The .dacpac is just a zip file containing goodies like metadata, and model information.

这是您将在 .dacpac 中找到的内容的屏幕截图:

文件 model.xml 的结构 XML 如下所示

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

What we need to do is extract the contents from <Model>...</Model> and treat this as the fingerprint of the schema.

"But wait!"你说。 "Origin.xml has the following nodes:"

<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
    }
}

@Aaron Hudon 的回答不考虑 post 脚本更改。有时您只是向类型 table 添加一个新条目而不更改模型。在我们的例子中,我们希望这算作新的 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);
}