1

问题总结:

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

当前解决方案:

现在我一直在 C# 中使用 SMO 来获取所有对象的 urns 并编写脚本。当我尝试一次为它们编写 1 个对象时,该过程很慢(大量调用服务器)。如果我尝试通过将它们的骨灰盒打包到一个数组中来编写脚本,这个过程会更快,但我只是得到一个 Enumerable 或 StringCollection 生成的脚本,而没有组织脚本来自哪个对象等。什么是更好的方法接近这个(我知道现有的工具,如 ApexSQL 或 Red-Gate,它们暂时不存在)。我目前的解决方案是按名称对它们进行分组(并按服务器拆分),并在那些较小的按名称批次中编写脚本。

请原谅我当前的代码,我一直在尝试不同的方法。也许有一个甚至不需要分析代码的解决方案。有两点需要注意:

  1. 我有一个名为 SqlObjectInfo 的类,它只存储每个对象的一些基本信息,例如:名称、服务器、数据库、架构、类型、Urn
  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 文件,这些对象在跨多个服务器的所有数据库中完全相同。简直太慢了 我以正确的方式接近这个吗?有更好/更现代的解决方案吗?

4

1 回答 1

1

There are tables in all databases that have objects. In sqlserver it is sysobj. First you need to create a master list. You could union this view from.all dbs and do a distinct. Then outer join that to each db sysobj

于 2019-04-10T01:20:54.833 回答