如何从多个服务器/数据库中找到相等,修改和唯一的SQL对象?

时间:2019-04-09 22:59:35

标签: c# sql sql-server smo ssms-2017

问题摘要:

我们在多个服务器上都有一组数据库,这些数据库都应该具有相同的SQL对象。多年来,我们的开发人员已在各种数据库中添加/修改了对象,以使它们不再匹配。我需要从完全相同的多个服务器上的多个数据库中获取所有SQL对象(表,视图,存储过程,用户定义的函数)的列表。 (稍后获取唯一项列表,然后获取修改项列表)。我当前的解决方案有效,但是速度很慢。我想知道是否有更好的现有替代方法,但找不到。

当前解决方案:

目前,我一直在C#中使用SMO来获取所有对象的骨灰盒并将其编写脚本。当我尝试一次为它们编写1个对象的脚本时,该过程很慢(对服务器的大量调用)。如果我尝试通过将它们的骨灰盒打包到一个数组中来编写脚本,则过程会更快,但是我只是得到了生成的脚本的Enumerable或StringCollection,而没有组织该脚本来自哪个对象,等等。可以做到这一点(我知道现有的工具,例如ApexSQL或Red-Gate,目前尚无法解决)。我目前的解决方案是按名称将它们分组(并按服务器划分),并用较小的按批命名脚本编写脚本。

不好意思,我到处都是尝试不同方法的地方。也许有一种解决方案甚至不需要分析代码。需要注意的两件事:

  1. 我有一个名为SqlObjectInfo的类,该类仅存储有关每个对象的一些基本信息,例如:名称,服务器,数据库,架构,类型,Ur。
  2. items是一个SqlObjectInfoCollection,它是一个类,其中包含SqlObjectInfo列表以及一些从服务器和数据库添加对象的帮助函数。用所有SqlObjectInfo填充此集合很快,因此就不成问题了。
//Create DataTable
var table = new DataTable("Equal Objects");
table.Columns.Add("Name");
table.Columns.Add("Type");

//Create DataRows
int dbCount = items.SqlObjects.GroupBy(obj => obj.Database).Count();
DMP dmp = DiffMatchPatchModule.Default;
var rows = new List<DataRow>();
foreach (IGrouping<string, SqlObjectInfo> nameGroup in items.SqlObjects.GroupBy(obj => obj.Name))
{
    var likeNamedObjs = nameGroup.ToList();
    if (likeNamedObjs.Count != dbCount)
    {
        continue; //object not in all databases
    }

    //Script Objects
    var rawScripts = new List<string>();
    bool scriptingSucceeded = true;
    foreach (IGrouping<Server, SqlObjectInfo> serverGroup in nameGroup.GroupBy(obj => obj.Server))
    {
        Server server = serverGroup.Key;
        Urn[] urns = serverGroup.Select(obj => obj.Urn).ToArray();
        var scripter = new Scripter(server)
        {
            Options = items.ScriptingOptions
        };

        IEnumerable<string> results;
        try
        {
            results = scripter.EnumScript(urns);
        }
        catch (FailedOperationException)
        {
            scriptingSucceeded = false;
            break; //the object is probably encrypted
        }
        rawScripts.AddRange(results);
    }

    if (!scriptingSucceeded)
    {
        continue;
    }

    if (rawScripts.Count % nameGroup.Count() != 0)
    {
        continue;
    }

    var allScripts = new List<string>();
    int stringsPerScript = rawScripts.Count / nameGroup.Count();
    for (int i = 0; i < rawScripts.Count; i += stringsPerScript) //0, 3, 6, 9
    {
        IEnumerable<string> scriptParts = rawScripts.Skip(i).Take(stringsPerScript);
        allScripts.Add(string.Join(Environment.NewLine, scriptParts));
    }

    //Compare Scripts
    bool allEqual = true;
    for (int i = 1; i < allScripts.Count; i++)
    {
        (string lineScript0, string lineScriptCurr, _) = dmp.DiffLinesToChars(allScripts[0], allScripts[i]).ToValueTuple();
        List<Diff> diffs = dmp.DiffMain(lineScript0, lineScriptCurr, false);
        if (!diffs.TrueForAll(diff => diff.Operation.IsEqual))
        {
            allEqual = false;
            break; //scripts not equal
        }
    }

    //If all scripts are equal, create data row for object
    if (allEqual)
    {
        DataRow row = table.NewRow();
        row["Name"] = likeNamedObjs[0].Name;
        row["Type"] = likeNamedObjs[0].Type;
        rows.Add(row);
    }
}

//Add DataRows to DataTable
foreach (DataRow row in rows.OrderBy(r => r["Type"]).ThenBy(r => r["Name"]))
{
    table.Rows.Add(row);
}

//Write DataTable to csv
var builder = new StringBuilder();
builder.AppendLine(string.Join(",", table.Columns.Cast<DataColumn>().Select(col => col.ColumnName)));
foreach (DataRow row in table.Rows)
{
    builder.AppendLine(string.Join(",", row.ItemArray.Select(field => field.ToString())));
}
File.WriteAllText("equalObjects.csv", builder.ToString());

该代码有效。我可以得到所有对象的(名称|类型)的预期结果csv文件,这些文件在多台服务器的所有数据库中完全相同。真是太慢了。我是否采用正确的方法?是否有更好/更现代的解决方案?

1 个答案:

答案 0 :(得分:1)

所有数据库中都有包含对象的表。在sqlserver中,它是sysobj。首先,您需要创建一个主列表。您可以从所有数据库合并此视图,并执行不同的操作。然后将其外部连接到每个数据库sysobj