internal static void CopyParsePropertyValues(IDictionary <string, object> sourceProps, object source, object target, bool isNew, ServiceScope ss, IDictionary <object, object> visits, bool justTraverse) { // Recursively parse property values for an object graph. This not only adjusts collection types to be trackable concrete types, but registers child objects into the current service scope. _propCache.TryGetValue(target.GetType(), out var dic); if (dic == null) { dic = (from a in target.FastGetAllProperties(true, true) select new { Name = a.name, PropertyType = a.type }).ToDictionary((p) => p.Name, (p) => p.PropertyType); _propCache[target.GetType()] = dic; } var iter = sourceProps == null ? (from t in dic select(t.Key, target.FastGetValue(t.Key), t.Value)) : (from s in sourceProps from t in dic where s.Key == t.Key select(s.Key, s.Value, t.Value)); var maxdop = Globals.EnableParallelPropertyParsing && Environment.ProcessorCount > 4 && iter.Count() >= Environment.ProcessorCount ? Environment.ProcessorCount >> 2 : 1; Interlocked.Add(ref _copyNesting, maxdop); try { Action <(string PropName, object SourceVal, Type TargPropType)> a = ((string PropName, object SourceVal, Type TargPropType)info) => { object wrapped = null; if (ss != null && IsWrappableListType(info.TargPropType, info.SourceVal)) { ICEFList wrappedCol = null; if (ss.Settings.InitializeNullCollections || info.SourceVal != null) { // This by definition represents CHILDREN // Use an observable collection we control - namely EntitySet wrappedCol = CreateWrappingList(ss, info.TargPropType, target, info.PropName); target.FastSetValue(info.PropName, wrappedCol); } else { wrappedCol = info.SourceVal as ICEFList; } // Merge any existing data into the collection - as we do this, recursively construct wrappers! if (info.SourceVal != null && wrappedCol != null) { // Based on the above type checks, we know this thing supports IEnumerable var sValEnum = ((System.Collections.IEnumerable)info.SourceVal).GetEnumerator(); while (sValEnum.MoveNext()) { if (visits.ContainsKey(sValEnum.Current)) { wrapped = visits[sValEnum.Current] ?? sValEnum.Current; } else { wrapped = ss.InternalCreateAddBase(sValEnum.Current, isNew, null, null, null, visits, true, true); } wrappedCol.AddWrappedItem(wrapped); } } if (ss.Settings.InitializeNullCollections || info.SourceVal != null) { ((ISupportInitializeNotification)wrappedCol).EndInit(); } } else { // If the type is a ref type that we manage, then this property represents a PARENT and we should replace/track it (only if we have a PK for it: without one, can't be tracked) if (ss != null && info.SourceVal != null) { var svt = info.SourceVal.GetType(); bool svtok; if (!_sourceValTypeOk.TryGetValue(svt, out svtok)) { svtok = !svt.IsValueType && svt != typeof(string) && KeyService.ResolveKeyDefinitionForType(svt).Any(); _sourceValTypeOk[svt] = svtok; } if (svtok) { if (visits.ContainsKey(info.SourceVal)) { wrapped = visits[info.SourceVal] ?? info.SourceVal; } else { var to = ss.GetTrackedByWrapperOrTarget(info.SourceVal); if (to == null) { wrapped = ss.InternalCreateAddBase(info.SourceVal, isNew, null, null, null, visits, true, true); } else { wrapped = to.GetWrapperTarget(); } } if (wrapped != null) { target.FastSetValue(info.PropName, wrapped); } } else { if (!justTraverse) { target.FastSetValue(info.PropName, info.SourceVal); } } } else { if (!justTraverse) { target.FastSetValue(info.PropName, info.SourceVal); } } } }; int resdop = Interlocked.Read(ref _copyNesting) > 12 ? 1 : maxdop; if (resdop == 1) { foreach (var info in iter) { a.Invoke(info); } } else { Parallel.ForEach(iter, new ParallelOptions() { MaxDegreeOfParallelism = resdop }, (info) => { using (CEF.UseServiceScope(ss)) { a.Invoke(info); } }); } } finally { Interlocked.Add(ref _copyNesting, -maxdop); } }