Example #1
0
 public RowCounts ApplyTableChanges(TableConf table, TableConf archiveTable, string dbName, long CTID, string CTDBName, bool isConsolidated)
 {
     var cmds = new List<InsertDelete>();
     cmds.Add(BuildApplyCommand(table, dbName, CTDBName, CTID));
     if (archiveTable != null) {
         cmds.Add(BuildApplyCommand(archiveTable, dbName, CTDBName, CTID));
     }
     var connStr = buildConnString(dbName);
     var rowCounts = new RowCounts(0, 0);
     using (var conn = new OleDbConnection(connStr)) {
         conn.Open();
         var trans = conn.BeginTransaction();
         foreach (var id in cmds) {
             id.delete.Transaction = trans;
             id.delete.Connection = conn;
             id.delete.CommandTimeout = Config.QueryTimeout;
             logger.Log(id.delete.CommandText, LogLevel.Trace);
             int deleted = id.delete.ExecuteNonQuery();
             logger.Log(new { Table = table.Name, message = "Rows deleted: " + deleted }, LogLevel.Info);
             id.insert.Transaction = trans;
             id.insert.Connection = conn;
             id.insert.CommandTimeout = Config.QueryTimeout;
             logger.Log(id.insert.CommandText, LogLevel.Trace);
             int inserted = id.insert.ExecuteNonQuery();
             logger.Log(new { Table = table.Name, message = "Rows inserted: " + inserted }, LogLevel.Info);
             rowCounts = new RowCounts(rowCounts.Inserted + inserted, rowCounts.Deleted + deleted);
         }
         trans.Commit();
     }
     return rowCounts;
 }
Example #2
0
        public RowCounts ApplyTableChanges(TableConf table, TableConf archiveTable, string dbName, long CTID, string CTDBName, bool isConsolidated)
        {
            var cmds = new List <InsertDelete>();

            cmds.Add(BuildApplyCommand(table, dbName, CTDBName, CTID));
            if (archiveTable != null)
            {
                cmds.Add(BuildApplyCommand(archiveTable, dbName, CTDBName, CTID));
            }
            var connStr   = buildConnString(dbName);
            var rowCounts = new RowCounts(0, 0);

            using (var conn = new OleDbConnection(connStr)) {
                conn.Open();
                var trans = conn.BeginTransaction();
                foreach (var id in cmds)
                {
                    id.delete.Transaction    = trans;
                    id.delete.Connection     = conn;
                    id.delete.CommandTimeout = Config.QueryTimeout;
                    logger.Log(id.delete.CommandText, LogLevel.Trace);
                    int deleted = id.delete.ExecuteNonQuery();
                    logger.Log(new { Table = table.Name, message = "Rows deleted: " + deleted }, LogLevel.Info);
                    id.insert.Transaction    = trans;
                    id.insert.Connection     = conn;
                    id.insert.CommandTimeout = Config.QueryTimeout;
                    logger.Log(id.insert.CommandText, LogLevel.Trace);
                    int inserted = id.insert.ExecuteNonQuery();
                    logger.Log(new { Table = table.Name, message = "Rows inserted: " + inserted }, LogLevel.Info);
                    rowCounts = new RowCounts(rowCounts.Inserted + inserted, rowCounts.Deleted + deleted);
                }
                trans.Commit();
            }
            return(rowCounts);
        }
Example #3
0
        /// <summary>
        /// Apply changes for the change tables we captured
        /// </summary>
        /// <param name="tables">Change tables we captured</param>
        /// <param name="CTID">Change tracking id</param>
        /// <param name="isConsolidated">Whether changes are consolidated</param>
        /// <returns></returns>
        private RowCounts ApplyChanges(List <ChangeTable> tables, Int64 CTID, bool isConsolidated)
        {
            var hasArchive = ValidTablesAndArchives(tables, CTID);
            var actions    = new List <Action>();
            var counts     = new ConcurrentDictionary <string, RowCounts>();

            foreach (var tableArchive in hasArchive)
            {
                KeyValuePair <TableConf, TableConf> tLocal = tableArchive;
                Action act = () => {
                    try {
                        logger.Log(new { message = "Applying changes", Table = tLocal.Key.Name + (tLocal.Value == null ? "" : " (and archive)") }, LogLevel.Debug);
                        var sw = Stopwatch.StartNew();
                        var rc = destDataUtils.ApplyTableChanges(tLocal.Key, tLocal.Value, Config.SlaveDB, CTID, Config.SlaveCTDB, isConsolidated);
                        counts[tLocal.Key.Name] = rc;
                        logger.Log(new { message = "ApplyTableChanges : " + sw.Elapsed, Table = tLocal.Key.Name }, LogLevel.Trace);
                    } catch (Exception e) {
                        HandleException(e, tLocal.Key);
                    }
                };
                actions.Add(act);
            }
            logger.Log("Parallel invocation of " + actions.Count + " table change applies", LogLevel.Trace);
            var options = new ParallelOptions();

            options.MaxDegreeOfParallelism = Config.MaxThreads;
            Parallel.Invoke(options, actions.ToArray());
            RowCounts total = counts.Values.Aggregate(new RowCounts(0, 0), (a, b) => new RowCounts(a.Inserted + b.Inserted, a.Deleted + b.Deleted));

            return(total);
        }
