protected virtual void GenerateRootTableCopy()
        {
            var insertColumns = RootTable.Columns
                                .Where(c => c.IsCopyable)
                                .Where(c => !ExcludedColumns.Contains(c));
            var insertColumnNames = insertColumns
                                    .Select(c => $"[{c.Name}]");
            var insertColumnValues = insertColumns
                                     .Select(c => UpdateParameterNames.ContainsKey(c) ? UpdateParameterNames[c] : $"Source.[{c.Name}]");
            string insertClause = !insertColumns.Any()
? @"INSERT DEFAULT VALUES"
: $@"INSERT (
        {string.Join(Separators.Cnlw8, insertColumnNames)})
    VALUES (
        {string.Join(Separators.Cnlw8, insertColumnValues)})";
            string setStatement = PrimaryKeyOutputParameterName != null
? $@"
    SET {PrimaryKeyOutputParameterName} = SCOPE_IDENTITY();"
: "";

            ProcedureBody.AppendLine($@"
    MERGE INTO [{RootTable.Schema.Name}].[{RootTable.Name}] AS Target
    USING (
        SELECT *
        FROM [{RootTable.Schema.Name}].[{RootTable.Name}]
        WHERE [{RootTable.PrimaryKey.Column.Name}] = {PrimaryKeyParameterName}
    ) AS Source
    ON 1 = 0
    WHEN NOT MATCHED BY TARGET THEN
    {insertClause}{GenerateOutputClause(RootTable)}{setStatement}");
        }
        protected virtual void GenerateDependentTableCopy(ReferenceGraph.Vertex vertex)
        {
            var table = vertex.Table;
            var dependentReferences = vertex.DependentReferences
                                      // Check constraints can lead to nullable dependent reference columns. However, rows needs to be dependent
                                      // on something copied, so if there's only one dependency we might as well inner join on it regardless.
                                      .Select(r => new { r.ParentColumn, r.ReferencedTable, UseLeftJoin = r.ParentColumn.IsNullable && vertex.DependentReferences.Count > 1 })
                                      .ToReadOnlyList();
            var selectColumnNames = dependentReferences
                                    .Select((r, i) => $"j{i}.InsertedID j{i}InsertedID");
            // If necessary (it usually isn't), make sure something in the row is dependent on something copied. Faster to
            // or together where-ins than it is to coalesce on all left joined columns and perform a not null check.
            string fromClause = dependentReferences.All(r => r.UseLeftJoin)
? $@"FROM (
            SELECT *
            FROM [{table.Schema}].[{table.Name}]
            WHERE {string.Join($"{Separators.Nlw16}OR ", dependentReferences.Select(r => $"[{r.ParentColumn.Name}] IN (SELECT ExistingID FROM {TableVariableNames[r.ReferencedTable]})"))}
        ) AS copy"
: $@"FROM [{table.Schema.Name}].[{table.Name}] copy";
            var joinClauses = dependentReferences
                              .Select((r, i) => $"{(r.UseLeftJoin ? "LEFT " : "")}JOIN {TableVariableNames[r.ReferencedTable]} j{i}{Separators.Nlw12}ON copy.[{r.ParentColumn.Name}] = j{i}.ExistingID");
            var dependentInsertColumnNames = dependentReferences
                                             .Select(r => $"[{r.ParentColumn.Name}]");
            var dependentInsertColumnValues = dependentReferences
                                              // A potential left join should leave InsertedID null only if the original value was null, since this is a root copy.
                                              .Select((r, i) => $"j{i}InsertedID");
            var nonDependentInsertColumns = table.Columns
                                            .Where(c => c.IsCopyable)
                                            .Where(c => !ExcludedColumns.Contains(c))
                                            .Where(c => !dependentReferences.Select(r => r.ParentColumn).Contains(c));
            var nonDependentInsertColumnNames = nonDependentInsertColumns
                                                .Select(c => $"[{c.Name}]");
            var nonDependentInsertColumnValues = nonDependentInsertColumns
                                                 .Select(c => UpdateParameterNames.ContainsKey(c) ? UpdateParameterNames[c] : $"Source.[{c.Name}]");

            ProcedureBody.AppendLine($@"
    MERGE INTO [{table.Schema.Name}].[{table.Name}] AS Target
    USING (
        SELECT
            copy.*,
            {string.Join(Separators.Cnlw12, selectColumnNames)}
        {fromClause}
        {string.Join(Separators.Nlw8, joinClauses)}
    ) AS Source
    ON 1 = 0
    WHEN NOT MATCHED BY TARGET THEN
    INSERT (
        {string.Join(Separators.Cnlw8, dependentInsertColumnNames.Concat(nonDependentInsertColumnNames))})
    VALUES (
        {string.Join(Separators.Cnlw8, dependentInsertColumnValues.Concat(nonDependentInsertColumnValues))}){GenerateOutputClause(table)}");
        }
Beispiel #3
0
        protected override void GenerateDependentTableCopy(ReferenceGraph.Vertex vertex)
        {
            var table = vertex.Table;
            var dependentReferences = vertex.DependentReferences;
            // Rows needs to be dependent on something copied, so if there's only one dependency we might as well inner join on it.
            bool useLeftJoin       = dependentReferences.Count > 1;
            var  selectColumnNames = dependentReferences
                                     .Select((r, i) => $"j{i}.InsertedID j{i}InsertedID");
            // If necessary (it often is), make sure something in the row is dependent on something copied. Faster to
            // or together where-ins than it is to coalesce on all left joined columns and perform a not null check.
            string fromClause = useLeftJoin
? $@"FROM (
            SELECT *
            FROM [{table.Schema}].[{table.Name}]
            WHERE {string.Join($"{Separators.Nlw16} OR ", dependentReferences.Select(r => $"[{r.ParentColumn.Name}] IN (SELECT ExistingID FROM {TableVariableNames[r.ReferencedTable]})"))}
        ) AS copy"
