protected override void PopulateWriterColumns(SqlWriteBuilder builder, RowChangingData cdata, bool includePrimaryKey) { base.PopulateWriterColumns(builder, cdata, includePrimaryKey); //change the Notes value during RetroDRY saving if (cdata.ModifiedRow is Customer cust) { builder.ChangeValue("Notes", ">" + cust.Notes); } }
/// <summary> /// Add all non-key values from a diff row to a SqlWriteBuilder /// </summary> protected virtual void PopulateWriterColumns(SqlWriteBuilder builder, RowChangingData cdata, bool includePrimaryKey) { var customColValues = new Dictionary <string, object>(); //contents must be json compatible types //write to builder; write custom cols to temporary dictionary foreach (var coldef in cdata.TableDef.Cols) { if (coldef.IsComputed) { continue; } if (!includePrimaryKey && coldef.Name == cdata.TableDef.PrimaryKeyColName) { continue; } if (!cdata.DiffRow.Columns.TryGetValue(coldef.Name, out object value)) { continue; } if (coldef.IsCustom) { customColValues[coldef.Name] = value; } else { builder.AddNonKey(coldef.Name, coldef.WireType, value); } } //if any custom columns are to be written, include CustomValues column with old and new values/ //Example: if custom cols A and B exist, and the current write only updates A, we still need to include the pristine value of B in the json if (customColValues.Any()) { if (cdata.PristineRow != null) { foreach (var coldef in cdata.TableDef.Cols.Where(c => c.IsCustom)) { if (customColValues.ContainsKey(coldef.Name)) { continue; //don't overwrite old over new } customColValues[coldef.Name] = cdata.PristineRow.GetCustom(coldef.Name); } } string json = CustomValuesToJson(cdata.TableDef, customColValues); builder.AddNonKey(CUSTOMCOLNAME, null, json, useJson: true); } }
/// <summary> /// Insert or update a row /// </summary> /// <returns>database assigned parent key (only if changed) else null</returns> protected async Task <object> InsertUpdateRow(IDbConnection db, RowChangingData cdata) { if (cdata.DiffRow.Kind == DiffKind.NewRow) { var builder = new SqlInsertBuilder(SqlFlavor, CustomizeSqlStatement); bool dbAssignsKey = cdata.TableDef.DatabaseAssignsKey; PopulateWriterColumns(builder, cdata, !dbAssignsKey); if (cdata.TableDef.ParentKeyColumnName != null) { builder.AddNonKey(cdata.TableDef.ParentKeyColumnName, null, cdata.ParentKey); } var newKeyValue = await builder.Execute(db, cdata.TableDef.SqlTableName, cdata.TableDef.PrimaryKeyColName, dbAssignsKey); //populate the new key value in Modified persiston's row if (newKeyValue != null) { var rr = new RowRecurPoint() { TableDef = cdata.TableDef, Row = cdata.ModifiedRow }; rr.SetPrimaryKey(newKeyValue); } return(newKeyValue); } if (cdata.DiffRow.Kind == DiffKind.Other) { var builder = new SqlUpdateBuilder(SqlFlavor, CustomizeSqlStatement); PopulateWriterColumns(builder, cdata, false); if (!cdata.DiffRow.Columns.TryGetValue(cdata.TableDef.PrimaryKeyColName, out object pkValue)) { throw new Exception($"Cannot update row in {cdata.TableDef.Name} because no primary key was found in diff"); } if (builder.NonKeyCount > 0) { await builder.Execute(db, cdata.TableDef.SqlTableName, cdata.TableDef.PrimaryKeyColName, pkValue); } } return(null); }
/// <summary> /// Call ProcessRowF on all rows in the diff list, and recurse to all child tables. /// </summary> /// <param name="singleMainPristineRow">only set for top level call if there is a single main row</param> /// <param name="singleMainModifiedRow">only set for top level call if there is a single main row</param> protected async Task TraverseDiffList(TraversalData tdata, Row singleMainPristineRow, Row singleMainModifiedRow) { var pkField = tdata.TableDef.RowType.GetField(tdata.TableDef.PrimaryKeyColName); foreach (var row in tdata.DiffRowList) { if (!row.Columns.TryGetValue(tdata.TableDef.PrimaryKeyColName, out object rowKey)) { throw new Exception($"Diff row is missing primary key {tdata.TableDef.PrimaryKeyColName}"); } //find pristine row Row pristineRow = singleMainPristineRow; if (tdata.PristineList != null && row.Kind != DiffKind.NewRow) { int rowIdx = Utils.IndexOfPrimaryKeyMatch(tdata.PristineList, pkField, rowKey); if (rowIdx >= 0) { pristineRow = tdata.PristineList[rowIdx] as Row; } } //find modified row Row modifiedRow = singleMainModifiedRow; if (tdata.ModifiedList != null) { int rowIdx = Utils.IndexOfPrimaryKeyMatch(tdata.ModifiedList, pkField, rowKey); if (rowIdx >= 0) { modifiedRow = tdata.ModifiedList[rowIdx] as Row; } } //callback to process row var cdata = new RowChangingData { ParentKey = tdata.ParentKey, DiffRow = row, DiffRowList = tdata.DiffRowList, TableDef = tdata.TableDef, PristineRow = pristineRow, ModifiedRow = modifiedRow }; (object newPK, bool doTraverseChildren) = await tdata.ProcessRowF?.Invoke(cdata); if (newPK != null) { rowKey = newPK; } //skip child tables? if (!doTraverseChildren) { continue; } //loop child tables of this row if (row.ChildTables != null) { foreach (var child in row.ChildTables) { var childTableDef = child.Key; var childRows = child.Value; var listField = tdata.TableDef.RowType.GetField(childTableDef.Name); //get pristine list IList childPristineList = null; if (pristineRow != null) { childPristineList = listField.GetValue(pristineRow) as IList; } //get modified list IList childModifiedList = null; if (modifiedRow != null) { childModifiedList = listField.GetValue(modifiedRow) as IList; } var childTdata = new TraversalData { ParentKey = rowKey, TableDef = childTableDef, DiffRowList = childRows, PristineList = childPristineList, ModifiedList = childModifiedList, ProcessRowF = tdata.ProcessRowF }; await TraverseDiffList(childTdata, null, null); } } } }