Example #4
0
        private void RecordRowCounts(RowCounts actual, ChangeTrackingBatch ctb)
        {
            var expected = sourceDataUtils.GetExpectedRowCounts(Config.RelayDB, ctb.CTID);

            logger.Log("Expected row counts: " + expected + " | actual: " + actual, LogLevel.Info);
            double diff = expected - actual.Inserted;
            double mismatch;

            if (expected == 0)
            {
                if (actual.Inserted == 0)
                {
                    mismatch = 0.0;
                }
                else
                {
                    logger.Log("Expected 0 rows, got " + actual.Inserted + " rows inserted on slave.", LogLevel.Error);
                    return;
                }
            }
            else
            {
                mismatch = diff / expected;
            }
            int    percentDiff = (int)(mismatch * 100);
            string key         = string.Format("db.mssql_changetracking_counters.RecordCountMismatchProd{0}.{1}", Config.Slave.Replace('.', '_'), Config.SlaveDB);

            logger.Increment(key, percentDiff);
        }
Example #5
0
        /// <summary>
        /// Runs a single change tracking batch
        /// </summary>
        /// <param name="ctb">Change tracking batch object to work on</param>
        private void RunSingleBatch(ChangeTrackingBatch ctb)
        {
            Stopwatch sw;

            logger.Log("Applying schema changes ", LogLevel.Info);
            ApplySchemaChangesAndWrite(ctb);
            //marking this field so that all completed slave batches will have the same values
            sourceDataUtils.WriteBitWise(Config.RelayDB, ctb.CTID, Convert.ToInt32(SyncBitWise.ConsolidateBatches), AgentType.Slave);

            logger.Log("Populating table list", LogLevel.Info);

            List <ChangeTable> existingCTTables = PopulateTableList(Config.Tables, Config.RelayDB, new List <ChangeTrackingBatch>()
            {
                ctb
            });

            logger.Log("Capturing field lists", LogLevel.Info);
            SetFieldListsSlave(Config.RelayDB, Config.Tables, ctb, existingCTTables);

            if ((ctb.SyncBitWise & Convert.ToInt32(SyncBitWise.DownloadChanges)) == 0)
            {
                logger.Log("Downloading changes", LogLevel.Info);
                sw = Stopwatch.StartNew();
                CopyChangeTables(Config.Tables, Config.RelayDB, Config.SlaveCTDB, ctb.CTID, existingCTTables);
                logger.Log("CopyChangeTables: " + sw.Elapsed, LogLevel.Trace);
                sourceDataUtils.WriteBitWise(Config.RelayDB, ctb.CTID, Convert.ToInt32(SyncBitWise.DownloadChanges), AgentType.Slave);
                logger.Timing(StepTimingKey("DownloadChanges"), (int)sw.ElapsedMilliseconds);
            }

            if ((ctb.SyncBitWise & Convert.ToInt32(SyncBitWise.ApplyChanges)) == 0)
            {
                logger.Log("Applying changes", LogLevel.Info);
                sw = Stopwatch.StartNew();
                RowCounts total = ApplyChanges(existingCTTables, ctb.CTID, isConsolidated: false);
                RecordRowCounts(total, ctb);
                logger.Log("ApplyChanges: " + sw.Elapsed, LogLevel.Trace);
                sourceDataUtils.WriteBitWise(Config.RelayDB, ctb.CTID, Convert.ToInt32(SyncBitWise.ApplyChanges), AgentType.Slave);
                logger.Timing(StepTimingKey("ApplyChanges"), (int)sw.ElapsedMilliseconds);
            }
            logger.Log("Syncing history tables", LogLevel.Info);
            sw = Stopwatch.StartNew();
            SyncHistoryTables(Config.SlaveCTDB, existingCTTables, isConsolidated: false);
            logger.Log("SyncHistoryTables: " + sw.Elapsed, LogLevel.Trace);
            var syncStopTime = DateTime.Now;

            sourceDataUtils.MarkBatchComplete(Config.RelayDB, ctb.CTID, syncStopTime, Config.Slave);
            string key = String.Format(
                "db.mssql_changetracking_counters.DataDurationToSync{0}.{1}",
                Config.Slave.Replace('.', '_'),
                Config.SlaveDB);

            logger.Increment(key, (int)(syncStopTime - ctb.SyncStartTime.Value).TotalMinutes);
            logger.Timing(StepTimingKey("SyncHistoryTables"), (int)sw.ElapsedMilliseconds);
        }