: $@"FROM [{table.Schema.Name}].[{table.Name}] copy";
            var joinClauses = dependentReferences
                              .Select((r, i) => new { r.ParentColumn, r.ReferencedTable, JoinString = $"{(useLeftJoin ? "LEFT " : "")}JOIN" })
                              .Select((r, i) => $"{r.JoinString} {TableVariableNames[r.ReferencedTable]} j{i}{Separators.Nlw12}ON copy.[{r.ParentColumn.Name}] = j{i}.ExistingID");
            var dependentInsertColumnNames = dependentReferences
                                             .Select(r => $"[{r.ParentColumn.Name}]");
            var dependentInsertColumnValues = dependentReferences
                                              .Select((r, i) => useLeftJoin ? $"COALESCE(j{i}InsertedID, [{r.ParentColumn.Name}])" : $"j{i}InsertedID");
            var nonDependentInsertColumns = table.Columns
                                            .Where(c => c.IsCopyable)
                                            .Where(c => !ExcludedColumns.Contains(c))
                                            .Where(c => !dependentReferences.Select(r => r.ParentColumn).Contains(c));
            var nonDependentInsertColumnNames = nonDependentInsertColumns
                                                .Select(c => $"[{c.Name}]");
            var nonDependentInsertColumnValues = nonDependentInsertColumns
                                                 .Select(c => UpdateParameterNames.ContainsKey(c) ? UpdateParameterNames[c] : $"Source.[{c.Name}]");

            ProcedureBody.AppendLine($@"
    MERGE INTO [{table.Schema.Name}].[{table.Name}] AS Target
    USING (
        SELECT
            copy.*,
            {string.Join(Separators.Cnlw12, selectColumnNames)}
        {fromClause}
        {string.Join(Separators.Nlw8, joinClauses)}
    ) AS Source
    ON 1 = 0
    WHEN NOT MATCHED BY TARGET THEN
    INSERT (
        {string.Join(Separators.Cnlw8, dependentInsertColumnNames.Concat(nonDependentInsertColumnNames))})
    VALUES (
        {string.Join(Separators.Cnlw8, dependentInsertColumnValues.Concat(nonDependentInsertColumnValues))}){GenerateOutputClause(table)}");
        }
        protected virtual void GenerateTableVariables()
        {
            var relevantTables = ReferenceGraph.Vertices
                                 .Where(v => v.Table.HasIdentityColumnAsPrimaryKey)
                                 .Where(v => v.IsReferenced() || v.NonDependentReferences.Any())
                                 .Select(v => v.Table);
            bool areTableNamesDistinct = relevantTables
                                         .Select(t => t.Name)
                                         .Distinct()
                                         .Count() == relevantTables.Count();

            foreach (var table in relevantTables)
            {
                string tableVariableName = $"@{(areTableNamesDistinct ? "" : table.Schema.SpacelessName)}{table.SingularSpacelessName}IDPairs";
                TableVariableNames.Add(table, tableVariableName);

                ProcedureBody.AppendLine($@"
    DECLARE {tableVariableName} TABLE (
        ExistingID INT NOT NULL UNIQUE,
        InsertedID INT NOT NULL UNIQUE
    );");
            }
        }
        protected virtual void GenerateNonDependentReferenceUpdates(ReferenceGraph.Vertex vertex)
        {
            var table = vertex.Table;
            var nonDependentReferences = vertex.NonDependentReferences;
            // Since this is a root copy, all non-null original values should have corresponding InsertedIDs. It's not necessary
            // to worry about what to do with references (between tables in the subgraph of the database discovered from whatever the
            // root table is) to non-copied data, because there shouldn't be any such references. So if there's only one dependency,
            // inner join, otherwise left join WITH a coalesce--because triggers could've already updated these references underneath us!
            // Added side benefit: coalescing here makes this code exactly what we want in the deep copy case.
            bool useLeftJoin   = nonDependentReferences.Count > 1;
            var  setStatements = useLeftJoin ? nonDependentReferences
                                 .Select((r, i) => $"copy.[{r.ParentColumn.Name}] = COALESCE(j{i}.InsertedID, copy.[{r.ParentColumn.Name}])") : nonDependentReferences
                                 .Select((r, i) => $"copy.[{r.ParentColumn.Name}] = j{i}.InsertedID");
            var joinClauses = nonDependentReferences
                              .Select((r, i) => $"{(useLeftJoin ? "LEFT " : "")}JOIN {TableVariableNames[r.ReferencedTable]} j{i}{Separators.Nlw8}ON copy.[{r.ParentColumn.Name}] = j{i}.ExistingID");

            ProcedureBody.AppendLine($@"
    UPDATE copy
    SET
        {string.Join(Separators.Cnlw8, setStatements)}
    FROM [{table.Schema.Name}].[{table.Name}] copy
    {string.Join(Separators.Nlw4, joinClauses)}
    WHERE copy.[{table.PrimaryKey.Column.Name}] IN (SELECT InsertedID FROM {TableVariableNames[table]});");
        }