【问题标题】:C#: SMO problems with dependencies / Multipe Create Table statementsC#:依赖关系的 SMO 问题/Multipe Create Table 语句
【发布时间】:2018-08-31 12:32:56
【问题描述】:

我正在尝试使用 SMO 从现有数据库生成 SQL 脚本。 稍后将使用此脚本来还原没有数据的数据库。 为此,我设置了一个如下所示的方法:

public void GenerateSQLScripts(string dbName)
    {

        StringBuilder sb = new StringBuilder();
        Server server = new Server(SqlServer);
        Database db = server.Databases[dbName];


        var scriptopt = new ScriptingOptions();
        scriptopt.TargetServerVersion = SqlServerVersion.Version105; // Windows 2008 R2
        scriptopt.AnsiPadding = true;
        scriptopt.WithDependencies = true;
        scriptopt.IncludeHeaders = true;
        scriptopt.SchemaQualify = true;
        scriptopt.ExtendedProperties = true;
        scriptopt.TargetDatabaseEngineType = DatabaseEngineType.Standalone;
        scriptopt.IncludeDatabaseContext = true;
        scriptopt.ScriptDrops = false;
        scriptopt.ScriptData = false;
        scriptopt.ScriptSchema = true;
        scriptopt.DriAllConstraints = true;
        scriptopt.DriForeignKeys = true;
        scriptopt.Indexes = true;
        scriptopt.DriPrimaryKey = true;
        scriptopt.DriUniqueKeys = true;
        scriptopt.DriChecks = true;
        scriptopt.AllowSystemObjects = false;
        scriptopt.AppendToFile = false;
        scriptopt.ScriptBatchTerminator = true;


        // script Tables
        foreach (Table t in db.Tables)
        {
            if (!t.IsSystemObject)
            {
                StringCollection sc = t.Script(scriptopt);
                foreach (string s in sc)
                {
                    sb.AppendLine(s);
                }
            }

        }

        //Script Stored Procedures
        foreach (StoredProcedure sp in db.StoredProcedures)
        {
            if (!sp.IsSystemObject)
            {
                var sc = sp.Script(scriptopt);
                foreach (string s in sc)
                {
                    sb.AppendLine(s);
                }
            } 

        }

        //Views
        foreach(View v in db.Views){
            if (!v.IsSystemObject)
            {
                StringCollection sc = v.Script(scriptopt);
                foreach (string s in sc)
                {
                    sb.AppendLine(s);
                }
            }

        }
        File.WriteAllText(Path, sb.ToString());

    } 

此方法创建一个 SQL 文件,其中包含针对同一表的多个 Create Table 语句。每次引用表时都会发生这种情况,无论它是否存在。 我通过关闭 WithDependencies 找到了解决此问题的方法:

scriptopt.WithDependencies = false;

现在脚本要小得多,因为没有额外的语句。但是由于我的语句中没有顺序,所以有些表引用了当时不存在的其他表。 经过一番研究,我找到了DependencyWalker这个类。

我更改了我的代码以使用此类并对语句的顺序进行排序以恢复我的数据库:

        UrnCollection ucol = new UrnCollection();
        foreach(Table t in db.Tables){
            ucol.Add(t.Urn);
        }

        DependencyWalker dw = new DependencyWalker(server);

        DependencyTree dtree = dw.DiscoverDependencies(ucol.ToArray(), DependencyType.Parents);

        DependencyCollection dcol = dw.WalkDependencies(dtree);

// ScriptingOptions

        Scripter scripter = new Scripter(server);
        foreach (Table t in db.Tables)
        {

            if (!t.IsSystemObject)
            {
                scripter.Options = scriptopt;
                scripter.Options.WithDependencies = false;
                IEnumerable<string> tscripts = scripter.EnumScriptWithList(dcol);
                foreach (string s in tscripts)
                {
                    sb.AppendLine(s);
                }
            }
        }

由于测试目的,源代码的其余部分不受影响。 现在我得到了和以前一样的错误。我的脚本充满了多个 Create Table 语句,即使该选项已关闭,也会导致 Table already exists 错误。

