internal readonly Func <Phase, int, PhaseStatus> _processingStatusSupplier; // internal to allow ReadOnlyCluster ctor (& record specific transform providers) use the same processingStatusSupplier //Note that KeyValCluster is constructed eagerly (recList will be consumed upon object creation) internal KeyValCluster(IEnumerable <IRecord> recList, int clstrNo, int startRecNo, int startSourceNo, IGlobalCache globalCache, IDictionary <string, object> propertyBin, TypeDefinitions typeDefinitions, OrchestratorConfig config, Func <Phase, int, PhaseStatus> processingStatusSupplier) { this.ClstrNo = clstrNo; this.StartRecNo = startRecNo; this.StartSourceNo = startSourceNo; //Note that StartSourceNo should match the 1st record, but to facilitate cloning, etc. it was decided to expose it // separately as ctor parm as opposed to reading it like this: this.StartSourceNo = recList.Any() ? recList.First().SourceNo : 0; this._recordColl = new RecordCollection(recList); this.GlobalCache = globalCache; this._typeDefinitions = typeDefinitions; this._config = config; this._processingStatusSupplier = processingStatusSupplier; //Make sure all records have the ClstrNo matching the cluster they belong to: this._recordColl.ForEach(r => (r as KeyValRecord)?.SetClstrNo(clstrNo)); PropertyBin = (_config.PropertyBinEntities & PropertyBinAttachedTo.Clusters) == PropertyBinAttachedTo.Clusters ? propertyBin ?? new Dictionary <string, object>() //"reuse" PB in case of cloning, creation of ReadOnlyCluster wrapper, etc. : null; //null if Clusters flag not set in PropertyBinEntities } //ctor
private readonly bool _fieldsCanBeAltered; //Reflects config.AllowTransformToAlterFields. // If true, fields can be added to and removed from the record; // if false, AddItem (flavors) and RemoveItem methods have no effect. /// <summary> /// Constructs a single record consisting of key value pairs. /// </summary> /// <param name="items">A collection of KeyValItems (a set of key value pairs) obtained from a single input record.</param> /// <param name="recNo">Record number, i.e. position in input data (1-based).</param> /// <param name="sourceNo">Index number of the source that supplied this record (1-based).</param> /// <param name="clstrNo">Sequential number (1-based) that the record is initially assigned (e.g. XML or JSON input); 0=undetermined;.</param> /// <param name="globalCache">A set of key value pairs that are common to all records and clusters throughout the process execution.</param> /// <param name="traceBin">The trace bin to be used by this record.</param> /// <param name="propertyBin">The property bin to be used by this record.</param> /// <param name="typeDefinitions">Field type definitions (name -> type translations).</param> /// <param name="config">Current orchestrator configuration.</param> /// <param name="processingStatusSupplier">Function to return the PhaseStatus object.</param> /// <param name="actionOnDuplicateKey">Action to take in case duplicate key is encountered; same as config.ActionOnDuplicateKey except for cloning.</param> internal KeyValRecord(IEnumerable <IItem> items, int recNo, int sourceNo, int clstrNo, IGlobalCache globalCache, IReadOnlyDictionary <string, object> traceBin, IDictionary <string, object> propertyBin, TypeDefinitions typeDefinitions, OrchestratorConfig config, Func <Phase, int, PhaseStatus> processingStatusSupplier, ActionOnDuplicateKey actionOnDuplicateKey) { _itemColl = new ItemCollection(); GlobalCache = globalCache; _typeDefinitions = typeDefinitions; _config = config; _processingStatusSupplier = processingStatusSupplier; _fieldsCanBeAltered = config.AllowTransformToAlterFields; RecNo = recNo; SourceNo = sourceNo; TargetNo = 0; //not yet determined ClstrNo = clstrNo; int fldNo = 0; foreach (IItem item in items) { fldNo++; var key = item.Key; if (_itemColl.Contains(key)) //not thread-safe, but we're in ctor so that nothing else can access _itemColl { //Duplicate key encountered, the action is dictated by actionOnDuplicateKey string msgOnDemand() => $"Duplicate key in field #{fldNo} of record #{recNo}: A key of '{item.Key.ToString()}' already exists in current record."; switch (actionOnDuplicateKey) { case ActionOnDuplicateKey.IgnoreItem: //Do nothing, except for reporting the error _config.Logger.LogWarning(() => msgOnDemand() + $" Item containing value of '{item.Value.ToString()}' has been ignored."); break; case ActionOnDuplicateKey.ReplaceItem: //remove existing item and then add the new item _config.Logger.LogWarning(() => msgOnDemand() + $" Item has been replaced with a value of '{item.Value.ToString()}'."); _itemColl.Remove(item.Key); _itemColl.Add(item); break; case ActionOnDuplicateKey.ExcludeRecord: //Clear the items constructed so far and exit; the caller will ignore such record _itemColl.Clear(); _config.Logger.LogError(() => msgOnDemand() + "Record has been excluded from processing."); return; case ActionOnDuplicateKey.AssignDefaultKey: var errMsg = "Feature to provide substitutes for duplicate keys (ActionOnDuplicateKey.AssignDefaultKey) has not been implemented."; _config.Logger.LogError(() => msgOnDemand() + errMsg); throw new NotImplementedException(errMsg); //TODO: Implement this feature - note that any "default" value can also already exist } ; } else //The key is not a duplicate, so simply add the item { _itemColl.Add(item); } } //foreach item TraceBin = traceBin; //"reuse" TB in case of cloning, creation of ReadOnlyRecord wrapper, etc. PropertyBin = (_config.PropertyBinEntities & PropertyBinAttachedTo.Records) == PropertyBinAttachedTo.Records ? propertyBin ?? new Dictionary <string, object>() //"reuse" PB in case of cloning, creation of ReadOnlyRecord wrapper, etc. : null; //null if Records flag not set in PropertyBinEntities } //ctor