/// <summary> /// Run-time implementation for copying the relationship data from object readers to corresponding entities. /// </summary> /// <param name="readerEntityPairs">The readers and their corresponding entities.</param> /// <param name="identityReader">Callback for reading the identities from the readers.</param> /// <param name="resourceResolver">Service for resolving identity values.</param> /// <param name="memberName">Name of the member.</param> /// <param name="relationshipId">ID of relationship being updated.</param> /// <param name="direction">Direction that the relationship is being updated.</param> /// <param name="mandatory">Indicates that the member is mandatory.</param> /// <param name="reporter">Target for any errors.</param> /// <param name="reportingName">Member name used for reporting.</param> /// <exception cref="ConnectorRequestException"> /// </exception> private void RelationshipProcessorImpl(IEnumerable <ReaderEntityPair> readerEntityPairs, Func <IObjectReader, string, IReadOnlyCollection <object> > identityReader, IResourceResolver resourceResolver, string memberName, long relationshipId, Direction direction, bool mandatory, IImportReporter reporter, string reportingName) { if (reporter == null) { throw new ArgumentNullException(nameof(reporter)); } // Get identifiers var identities = new HashSet <object>( ); var entitiesToIdentities = new Dictionary <IEntity, IReadOnlyCollection <object> >( ); foreach (ReaderEntityPair pair in readerEntityPairs) { // Resolve targeted entity IReadOnlyCollection <object> targetResourceIdentities; IObjectReader reader = pair.ObjectReader; try { targetResourceIdentities = identityReader(reader, memberName); // Handle missing fields if (mandatory && (!reader.HasKey(memberName) || targetResourceIdentities.Count == 0)) { string message = string.Format(Messages.MandatoryPropertyMissing, reportingName); reporter.ReportError(reader, message); continue; } } catch (FormatException) // hmm .. this is a bit specific to the JSON reader { reporter.ReportError(reader, Messages.IdentifierListContainedNulls); continue; } if (targetResourceIdentities == null) { continue; } foreach (object identity in targetResourceIdentities) { if (identity == null || identity as string == string.Empty) { continue; } identities.Add(identity); } entitiesToIdentities.Add(pair.Entity, targetResourceIdentities); } // Resolve resources IDictionary <object, ResourceResolverEntry> resourceLookup = null; if (identities.Count > 0) { resourceLookup = resourceResolver.ResolveResources(identities.ToList()); } // Update relationships foreach (ReaderEntityPair pair in readerEntityPairs) { IObjectReader reader = pair.ObjectReader; IEntity updateEntity = pair.Entity; IReadOnlyCollection <object> targetIdentities; if (!entitiesToIdentities.TryGetValue(updateEntity, out targetIdentities)) { continue; } // Update relationship var col = updateEntity.GetRelationships(relationshipId, direction); // Clearing is mandatory for lookups; // mandatory for relationships on new instances with default values; // and a good idea for other relationships. // Clearing will reset any default instances, but we've decided this is sensible, otherwise a subsequent update using the // same data would just clear them anyway. Default values do not get applied for mapped columns. col.Clear( ); // Find each target identity in the lookup foreach (object targetIdentity in targetIdentities) { if (targetIdentity == null) { continue; } ResourceResolverEntry entry; if (resourceLookup == null || !resourceLookup.TryGetValue(targetIdentity, out entry)) { reporter.ReportError(reader, Messages.ResourceNotFoundByField); continue; } if (entry.Error != ResourceResolverError.None) { string message = ResourceResolver.FormatResolverError(entry.Error, targetIdentity.ToString( )); reporter.ReportError(reader, message); continue; } IEntity targetEntity = entry.Entity; // And set into the relationship if (targetEntity != null) { col.Add(targetEntity); } } } }