确定数据库是否与DacPackage“相等”

问题描述 投票:2回答:3

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

c# sql-server sql-server-data-tools
3个回答
2
投票

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

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

.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>节点都会更改。

所以让我们开始吧。计算达帕奇的指纹。

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
投票

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

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

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


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);
}
© www.soinside.com 2019 - 2024. All rights reserved.