示例#1
0
        /// <summary>
        /// Build an execution plan for the specified data source for blazing fast performance.
        /// </summary>
        /// <param name="sourceRow">Identify a single or multiples rows to clone.</param>
        /// <param name="getDerivatives">When true the data related to the input(s) line(s) from other tables will be cloned.</param>
        /// <param name="shouldReturnFk">Indicate that a source row should only return a single line.</param>
        /// <param name="level">Current recursion level.</param>
        /// <param name="rowsGenerating">Current stack to handle circular foreign keys.</param>
        /// <returns>Always return the primary key of the source row, same if the value queried is a foreign key.</returns>
        private RowIdentifier BuildExecutionPlan(RowIdentifier sourceRow, bool getDerivatives, bool shouldReturnFk, int level,
                                                 Stack <RowIdentifier> rowsGenerating)
        {
            var srcRows = ConnectionsContext.Select(sourceRow);
            var nbRows  = srcRows.Length;
            var table   = Metadatas.GetTable(sourceRow);

            //By default the destination server is the source if no road is found.
            var serverDst = new SehemaIdentifier
            {
                ServerId = sourceRow.ServerId,
                Database = sourceRow.Database,
                Schema   = sourceRow.Schema
            };

            if (ExecutionContext.Map.ContainsKey(serverDst))
            {
                serverDst = ExecutionContext.Map[serverDst];
            }

            var riReturn = new RowIdentifier
            {
                ServerId = serverDst.ServerId,
                Database = serverDst.Database,
                Schema   = serverDst.Schema,
                Table    = sourceRow.Table
            };
            var tiDestination = new TableIdentifier
            {
                ServerId = serverDst.ServerId,
                Database = serverDst.Database,
                Schema   = serverDst.Schema,
                Table    = sourceRow.Table
            };

            LogStatusChanged(sourceRow, level);

            if (shouldReturnFk && nbRows > 1)
            {
                throw new Exception("The foreignkey is not unique!");
            }

            //For each row
            for (var i = 0; i < nbRows; i++)
            {
                var currentRow = srcRows[i];
                var srcKey     = table.BuildRawPkFromDataRow(currentRow);

                //Si ligne déjà enregistrée
                var dstKey = _keyRelationships.GetKey(serverDst.ServerId, serverDst.Database,
                                                      serverDst.Schema, sourceRow.Table, srcKey);
                if (dstKey != null)
                {
                    if (shouldReturnFk)
                    {
                        //Construit la pk de retour
                        riReturn.Columns = table.BuildPkFromRawKey(dstKey);
                        return(riReturn);
                    }
                    continue;
                }
                var destinationRow = (object[])currentRow.Clone();
                foreach (var fk in table.ForeignKeys)
                {
                    //On skip si la FK est null
                    var fkValue = table.BuildRawFkFromDataRow(fk, currentRow);
                    if (fkValue.Contains(DBNull.Value))
                    {
                        continue;
                    }

                    //Si la foreignkey a déjà été enregistrée, on l'utilise
                    var fkDst = _keyRelationships.GetKey(fk.ServerIdTo, fk.DatabaseTo, fk.SchemaTo, fk.TableTo, fkValue);
                    if (fkDst != null)
                    {
                        table.SetFkInDatarow(fk, fkDst, destinationRow);
                    }
                    else
                    {
                        var fkDestinationExists = false;
                        var fkTable             = Metadatas.GetTable(fk);
                        var riFk = new RowIdentifier
                        {
                            ServerId = fk.ServerIdTo,
                            Database = fk.DatabaseTo,
                            Schema   = fk.SchemaTo,
                            Table    = fk.TableTo,
                            Columns  = table.BuildKeyFromDerivativeDataRow(fk, currentRow)
                        };

                        //On ne duplique pas la ligne si la table est statique
                        if (fkTable.IsStatic)
                        {
                            //TODO : Tester si la FK existe dans la table de destination de clônage et non si la fk existe dans la bd source
                            var fkRow = ConnectionsContext.Select(riFk);
                            fkDestinationExists = fkRow.Length == 1;

                            if (fkRow.Length > 1)
                            {
                                throw new Exception("The FK is not unique.");
                            }

                            //Si la ligne existe déjà, on l'utilise
                            if (fkDestinationExists)
                            {
                                //Sauve la clef
                                var colFkObj = fkTable.BuildRawPkFromDataRow(fkRow[0]);
                                _keyRelationships.SetKey(fk.ServerIdTo, fk.DatabaseTo, fk.SchemaTo, fk.TableTo, colFkObj, colFkObj);
                            }
                        }

                        //La FK n'existe pas, on la crer
                        if (!fkDestinationExists)
                        {
                            //Si référence circulaire
                            if (rowsGenerating.Contains(riFk))
                            {
                                //Affecte la FK à 1 pour les contraintes NOT NULL. EnforceIntegrity doit être désactivé.
                                var nullFk = Enumerable.Repeat <object>(1, fk.Columns.Count).ToArray();
                                table.SetFkInDatarow(fk, nullFk, destinationRow);

                                //On ajoute une tâche pour réassigner la FK "correctement", une fois que toute la chaîne aura été enregistrée.
                                _circularKeyJobs.Add(new CircularKeyJob
                                {
                                    SourceBaseRowStartPoint = new RowIdentifier
                                    {
                                        ServerId = sourceRow.ServerId,
                                        Database = sourceRow.Database,
                                        Schema   = sourceRow.Schema,
                                        Table    = sourceRow.Table,
                                        Columns  = table.BuildPkFromDataRow(currentRow)
                                    },
                                    SourceFkRowStartPoint = riFk,
                                    ForeignKey            = fk
                                });
                            }
                            else
                            {
                                //Crer la ligne et ses dépendances
                                rowsGenerating.Push(riFk);
                                var riNewFk = BuildExecutionPlan(riFk, false, true, level + 1, rowsGenerating);
                                rowsGenerating.Pop();

                                var newFkRow = GetDataRow(riNewFk);

                                //Affecte la clef
                                table.SetFkFromDatarowInDatarow(fkTable, fk, newFkRow, destinationRow);
                            }
                        }
                    }
                }

                var step = CreateExecutionStep(sourceRow, tiDestination, table, destinationRow, level);

                //Sauve la PK dans la cache
                dstKey = table.BuildRawPkFromDataRow(step.Datarow);
                _keyRelationships.SetKey(sourceRow.ServerId, sourceRow.Database, sourceRow.Schema, sourceRow.Table, srcKey, dstKey);

                //Ajouter les colonnes de contrainte unique dans _keyRelationships
                //...

                //On affecte la valeur de retour
                if (shouldReturnFk)
                {
                    riReturn.Columns = table.BuildPkFromRawKey(dstKey);
                }

                //On clone les lignes des tables dépendantes
                GetDerivatives(table, currentRow, getDerivatives, level, rowsGenerating);
            }

            return(riReturn);
        }
 public KeyRelationshipTests()
 {
     _keys = new KeyRelationship();
     _keys.SetKey(1, "db", "dbo", "table1", new object[] { 1, 1 }, new object[] { 1, 2 });
     _keys.SetKey(1, "db", "dbo", "table1", new object[] { 1, 2 }, new object[] { 1 });
 }