/// <summary> /// Serialization text on the EntitySet level turns into a JSON array, composed of individual object-per-collection member. /// The output format is lighter-weight than the portable format (no schema). /// </summary> /// <param name="mode"></param> /// <returns></returns> public string GetSerializationText(SerializationMode?mode = null) { StringBuilder sb = new StringBuilder(4096); var actmode = mode.GetValueOrDefault(CEF.CurrentServiceScope.Settings.SerializationMode); var st = new SerializationVisitTracker(); CEF.CurrentServiceScope.ReconcileModifiedState(null); using (var jw = new JsonTextWriter(new StringWriter(sb))) { jw.WriteStartArray(); foreach (var i in this) { var iw = i.AsInfraWrapped(); var rs = iw.GetRowState(); if ((rs != ObjectState.Unchanged && rs != ObjectState.Unlinked) || ((actmode & SerializationMode.OnlyChanged) == 0)) { CEF.CurrentPCTService()?.SaveContents(jw, i, actmode, st); } } jw.WriteEndArray(); } return(sb.ToString()); }
/// <summary> /// For a given object, persist its JSON representation to stream. /// </summary> /// <param name="tw"></param> /// <param name="o"></param> /// <param name="mode"></param> /// <param name="visits"></param> /// <returns></returns> public bool SaveContents(JsonTextWriter tw, object o, SerializationMode mode, SerializationVisitTracker visits) { if (visits.Objects.Contains(o)) { return(true); } bool include = true; if ((mode & SerializationMode.OnlyChanged) != 0) { // Point of this? if we have object graph a->b->c, if c is modified, both a and b need to be included even if unmodified to support proper hierarchy include = RequiresPersistenceForChanges(o, mode, new ConcurrentDictionary <object, bool>(Globals.DefaultCollectionConcurrencyLevel, Globals.DefaultDictionaryCapacity)); } if (include) { WriteSerializationText(tw, o, mode, visits); } visits.Objects.Add(o); var wot = o.AsInfraWrapped().GetWrappedObject()?.GetType() ?? o.GetBaseType(); if (!visits.Types.Contains(wot)) { visits.Types.Add(wot); } return(include); }
private void WriteSerializationText(JsonTextWriter tw, object o, SerializationMode mode, SerializationVisitTracker visits) { var iw = o.AsInfraWrapped(false); if (iw != null) { tw.WriteStartObject(); var wot = iw.GetWrappedObject()?.GetType() ?? iw.GetBaseType(); // We only really want/need to include type info on outermost objects (session scope level only), so reset this for all nested objects var nextmode = (SerializationMode)((int)mode & (-1 ^ (int)SerializationMode.IncludeType)); if ((mode & SerializationMode.IncludeType) != 0) { tw.WritePropertyName(Globals.SerializationTypePropertyName); tw.WriteValue(o.GetType().AssemblyQualifiedName); } // Attempt to push enumerable types to the "end" foreach (var kvp in (from a in iw.GetAllValues() orderby a.Value is IEnumerable ? 1 : 0 select a)) { // If it's enumerable, recurse each item // TODO - better way to detect primitive type like string w/o hardcoding?? if (kvp.Value is IEnumerable && kvp.Value.GetType() != typeof(string)) { tw.WritePropertyName(kvp.Key); tw.WriteStartArray(); var asEnum = (kvp.Value as IEnumerable).GetEnumerator(); while (asEnum.MoveNext()) { var i = asEnum.Current; // We only need to do this for tracked objects, for now we only do for value types or non-system (TODO) if (i == null || i.GetType().IsValueType || i.GetType().FullName.StartsWith("System.")) { tw.WriteValue(i); } else { if ((mode & SerializationMode.SingleLevel) == 0) { var iw2 = i.AsInfraWrapped(); if (iw2 != null) { SaveContents(tw, iw2, nextmode, visits); } else { if (i.GetType().IsSerializable) { tw.WriteValue(i); } } } } } tw.WriteEndArray(); } else { // If it's a tracked object, recurse if (kvp.Value != null && !kvp.Value.GetType().IsValueType&& CEF.CurrentServiceScope.GetTrackedByWrapperOrTarget(kvp.Value) != null) { if ((mode & SerializationMode.SingleLevel) == 0) { var iw2 = kvp.Value.AsInfraWrapped(); if (iw2 != null) { tw.WritePropertyName(kvp.Key); SaveContents(tw, iw2, nextmode, visits); } } } else { if ((mode & SerializationMode.ExtendedInfoAsShadowProps) != 0) { var rs = iw.GetRowState(); if (rs == ObjectState.Modified || rs == ObjectState.ModifiedPriority) { var ov = iw.GetOriginalValue(kvp.Key, false); if (ov != null) { tw.WritePropertyName("\\\\" + kvp.Key); tw.WriteValue(ov); } } // We write out schema metadata only when see a type for the first time if (!visits.Types.Contains(wot)) { var pb = wot.GetProperty(kvp.Key); // Preferred data type var pt = (from a in iw.GetAllPreferredTypes() where a.Key == kvp.Key select a.Value).FirstOrDefault() ?? pb?.PropertyType; if (pt != null) { tw.WritePropertyName("\\+" + kvp.Key); tw.WriteValue(pt.AssemblyQualifiedName); // Is writeable tw.WritePropertyName("\\-" + kvp.Key); tw.WriteValue(pb?.CanWrite); } } } if (kvp.Value != null || (mode & SerializationMode.IncludeNull) != 0) { if (((mode & SerializationMode.IncludeReadOnlyProps) != 0) || (wot.GetProperty(kvp.Key)?.CanWrite).GetValueOrDefault(true)) { if (((mode & SerializationMode.OnlyCLRProperties) == 0) || (wot.GetProperty(kvp.Key)?.CanRead).GetValueOrDefault(false)) { var aud = CEF.CurrentAuditService(); if (aud != null) { if (((mode & SerializationMode.OriginalForConcurrency) == 0) || (string.Compare(aud.LastUpdatedDateField, kvp.Key, true) != 0 && string.Compare(aud.IsDeletedField, kvp.Key, true) != 0)) { if (kvp.Value == null || kvp.Value.GetType().IsSerializable) { tw.WritePropertyName(kvp.Key); tw.WriteValue(kvp.Value); } } else { // Only need to send original date - do not send isdeleted at all if (string.Compare(aud.IsDeletedField, kvp.Key, true) != 0) { var rs = iw.GetRowState(); if (rs != ObjectState.Added && rs != ObjectState.Unlinked) { var val = iw.GetOriginalValue(kvp.Key, false); if (val != null) { tw.WritePropertyName(kvp.Key); tw.WriteValue(val); } } } } } } } } } } } if ((mode & SerializationMode.ExtendedInfoAsShadowProps) != 0) { tw.WritePropertyName("_ot_"); tw.WriteValue(wot.Name); } // Allows for inclusion of object state, etc. iw.FinalizeObjectContents(tw, mode); tw.WriteEndObject(); } else { tw.WriteValue(o); } }