C#:依赖项引起的SMO问题/ Multipe Create Table语句

时间:2018-08-31 12:32:57

标签: c# sql-server smo

我正在尝试使用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错误。

3 个答案:

答案 0 :(得分:1)

Smo充满了笑声,不是吗?您将发现,依赖遍历器显然对每个表产生了冗余引用……由于多个关系……以及级联关系。

帮自己一个忙,输出依赖项遍历树的结果(自己遍历)...这样您就可以看到这是真的。这是我只是为了“看到”我所得到的。请注意,缩进说明了表被多次提及的情况,并且仅当您调用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
  2. 添加用于Visual Studio的SQL Server数据工具
  3. 创建一个数据库项目。
  4. 导入现有数据库。
  5. Build + Publish菜单以生成脚本。

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

答案 1 :(得分: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 :(得分:0)

您可能不需要手动迭代任何内容。只需配置脚本编写器以包括所需的类型(表,存储的procs,所有者,甚至数据)并运行它,它将处理依赖项(至少它对我生成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));