Example #6
0
        // internal for testing
        internal static RowCounts[][] GetBaseRowCounts(IReadOnlyList <int> baseRowCounts, int generations)
        {
            var rowCounts = new RowCounts[TableIndexExtensions.Count][];

            for (int t = 0; t < rowCounts.Length; t++)
            {
                rowCounts[t] = new RowCounts[generations];
                rowCounts[t][0].AggregateInserts = baseRowCounts[t];
            }

            return(rowCounts);
        }
Example #7
0
        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            PropertyDescriptor property = context.DataContext.GetProperties()[ExcelCreate.GetExcelAppTag];
            Excel::Application excelApp = property.GetValue(context.DataContext) as Excel::Application;

            try
            {
                m_Delegate = new runDelegate(Run);
                string            sheetName = SheetName.Get(context);
                Excel::_Worksheet sheet;
                if (sheetName == null)
                {
                    sheet = excelApp.ActiveSheet;
                }
                else
                {
                    sheet = excelApp.ActiveWorkbook.Sheets[sheetName];
                }

                int rowCounts = 0, colCounts = 0;
                //有效行列数 不包含中间的空行
                if (isValid)
                {
                    rowCounts = sheet.UsedRange.Rows.Count;
                    colCounts = sheet.UsedRange.Columns.Count;
                }
                //空行/列截止
                else if (isActive)
                {
                    rowCounts = sheet.UsedRange.CurrentRegion.Rows.Count;
                    colCounts = sheet.UsedRange.CurrentRegion.Columns.Count;
                }
                else if (isSingleLine)
                {
                    rowCounts = sheet.get_Range("A65535").get_End(Excel.XlDirection.xlUp).Row;
                    colCounts = sheet.get_Range("IV1").get_End(Excel.XlDirection.xlToLeft).Column;
                }

                RowCounts.Set(context, rowCounts);
                ColCounts.Set(context, colCounts);

                System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet);
                sheet = null;
                GC.Collect();
            }
            catch (Exception e)
            {
                SharedObject.Instance.Output(SharedObject.enOutputType.Error, "EXCEL获取行列总数过程出错", e.Message);
                new CommonVariable().realaseProcessExit(excelApp);
            }
            return(m_Delegate.BeginInvoke(callback, state));
        }
Example #8
0
        public async Task RenderAsync(CancellationToken cancellationToken = default)
        {
            var orphanedTables = Tables
                                 .Where(t => t.ParentKeys.Empty() && t.ChildKeys.Empty())
                                 .ToList();

            var mapper = new OrphansModelMapper();
            var orphanedTableViewModels = orphanedTables
                                          .Select(t =>
            {
                if (!RowCounts.TryGetValue(t.Name, out var rowCount))
                {
                    rowCount = 0;
                }
                return(mapper.Map(t, rowCount));
            })
                                          .ToList();

            var templateParameter = new Orphans(orphanedTableViewModels);
            var renderedOrphans   = await Formatter.RenderTemplateAsync(templateParameter, cancellationToken).ConfigureAwait(false);

            var databaseName = !IdentifierDefaults.Database.IsNullOrWhiteSpace()
                ? IdentifierDefaults.Database + " Database"
                : "Database";
            var pageTitle        = "Orphan Tables · " + databaseName;
            var orphansContainer = new Container(renderedOrphans, pageTitle, string.Empty);
            var renderedPage     = await Formatter.RenderTemplateAsync(orphansContainer, cancellationToken).ConfigureAwait(false);

            if (!ExportDirectory.Exists)
            {
                ExportDirectory.Create();
            }
            var outputPath = Path.Combine(ExportDirectory.FullName, "orphans.html");

            using var writer = File.CreateText(outputPath);
            await writer.WriteAsync(renderedPage.AsMemory(), cancellationToken).ConfigureAwait(false);

            await writer.FlushAsync().ConfigureAwait(false);
        }
