/// <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 }); }