/// <summary> /// copy row from node (a json object) to target (a daton root or child row), based on tableDef /// </summary> private static void ReadCompatibleJsonRow(JObject node, TableDef tableDef, Row targetRow) { //copy fields in this row foreach (var colDef in tableDef.Cols) { //get value from json or skip var jtoken = node.GetValue(colDef.Name, StringComparison.OrdinalIgnoreCase); if (jtoken == null) { continue; } var value = ParseNode(jtoken, colDef.CSType); //write to row if (colDef.IsCustom) { targetRow.SetCustom(colDef.Name, value); } else { var targetField = tableDef.RowType.GetField(colDef.Name); if (targetField == null) { throw new Exception($"Expected {colDef.Name} to be a member of {tableDef.RowType.Name}"); } targetField.SetValue(targetRow, value); } } //recursively copy child rows if (tableDef.Children != null) { foreach (var childTableDef in tableDef.Children) { //get json node or skip var jtoken = node.GetValue(childTableDef.Name, StringComparison.OrdinalIgnoreCase); if (!(jtoken is JArray jarray)) { continue; } //get target object or skip var targetListField = tableDef.RowType.GetField(childTableDef.Name); if (targetListField == null) { continue; } var listType = targetListField.FieldType; if (!(targetListField is IList) || !listType.IsGenericType) { continue; //is not List<xxx> } var list = Utils.CreateOrGetFieldValue <IList>(targetRow, targetListField); //loop rows foreach (var node2 in jarray) { if (!(node2 is JObject node3)) { throw new Exception("Array elements must be JSON objects"); } var row = Utils.ConstructRow(childTableDef.RowType, childTableDef); list.Add(row); ReadCompatibleJsonRow(node3, childTableDef, row); } } } }
private static void ReadJsonDiffRow(JObject node, PersistonDiff.DiffRow target, TableDef tableDef, bool allowChildren) { //copy fields in this row foreach (var colDef in tableDef.Cols) { //get json value or skip var jtoken = node.GetValue(colDef.Name, StringComparison.OrdinalIgnoreCase); if (jtoken == null) { continue; } //copy var value = ParseNode(jtoken, colDef.CSType); target.Columns[colDef.Name] = value; } //recursively copy child rows if (tableDef.Children != null && allowChildren) { foreach (var childTableDef in tableDef.Children) { if (target.ChildTables == null) { target.ChildTables = new Dictionary <TableDef, List <PersistonDiff.DiffRow> >(); } if (!target.ChildTables.ContainsKey(childTableDef)) { target.ChildTables[childTableDef] = new List <PersistonDiff.DiffRow>(); } ReadJsonDiffRowArray(node, childTableDef, target.ChildTables[childTableDef]); } } }
/// <summary> /// Called from Load after loading the top table to load all other tables. /// The base implementation is careful to only load a table once regardless of the number of detail levels and the number of parent/grandparent /// rows. For example for a persiston whose table structure is (Sale, LineItem, LineItemNote), then there will only be one select statement /// issued for LineItemNote, using an IN clause for the primary keys of LineItem (which were already loaded). /// (The call syntax would be complex to invoke this for the top table so this is called only once for the children of the top /// table.) /// </summary> /// <param name="parentRows">parent Row objects indexed by primary key value</param> protected async Task LoadChildTablesRecursive(Dictionary <object, Row> parentRows, IDbConnection db, DataDictionary dbdef, TableDef parentdef) { if (!parentRows.Any()) { return; } if (parentdef.Children == null) { return; } //get parent keys in the form "1,2,3..." string parentKeyListFormatted = SqlSelectBuilder.FormatInClauseList(parentRows.Keys); foreach (var childTabledef in parentdef.Children) { //get rows - where clause is "parentkey in (...)" var whereClause = new SqlSelectBuilder.Where(); whereClause.AddWhere($"{childTabledef.ParentKeyColumnName} in({parentKeyListFormatted})"); var loadResult = await LoadTable(db, dbdef, childTabledef, whereClause, childTabledef.DefaulSortColName, 0, 0); var rowdict = loadResult.RowsByParentKey; //deal out the rows into the parent objects' Lists of this type var listField = parentdef.RowType.GetField(childTabledef.Name); foreach (object parentKey in rowdict.Keys) { var rowsForParent = rowdict[parentKey]; var parentRow = parentRows[parentKey]; var list = Utils.CreateOrGetFieldValue <IList>(parentRow, listField); foreach (var row in rowsForParent) { list.Add(row); } } //recur var rowsByPK = RestructureByPrimaryKey(childTabledef, rowdict); await LoadChildTablesRecursive(rowsByPK, db, dbdef, childTabledef); } }
/// <summary> /// Given a single row of changes (add/new/delete) and a list, either delete or update the list row with the matching primary key, /// or add a new row. /// </summary> /// <returns>true if any actual changes</returns> private bool ApplyDiffRowToList(TableDef tabledef, DiffRow source, IList targetList) { bool anyChanges = false; if (tabledef.PrimaryKeyColName == null) { throw new Exception("Primary key not defined"); } var itemType = targetList.GetType().GenericTypeArguments[0]; var pkField = itemType.GetField(tabledef.PrimaryKeyColName); if (pkField == null) { throw new Exception($"Primary key field not found in type {itemType.Name}"); } if (source.Kind == DiffKind.DeletedRow) { if (!source.Columns.TryGetValue(tabledef.PrimaryKeyColName, out object pkToDelete)) { throw new Exception("Deleted row in diff needs primary key member"); } int idxToDelete = Utils.IndexOfPrimaryKeyMatch(targetList, pkField, pkToDelete); if (idxToDelete >= 0) { targetList.RemoveAt(idxToDelete); anyChanges = true; } } else //new or update { //find or create row Row target; if (source.Kind == DiffKind.NewRow) { target = Utils.Construct(itemType) as Row; targetList.Add(target); anyChanges = true; } else { if (!source.Columns.TryGetValue(tabledef.PrimaryKeyColName, out object pkToUpdate)) { throw new Exception("Updated row in diff needs primary key member"); } int idxToUpdate = Utils.IndexOfPrimaryKeyMatch(targetList, pkField, pkToUpdate); if (idxToUpdate < 0) { throw new Exception("Row to update is not found"); } target = targetList[idxToUpdate] as Row; } //copy values foreach (string colName in source.Columns.Keys) { anyChanges |= SetValue(tabledef, source, colName, target); } //process child tables anyChanges |= ApplyChildTables(source, target, itemType); } return(anyChanges); }
/// <summary> /// Load rows given by where clause for a table and return them as dictionary indexed by the parent key value. /// If there is no parent key for the table, then the dictionary will have one entry with key="". /// The returned list members are objects of the type indicated by tabledef. /// The implementation loads one additional row to determine whether the load was complete, if pageSize is nonzero. /// </summary> /// <param name="pageSize">if zero, does not do paging</param> /// <param name="whereClause">can be null</param> protected virtual Task <SingleLoadResult> LoadTable(IDbConnection db, DataDictionary dbdef, TableDef tabledef, SqlSelectBuilder.Where whereClause, string sortColName, int pageSize, int pageNo) { var colInfos = BuildColumnsToLoadList(dbdef, tabledef); string parentKeyColName = tabledef.ParentKeyColumnName; int parentKeyColIndex = -1; var columnNames = colInfos.Select(c => c.SqlExpression).ToList(); if (parentKeyColName != null) { parentKeyColIndex = columnNames.Count; columnNames.Add(parentKeyColName); } int customColIndex = -1; if (tabledef.HasCustomColumns) { customColIndex = columnNames.Count; columnNames.Add(CUSTOMCOLNAME); } var sql = new SqlSelectBuilder(SqlFlavor, tabledef.SqlTableName, sortColName, columnNames) { PageSize = pageSize, PageNo = pageNo }; if (whereClause != null) { sql.WhereClause = whereClause; } var rowsByParentKey = new Dictionary <object, List <Row> >(); bool isComplete = true; using (var cmd = db.CreateCommand()) { cmd.CommandText = CustomizeSqlStatement(sql.ToString()); if (whereClause != null) { whereClause.ExportParameters(cmd); } using (var reader = cmd.ExecuteReader()) { int rowsLoaded = 0; while (reader.Read()) { //if this is the throw-away row that was one more than the page size, then note as incomplete but don't look at it if (++rowsLoaded > pageSize && pageSize > 0) { isComplete = false; break; } //read parent key value object pk = ""; if (parentKeyColIndex >= 0) { pk = reader.GetValue(parentKeyColIndex); } //read remaining values var row = Utils.Construct(tabledef.RowType) as Row; SetRowFromReader(colInfos, reader, row); //read custom values if (tabledef.HasCustomColumns) { string json = (string)Utils.ChangeType(reader.GetValue(customColIndex), typeof(string)); if (!string.IsNullOrEmpty(json)) { SetRowFromCustomValues(json, tabledef, row); } } //store in return dict if (!rowsByParentKey.ContainsKey(pk)) { rowsByParentKey[pk] = new List <Row>(); } rowsByParentKey[pk].Add(row); } } } var ret = new SingleLoadResult { RowsByParentKey = rowsByParentKey, IsComplete = isComplete }; return(Task.FromResult(ret)); }