Example #9
0
        public async Task RenderAsync(CancellationToken cancellationToken = default)
        {
            var mapper = new MainModelMapper();

            var tableViewModels = new List <Main.Table>();

            foreach (var table in Tables)
            {
                if (!RowCounts.TryGetValue(table.Name, out var rowCount))
                {
                    rowCount = 0;
                }

                var renderTable = mapper.Map(table, rowCount);
                tableViewModels.Add(renderTable);
            }

            var tablesVm     = new Tables(tableViewModels);
            var renderedMain = await Formatter.RenderTemplateAsync(tablesVm, cancellationToken).ConfigureAwait(false);

            var databaseName = !IdentifierDefaults.Database.IsNullOrWhiteSpace()
                ? IdentifierDefaults.Database + " Database"
                : "Database";
            var pageTitle     = "Tables · " + databaseName;
            var mainContainer = new Container(renderedMain, pageTitle, string.Empty);
            var renderedPage  = await Formatter.RenderTemplateAsync(mainContainer, cancellationToken).ConfigureAwait(false);

            if (!ExportDirectory.Exists)
            {
                ExportDirectory.Create();
            }
            var outputPath = Path.Combine(ExportDirectory.FullName, "tables.html");

            using var writer = File.CreateText(outputPath);
            await writer.WriteAsync(renderedPage.AsMemory(), cancellationToken).ConfigureAwait(false);

            await writer.FlushAsync().ConfigureAwait(false);
        }
Example #10
0
        public async Task RenderAsync(CancellationToken cancellationToken = default)
        {
            var mapper = new MainModelMapper();

            var columns         = 0U;
            var constraints     = 0U;
            var indexesCount    = 0U;
            var tableViewModels = new List <Main.Table>();

            foreach (var table in Tables)
            {
                if (!RowCounts.TryGetValue(table.Name, out var rowCount))
                {
                    rowCount = 0;
                }

                var renderTable = mapper.Map(table, rowCount);

                var uniqueKeyLookup = table.GetUniqueKeyLookup();
                var uniqueKeyCount  = uniqueKeyLookup.UCount();

                var checksLookup = table.GetCheckLookup();
                var checksCount  = checksLookup.UCount();

                var indexesLookup = table.GetIndexLookup();
                var indexCount    = indexesLookup.UCount();
                indexesCount += indexCount;

                await table.PrimaryKey.IfSomeAsync(_ => constraints++).ConfigureAwait(false);

                constraints += uniqueKeyCount;
                constraints += renderTable.ParentsCount;
                constraints += checksCount;

                columns += renderTable.ColumnCount;

                tableViewModels.Add(renderTable);
            }

            var viewViewModels = Views.Select(mapper.Map).ToList();

            columns += (uint)viewViewModels.Sum(v => v.ColumnCount);

            var sequenceViewModels = Sequences.Select(mapper.Map).ToList();

            var synonymTargets    = new SynonymTargets(Tables, Views, Sequences, Synonyms, Routines);
            var synonymViewModels = Synonyms.Select(s => mapper.Map(s, synonymTargets)).ToList();

            var routineViewModels = Routines.Select(mapper.Map).ToList();

            var schemas = Tables.Select(t => t.Name)
                          .Union(Views.Select(v => v.Name))
                          .Union(Sequences.Select(s => s.Name))
                          .Union(Synonyms.Select(s => s.Name))
                          .Union(Routines.Select(r => r.Name))
                          .Select(n => n.Schema)
                          .Where(n => n != null)
                          .Distinct()
                          .Where(s => s != null)
                          .Select(s => s !)
                          .OrderBy(n => n)
                          .ToList();

            var templateParameter = new Main(
                Database.IdentifierDefaults.Database,
                DatabaseDisplayVersion ?? string.Empty,
                columns,
                constraints,
                indexesCount,
                schemas,
                tableViewModels,
                viewViewModels,
                sequenceViewModels,
                synonymViewModels,
                routineViewModels
                );

            var renderedMain = await Formatter.RenderTemplateAsync(templateParameter, cancellationToken).ConfigureAwait(false);

            var databaseName = !Database.IdentifierDefaults.Database.IsNullOrWhiteSpace()
                ? Database.IdentifierDefaults.Database + " Database"
                : "Database";
            var pageTitle     = "Home · " + databaseName;
            var mainContainer = new Container(renderedMain, pageTitle, string.Empty);
            var renderedPage  = await Formatter.RenderTemplateAsync(mainContainer, cancellationToken).ConfigureAwait(false);

            if (!ExportDirectory.Exists)
            {
                ExportDirectory.Create();
            }
            var outputPath = Path.Combine(ExportDirectory.FullName, "index.html");

            using var writer = File.CreateText(outputPath);
            await writer.WriteAsync(renderedPage.AsMemory(), cancellationToken).ConfigureAwait(false);

            await writer.FlushAsync().ConfigureAwait(false);
        }
