private static async Task <SectionValues> GetV3SectionValuesAsync(
            IUpstreamEdFiApiInvoker invoker,
            HttpRequestHeaders headers,
            SectionLookupKey lookupKey,
            short schoolYearFromRoute)
        {
            // Get the referenced section by natural key
            var response = await invoker.Get(
                typeof(V3Section),
                headers,
                new[]
            {
                new KeyValuePair <string, string>("localCourseCode", lookupKey.LocalCourseCode),
                new KeyValuePair <string, string>("schoolId", lookupKey.SchoolId.ToString()),
                new KeyValuePair <string, string>("schoolYear", lookupKey.SchoolYear.ToString()),
                new KeyValuePair <string, string>("sectionIdentifier", lookupKey.SectionIdentifier),
                new KeyValuePair <string, string>("sessionName", lookupKey.SessionName),
            },
                schoolYearFromRoute)
                           .ConfigureAwait(false);

            StreamReader sr = new StreamReader(response.ResponseStream);
            string       responseContent = await sr.ReadToEndAsync().ConfigureAwait(false);

            if (response.Status == HttpStatusCode.OK)
            {
                // Get the Section-derived members
                var v3Section = JsonConvert.DeserializeObject <V3Section[]>(responseContent, _serializerSettings).SingleOrDefault();

                if (v3Section == null)
                {
                    throw new Exception($"Unable to find referenced Section using the following values: {JsonConvert.SerializeObject(lookupKey, Formatting.Indented)}");
                }

                // Invasive defensive validation against "invalid" data for V2
                if (lookupKey.SchoolId != v3Section.LocationReference.SchoolId)
                {
                    throw new Exception($"Unable to convert the SectionReference from host (v3.1) to client (v2.5) because the LocationReference contains a School that differs from the containing Section, which is not supported under v2.5.");
                }

                // TODO: Validate presence of V2 required values, or ensure that V3 requires them (i.e. via required collections)

                var sectionValues = new SectionValues(
                    classPeriodName: v3Section.SectionClassPeriods.FirstOrDefault()?.ClassPeriodReference.ClassPeriodName ?? "Undefined",
                    classroomIdentificationCode: v3Section.LocationReference.ClassroomIdentificationCode ?? "Undefined",
                    sectionIdentifier: v3Section.SectionIdentifier,
                    sequenceOfCourse: v3Section.SequenceOfCourse ?? -1,
                    retrievedDateTime: DateTime.Now);

                return(sectionValues);
            }

            throw new Exception($"Error obtaining V3 Section resource during mapping of V2 SectionReference: {response.Status} - {responseContent}");
        }
        private static async Task ApplyPropertiesFromV3SectionAsync(
            IUpstreamEdFiApiInvoker invoker,
            HttpRequestHeaders headers,
            V3SectionReference source,
            V2SectionReference destination,
            short schoolYearFromRoute)
        {
            var sectionLookupKey = new SectionLookupKey(source.LocalCourseCode, source.SchoolId, source.SchoolYear, source.SectionIdentifier, source.SessionName);

            await Task.Run(() =>
            {
                var values = _sectionValuesByNaturalKey.GetOrAdd(sectionLookupKey, k =>
                                                                 GetV3SectionValuesAsync(invoker, headers, k, schoolYearFromRoute)
                                                                 .ConfigureAwait(false)
                                                                 .GetAwaiter()
                                                                 .GetResult());

                // Are the retrieved values stale?
                if ((DateTime.Now - values.RetrievedDateTime).TotalSeconds > _cacheDurationSeconds.Value)
                {
                    // Refresh the values
                    SectionValues ignored;
                    _sectionValuesByNaturalKey.TryRemove(sectionLookupKey, out ignored);

                    values = _sectionValuesByNaturalKey.GetOrAdd(sectionLookupKey, k =>
                                                                 GetV3SectionValuesAsync(invoker, headers, k, schoolYearFromRoute)
                                                                 .ConfigureAwait(false)
                                                                 .GetAwaiter()
                                                                 .GetResult());
                }

                // Straight mappings from source
                destination.LocalCourseCode = source.LocalCourseCode;
                destination.SchoolId        = source.SchoolId;
                destination.SchoolYear      = source.SchoolYear;

                // Mappings from upstream host's Section
                destination.ClassPeriodName             = values.ClassPeriodName;
                destination.ClassroomIdentificationCode = values.ClassroomIdentificationCode;
                destination.SequenceOfCourse            = values.SequenceOfCourse;

                // NOTE: Assumes UniqueSectionCode is unique enough to use as the SectionIdentifier.
                destination.UniqueSectionCode = values.SectionIdentifier;
            })
            .ConfigureAwait(false);
        }