【问题讨论】:

    标签: c# sql-server smo


    【解决方案1】:

    经过一番研究,我找到了解决问题的方法。 起初我将DriForeignKeys 的值更改为false。 这会导致暂时将外键排除在脚本之外。在创建表的脚本时,我使用 ForeignKeyCollection 提取它们的外键:

    List<ForeignKeyCollection> fkcolList = new List<ForeignKeyCollection>();
    foreach (Table t in db.Tables)
            {
                fkcolList.Add(t.ForeignKeys); // Extract the foreign keys
                if (!t.IsSystemObject)
                {
                    StringCollection sc = t.Script(scriptopt);
                    foreach (string s in sc)
                    {
                        sb.AppendLine(s);
                    }
                }
            }
    

    现在我可以单独编写外键脚本并将其附加到脚本末尾:

     foreach (ForeignKeyCollection fkcol in fkcolList) // Generate Relations
            {
                foreach (ForeignKey fk in fkcol)
                {
                    StringCollection sc = fk.Script();
                    foreach (string s in sc)
                    {
                        sb.AppendLine(s);
                    }
                }
            }
            fkcolList.Clear();
    

    这可能不是最好的解决方案,因为连续三个循环,但对我来说它工作得很好。

    【讨论】:

      【解决方案2】:

      Smo 是一大笑点,不是吗?你会发现依赖遍历器对每个表产生了明显的冗余引用......因为多个关系......和级联关系。

      帮自己一个忙,输出依赖walker的树的结果(做你自己的walking)......所以你可以看到这是真的。这就是我所做的只是为了“看看”我得到了什么。请注意,缩进说明表被多次提及...当您调用 WalkDependencies 时,这些表只会被展平为“线性列表”...这对您没有任何好处。

      class Program
      {
        static void Main( string[ ] args )
        {
          using ( var connection = new SqlConnection( "Data Source=.;Initial Catalog=...;Integrated Security=True" ) )
          {
            connection.Open( );
            var serverConnection = new ServerConnection( connection );
            var server = new Server( serverConnection );
            var db = server.Databases[ "..." ];
            var objects = new UrnCollection( );
            foreach ( Table table in db.Tables )
            {
              objects.Add( table.Urn );
            }
            var dependency = new DependencyWalker( server );
            var tree = dependency.DiscoverDependencies( objects, DependencyType.Parents );
            Walk( tree.FirstChild );
          }
        }
      
        static void Walk( DependencyTreeNode node, int depth = 0 )
        {
          Print( node.Urn, depth );
          if ( node.HasChildNodes )
          {
            Walk( node.FirstChild, depth + 1 );
          }
          if ( node.NextSibling != null )
          {
            Walk( node.NextSibling, depth );
          }
        }
      
        static void Print( string message, int depth )
        {
          var space = string.Empty;
          for ( int i = 0; i < depth; i++ ) space += " ";
          Debug.WriteLine( string.Format( "{0}{1}", space, message ) );
        }
      }
      

      我在一个小型规范化数据库上运行上述代码,并使用一组名义上的外键。正是这些外键导致了给定表的多次提及。

      这是部分编辑的输出:

      Server[@Name='...']/Database[@Name='...']/Table[@Name='AddressTypes' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='ApplicationComponents' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/UserDefinedFunction[@Name='GetApplicationElementExtension' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='ApplicationElements' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/UserDefinedFunction[@Name='GetApplicationElementName' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='ApplicationElements' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/UserDefinedFunction[@Name='GetArchitecture' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/UserDefinedFunction[@Name='GetArchitectureName' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/UserDefinedFunction[@Name='GetSetting' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Settings' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='ApplicationElements' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='ApplicationElements' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='DataFiles' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='FileSequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='DirectoryEntries' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Lists' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='ListSequence' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Names' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='NameSequence' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='SecurityIds' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='SecuritySequence' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='DirectoryEntrySequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Images' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Snapshots' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Machines' and @Schema='dbo']
         Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='SnapshotSequence' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Progress' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Progress' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='KeyTypes' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='ListItems' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Streams' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='DataFiles' and @Schema='dbo']
         Server[@Name='...']/Database[@Name='...']/Sequence[@Name='FileSequence' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Lists' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='ListSequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Lists' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='ListSequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='MachineAddresses' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Machines' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='AddressTypes' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='MachineKeys' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Machines' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='MachineKeySequence' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='KeyTypes' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Machines' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Names' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='NameSequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Progress' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='SecurityIds' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='SecuritySequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Settings' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='SnapshotDirectoryEntries' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Snapshots' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Machines' and @Schema='dbo']
         Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='SnapshotSequence' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Progress' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='DirectoryEntries' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Lists' and @Schema='dbo']
         Server[@Name='...']/Database[@Name='...']/Sequence[@Name='ListSequence' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Names' and @Schema='dbo']
         Server[@Name='...']/Database[@Name='...']/Sequence[@Name='NameSequence' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='SecurityIds' and @Schema='dbo']
         Server[@Name='...']/Database[@Name='...']/Sequence[@Name='SecuritySequence' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='DirectoryEntrySequence' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Snapshots' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Machines' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Table[@Name='Architectures' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Sequence[@Name='SnapshotSequence' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='Progress' and @Schema='dbo']
      Server[@Name='...']/Database[@Name='...']/Table[@Name='Streams' and @Schema='dbo']
       Server[@Name='...']/Database[@Name='...']/Table[@Name='DataFiles' and @Schema='dbo']
        Server[@Name='...']/Database[@Name='...']/Sequence[@Name='FileSequence' and @Schema='dbo']
      

      快速扫描发现某些表被引用了六七次。

      在好转之前会变得更糟。你最终要做的就是找出独立的东西......可以首先编写脚本的东西。这些东西没有依赖关系。然后,将这些独立的项目放在一个列表中并一遍又一遍地运行依赖项步行器,将内容添加到您的列表中......其中项目仅依赖于您已经添加的项目。因此,您将拥有可编写脚本的订单。

      请注意,就地数据库在技术上可能仍然无法以这种方式解决。表 a 可以具有表 b 的引用,而表 b 具有表 a 的引用。这是完全合法的(即使不受欢迎)。

      我更喜欢用 Visual Studio 来做艰苦的工作:

      1. here 添加适用于 Visual Studio 的 SQL Server Data Tools
      2. 创建一个数据库项目。
      3. 导入现有数据库。
      4. Build+Publish 菜单生成脚本。

      它可以做一些神奇的事情......比如创建对象,然后稍后修改它们以处理 a->b->a 类型的事情。您还可以发布到现有数据库,它会仔细生成仅进行更改的脚本。太棒了。

      【讨论】:

      • 感谢您的详细回答。我知道树包含有关依赖项的所有冗余信息。这就是为什么我关闭了“WithDependencies”选项,希望它只是编写没有依赖关系的表。我以前读过有关 SQL SDT 的文章,但它似乎不是适合我的解决方案,因为我的工具除了脚本之外还有其他任务。有没有办法以编程方式使用 SDT?
      • 我不知道...我认为您最终可以在 SMO 上取得成功...只是需要花很多时间才能看到您真正得到了什么。也许我没有看到全貌......但从你的最后一个代码看起来你正在遍历所有表格......然后在每个表格上,你正在编写 dcol 中的所有内容。 .这本身就是......多余的“一切”。对不起,如果我错过了重点。
      • 你说得对,这就是我想要做的。我想我可以使用类 ScriptingOptions 来过滤冗余信息。它在没有 DependencyWalker 的情况下进行了第一次尝试。但似乎 Scripter 类忽略了脚本选项。
      【解决方案3】:

      您可能不需要手动遍历任何内容。只需将脚本程序配置为包含您想要的类型(表、存储过程、所有者,甚至数据)并运行它,它就会处理依赖关系(至少它为我生成了一个 DataOnly 脚本):

      var server = new Server(@".\SQLExpress");
      var database = server.Databases["mydb"];
      var scripter = new Scripter(server);
      //scripter.Options.WithDependencies = true; //didn't even need this option
      scripter.Options.ScriptData = true;
      scripter.Options.ScriptSchema = false;
      
      var tables = database.Tables.Cast<Table>().Where(t => !t.IsSystemObject).ToList();
      var scripts = scripter.EnumScriptWithList(tables.Select(t => t.Urn).ToArray());
      
      return string.Join("\n", scripts.Select(s => s));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-12
        • 2018-06-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-21
        • 1970-01-01
        • 2018-07-05
        相关资源
        最近更新 更多