Example #11
0
 private void RecordRowCounts(RowCounts actual, ChangeTrackingBatch ctb)
 {
     var expected = sourceDataUtils.GetExpectedRowCounts(Config.RelayDB, ctb.CTID);
     logger.Log("Expected row counts: " + expected + " | actual: " + actual, LogLevel.Info);
     double diff = expected - actual.Inserted;
     double mismatch;
     if (expected == 0) {
         if (actual.Inserted == 0) {
             mismatch = 0.0;
         } else {
             logger.Log("Expected 0 rows, got " + actual.Inserted + " rows inserted on slave.", LogLevel.Error);
             return;
         }
     } else {
         mismatch = diff / expected;
     }
     int percentDiff = (int)(mismatch * 100);
     string key = string.Format("db.mssql_changetracking_counters.RecordCountMismatchProd{0}.{1}", Config.Slave.Replace('.', '_'), Config.SlaveDB);
     logger.Increment(key, percentDiff);
 }
Example #12
0
        public Table Map(IRelationalDatabaseTable table)
        {
            if (table == null)
            {
                throw new ArgumentNullException(nameof(table));
            }

            var tableColumns = table.Columns.Select((c, i) => new { Column = c, Ordinal = i + 1 }).ToList();
            var primaryKey   = table.PrimaryKey;
            var uniqueKeys   = table.UniqueKeys.ToList();
            var parentKeys   = table.ParentKeys.ToList();
            var childKeys    = table.ChildKeys.ToList();
            var checks       = table.Checks.ToList();
            var triggers     = table.Triggers.ToList();

            var columns = new List <Table.Column>();

            foreach (var tableColumn in tableColumns)
            {
                var col                 = tableColumn.Column;
                var columnName          = col.Name.LocalName;
                var qualifiedColumnName = table.Name.ToVisibleName() + "." + columnName;

                var isPrimaryKey = primaryKey.Match(pk => pk.Columns.Any(c => c.Name.LocalName == columnName), () => false);
                var isUniqueKey  = uniqueKeys.Any(uk => uk.Columns.Any(ukc => ukc.Name.LocalName == columnName));
                var isParentKey  = parentKeys.Any(fk => fk.ChildKey.Columns.Any(fkc => fkc.Name.LocalName == columnName));

                var matchingParentKeys = parentKeys.Where(fk => fk.ChildKey.Columns.Any(fkc => fkc.Name.LocalName == columnName)).ToList();
                var columnParentKeys   = new List <Table.ParentKey>();
                foreach (var parentKey in matchingParentKeys)
                {
                    var columnIndexes = parentKey.ChildKey.Columns
                                        .Select((c, i) => c.Name.LocalName == columnName ? i : -1)
                                        .Where(i => i >= 0)
                                        .ToList();

                    var parentColumnNames = parentKey.ParentKey.Columns
                                            .Where((_, i) => columnIndexes.Contains(i))
                                            .Select(c => c.Name.LocalName)
                                            .ToList();

                    var childKeyName = parentKey.ChildKey.Name.Match(name => name.LocalName, () => string.Empty);
                    var columnFks    = parentColumnNames.Select(colName =>
                                                                new Table.ParentKey(
                                                                    childKeyName,
                                                                    parentKey.ParentTable,
                                                                    colName,
                                                                    qualifiedColumnName,
                                                                    RootPath
                                                                    )).ToList();

                    columnParentKeys.AddRange(columnFks);
                }

                var matchingChildKeys = childKeys.Where(ck => ck.ParentKey.Columns.Any(ckc => ckc.Name.LocalName == columnName)).ToList();
                var columnChildKeys   = new List <Table.ChildKey>();
                foreach (var childKey in matchingChildKeys)
                {
                    var columnIndexes = childKey.ParentKey.Columns
                                        .Select((c, i) => c.Name.LocalName == columnName ? i : -1)
                                        .Where(i => i >= 0)
                                        .ToList();

                    var childColumnNames = childKey.ChildKey.Columns
                                           .Where((_, i) => columnIndexes.Contains(i))
                                           .Select(c => c.Name.LocalName)
                                           .ToList();

                    var childKeyName = childKey.ChildKey.Name.Match(name => name.LocalName, () => string.Empty);
                    var columnFks    = childColumnNames.Select(colName =>
                                                               new Table.ChildKey(
                                                                   childKeyName,
                                                                   childKey.ChildTable,
                                                                   colName,
                                                                   qualifiedColumnName,
                                                                   RootPath
                                                                   )).ToList();

                    columnChildKeys.AddRange(columnFks);
                }

                var column = new Table.Column(
                    columnName,
                    tableColumn.Ordinal,
                    tableColumn.Column.IsNullable,
                    tableColumn.Column.Type.Definition,
                    tableColumn.Column.DefaultValue,
                    isPrimaryKey,
                    isUniqueKey,
                    isParentKey,
                    columnChildKeys,
                    columnParentKeys
                    );
                columns.Add(column);
            }

            var tableIndexes  = table.Indexes.ToList();
            var renderIndexes = tableIndexes.Select(index =>
                                                    new Table.Index(
                                                        index.Name?.LocalName,
                                                        index.IsUnique,
                                                        index.Columns.Select(c => c.Expression).ToList(),
                                                        index.Columns.Select(c => c.Order).ToList(),
                                                        index.IncludedColumns.Select(c => c.Name.LocalName).ToList()
                                                        )).ToList();

            var renderPrimaryKey = primaryKey
                                   .Map(pk => new Table.PrimaryKeyConstraint(
                                            pk.Name.Match(name => name.LocalName, () => string.Empty),
                                            pk.Columns.Select(c => c.Name.LocalName).ToList()
                                            ));

            var renderUniqueKeys = uniqueKeys
                                   .Select(uk => new Table.UniqueKey(
                                               uk.Name.Match(name => name.LocalName, () => string.Empty),
                                               uk.Columns.Select(c => c.Name.LocalName).ToList()
                                               )).ToList();

            var renderParentKeys = parentKeys.Select(pk =>
                                                     new Table.ForeignKey(
                                                         pk.ChildKey.Name.Match(name => name.LocalName, () => string.Empty),
                                                         pk.ChildKey.Columns.Select(c => c.Name.LocalName).ToList(),
                                                         pk.ParentTable,
                                                         pk.ParentKey.Name.Match(name => name.LocalName, () => string.Empty),
                                                         pk.ParentKey.Columns.Select(c => c.Name.LocalName).ToList(),
                                                         pk.DeleteAction,
                                                         pk.UpdateAction,
                                                         RootPath
                                                         )).ToList();

            var renderChecks = checks.Select(c =>
                                             new Table.CheckConstraint(
                                                 c.Name.Match(name => name.LocalName, () => string.Empty),
                                                 c.Definition
                                                 )).ToList();

            var renderTriggers = triggers.Select(tr =>
                                                 new Table.Trigger(
                                                     table.Name,
                                                     tr.Name.LocalName,
                                                     tr.Definition,
                                                     tr.QueryTiming,
                                                     tr.TriggerEvent
                                                     )).ToList();

            var oneDegreeTables = RelationshipFinder.GetTablesByDegrees(table, 1);
            var twoDegreeTables = RelationshipFinder.GetTablesByDegrees(table, 2);

            var dotFormatter  = new DotFormatter(IdentifierDefaults);
            var renderOptions = new DotRenderOptions {
                HighlightedTable = table.Name
            };
            var oneDegreeDot = dotFormatter.RenderTables(oneDegreeTables, RowCounts, renderOptions);
            var twoDegreeDot = dotFormatter.RenderTables(twoDegreeTables, RowCounts, renderOptions);

            var diagrams = new[]
            {
                new Table.Diagram(table.Name, "One Degree", oneDegreeDot, true),
                new Table.Diagram(table.Name, "Two Degrees", twoDegreeDot, false)
            };

            if (!RowCounts.TryGetValue(table.Name, out var rowCount))
            {
                rowCount = 0;
            }

            return(new Table(
                       table.Name,
                       columns,
                       renderPrimaryKey,
                       renderUniqueKeys,
                       renderParentKeys,
                       renderChecks,
                       renderIndexes,
                       renderTriggers,
                       diagrams,
                       RootPath,
                       rowCount
                       ));
        }