C #: проблемы SMO с зависимостями / операторы Multipe Create Table - PullRequest
0 голосов
/ 31 августа 2018

Я пытаюсь сгенерировать сценарий SQL из моей существующей базы данных с помощью SMO. Этот скрипт будет использоваться позже для восстановления базы данных без данных. Для этого я настроил метод, который выглядит следующим образом:

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 голосов
/ 11 сентября 2018

После некоторых исследований я нашел решение для моей проблемы. Сначала я изменил значение 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();

Возможно, это не лучшее решение, поскольку в нем три цикла подряд, но для меня это работает просто отлично.

0 голосов
/ 30 марта 2019

Возможно, вам не нужно ничего перебирать вручную. Просто сконфигурируйте сценарий так, чтобы он включал нужные вам типы (таблицы, хранимые процедуры, владельца, даже данные), и запустите его, и он будет обрабатывать зависимости (по крайней мере, это было сделано для меня при создании сценария 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));
0 голосов
/ 31 августа 2018

Смо - это бочка смеха, не так ли? Вы обнаружите, что средство обхода зависимостей выдает явно избыточные ссылки на каждую таблицу ... из-за множества связей ... и каскадных связей.

Сделайте себе одолжение и выведите результаты дерева ходунков с зависимостями (делайте это самостоятельно) ... чтобы вы могли видеть, что это правда. Вот что я сделал, чтобы просто «увидеть», что я получаю. Обратите внимание, что отступы показывают, что таблицы упоминаются несколько раз ... и они сводятся в "линейный список" только тогда, когда вы вызываете 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, которая имеет ссылку на таблицу a. Это совершенно законно (даже если нежелательно).

Что я предпочитаю делать, так это использовать Visual Studio для выполнения тяжелой работы:

  1. Добавить инструменты данных SQL Server для Visual Studio из здесь
  2. Создать проект базы данных.
  3. Импорт существующей базы данных.
  4. Меню Build + Publish для генерации скрипта.

Он может творить магические вещи ... например, создавать объекты, а затем модифицировать их позже, чтобы иметь дело с вещами типа a-> b-> a. Вы также можете опубликовать в существующей базе данных, и она будет тщательно создавать сценарий, который вносит только изменения. Это круто.

...