private JObject InvokeSerializeDataToJson(Type classType, object data, SyncConfiguration.SchemaInfo schemaInfo, object transaction, OperationType operationType, string synchronizationId, Dictionary <string, object> customInfo) { string json = SerializeDataToJson(classType, data, transaction, operationType, synchronizationId, customInfo); if (string.IsNullOrEmpty(json)) { throw new SyncEngineConstraintException($"{nameof(SerializeDataToJson)} must not return null or empty string"); } JObject jObject = null; try { jObject = JsonConvert.DeserializeObject <JObject>(json); } catch (Exception e) { throw new SyncEngineConstraintException($"The returned value from {nameof(SerializeDataToJson)} cannot be parsed as JSON Object (JObject). Error: {e.Message}. Returned Value: {json}"); } if (!jObject.ContainsKey(schemaInfo.PropertyInfoId.Name)) { throw new SyncEngineConstraintException($"The parsed JSON Object (JObject) does not contain key: {schemaInfo.PropertyInfoId.Name} (SyncProperty Id)"); } if (!jObject.ContainsKey(schemaInfo.PropertyInfoLastUpdated.Name)) { throw new SyncEngineConstraintException($"The parsed JSON Object (JObject) does not contain key: {schemaInfo.PropertyInfoLastUpdated.Name} (SyncProperty LastUpdated)"); } if (!jObject.ContainsKey(schemaInfo.PropertyInfoDeleted.Name)) { throw new SyncEngineConstraintException($"The parsed JSON Object (JObject) does not contain key: {schemaInfo.PropertyInfoDeleted.Name} (SyncProperty Deleted)"); } return(jObject); }
internal void ProvisionExistingData(List <string> log, long timeStamp, object transaction, OperationType operationType, string synchronizationId, Dictionary <string, object> customInfo) { if (log == null) { log = new List <string>(); } log.Add("Provisioning All Existing Local Data with the acquired TimeStamp..."); log.Add($"SyncTypes Count: {SyncConfiguration.SyncTypes.Count}"); for (int i = 0; i < SyncConfiguration.SyncTypes.Count; i++) { Type syncType = SyncConfiguration.SyncTypes[i]; log.Add($"Processing Type: {syncType.Name} ({i + 1} of {SyncConfiguration.SyncTypes.Count})"); int dataCount = 0; SyncConfiguration.SchemaInfo schemaInfo = GetSchemaInfo(SyncConfiguration, syncType); IQueryable queryable = InvokeGetQueryable(syncType, transaction, operationType, synchronizationId, customInfo); queryable = queryable.Where($"{schemaInfo.PropertyInfoDatabaseInstanceId.Name} = null || {schemaInfo.PropertyInfoDatabaseInstanceId.Name} = \"\""); System.Collections.IEnumerator enumerator = queryable.GetEnumerator(); while (enumerator.MoveNext()) { dataCount += 1; object data = enumerator.Current; data.GetType().GetProperty(schemaInfo.PropertyInfoDatabaseInstanceId.Name).SetValue(data, null); data.GetType().GetProperty(schemaInfo.PropertyInfoLastUpdated.Name).SetValue(data, timeStamp); PersistData(syncType, data, false, transaction, operationType, synchronizationId, customInfo); } log.Add($"Type: {syncType.Name} Processed. Provisioned Data Count: {dataCount}"); } }
public void HookPreInsertOrUpdateGlobalTimeStamp(object data) { if (data == null) { throw new NullReferenceException(nameof(data)); } SyncConfiguration.SchemaInfo schemaInfo = GetSchemaInfo(SyncConfiguration, data.GetType()); if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { long nowTicks = GetNowTicks(); if (!IsServerEngine()) { long lastSync = GetClientLastSync(); if (nowTicks <= lastSync && !SyncConfiguration.SyncConfigurationOptions.GlobalTimeStampAllowHooksToUpdateWithOlderSystemDateTime) { throw new SyncEngineConstraintException("System Date and Time is older than the lastSync value"); } } data.GetType().GetProperty(schemaInfo.PropertyInfoLastUpdated.Name).SetValue(data, nowTicks); } else { throw new SyncEngineConstraintException($"Mismatch Hook Method for {SyncConfiguration.TimeStampStrategy}"); } }
internal static SyncLogData FromJObject(JObject jObject, Type syncType, SyncConfiguration.SchemaInfo schemaInfo) { SyncLogData syncLogData = new SyncLogData(); syncLogData.TypeName = syncType.Name; syncLogData.Id = Convert.ToString(jObject[schemaInfo.PropertyInfoId.Name].Value <object>()); syncLogData.LastUpdated = jObject[schemaInfo.PropertyInfoLastUpdated.Name].Value <long>(); syncLogData.Deleted = jObject[schemaInfo.PropertyInfoDeleted.Name].Value <long?>(); syncLogData.JsonData = jObject.ToString(); if (schemaInfo.PropertyInfoFriendlyId != null) { syncLogData.FriendlyId = jObject[schemaInfo.PropertyInfoFriendlyId.Name].Value <string>(); } return(syncLogData); }
public void HookPreInsertOrUpdateDatabaseTimeStamp(object data, object transaction, string synchronizationId, Dictionary <string, object> customInfo) { if (data == null) { throw new NullReferenceException(nameof(data)); } SyncConfiguration.SchemaInfo schemaInfo = GetSchemaInfo(SyncConfiguration, data.GetType()); if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { long timeStamp = InvokeGetNextTimeStamp(); UpdateLocalKnowledgeTimeStamp(timeStamp, synchronizationId, customInfo, null, transaction); data.GetType().GetProperty(schemaInfo.PropertyInfoDatabaseInstanceId.Name).SetValue(data, null); data.GetType().GetProperty(schemaInfo.PropertyInfoLastUpdated.Name).SetValue(data, timeStamp); } else { throw new SyncEngineConstraintException($"Mismatch Hook Method for {SyncConfiguration.TimeStampStrategy}"); } }
private object InvokeDeserializeJsonToExistingData(Type classType, JObject jObject, object data, object localId, object transaction, OperationType operationType, ConflictType conflictType, string synchronizationId, Dictionary <string, object> customInfo, SyncConfiguration.SchemaInfo localSchemaInfo) { object existingData = DeserializeJsonToExistingData(classType, jObject, data, transaction, operationType, conflictType, synchronizationId, customInfo); if (conflictType != ConflictType.NoConflict && existingData == null) { return(null); } if (existingData == null) { throw new SyncEngineConstraintException($"{nameof(DeserializeJsonToExistingData)} must not return null for {nameof(conflictType)} equals to {conflictType.ToString()}"); } if (existingData.GetType().FullName != classType.FullName) { throw new SyncEngineConstraintException($"Expected returned Type: {classType.FullName} during {nameof(DeserializeJsonToExistingData)}, but Type: {existingData.GetType().FullName} is returned instead."); } object existingDataId = classType.GetProperty(localSchemaInfo.PropertyInfoId.Name).GetValue(existingData); if (!existingDataId.Equals(localId)) { throw new SyncEngineConstraintException($"The returned Object Id ({existingDataId}) is different than the existing data Id: {localId}"); } return(existingData); }
internal (JObject typeChanges, int typeChangesCount, long maxTimeStamp, List <SyncLog.SyncLogData> logChanges) GetTypeChanges(long?lastSync, Type syncType, string synchronizationId, Dictionary <string, object> customInfo, List <object> appliedIds) { if (string.IsNullOrEmpty(synchronizationId)) { throw new NullReferenceException(nameof(synchronizationId)); } if (lastSync == null) { lastSync = GetMinValueTicks(); } if (customInfo == null) { customInfo = new Dictionary <string, object>(); } long maxTimeStamp = GetMinValueTicks(); List <SyncLog.SyncLogData> logChanges = new List <SyncLog.SyncLogData>(); SyncConfiguration.SchemaInfo schemaInfo = GetSchemaInfo(SyncConfiguration, syncType); JObject typeChanges = null; JArray datas = new JArray(); OperationType operationType = OperationType.GetChanges; object transaction = StartTransaction(syncType, operationType, synchronizationId, customInfo); try { IQueryable queryable = InvokeGetQueryable(syncType, transaction, operationType, synchronizationId, customInfo); if (appliedIds == null || appliedIds.Count == 0) { queryable = queryable.Where($"{schemaInfo.PropertyInfoLastUpdated.Name} > @0 || ({schemaInfo.PropertyInfoDeleted.Name} != null && {schemaInfo.PropertyInfoDeleted.Name} > @1)", lastSync.Value, lastSync); } else { Type typeId = Type.GetType(schemaInfo.PropertyInfoId.PropertyType, true); MethodInfo miCast = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(typeId); object appliedIdsTypeId = miCast.Invoke(appliedIds, new object[] { appliedIds }); queryable = queryable.Where($"({schemaInfo.PropertyInfoLastUpdated.Name} > @0 || ({schemaInfo.PropertyInfoDeleted.Name} != null && {schemaInfo.PropertyInfoDeleted.Name} > @1)) && !(@2.Contains({schemaInfo.PropertyInfoId.Name}))", lastSync.Value, lastSync, appliedIdsTypeId); } queryable = queryable.OrderBy($"{schemaInfo.PropertyInfoDeleted.Name}, {schemaInfo.PropertyInfoLastUpdated.Name}"); List <dynamic> dynamicDatas = queryable.ToDynamicList(); if (dynamicDatas.Count > 0) { typeChanges = new JObject(); typeChanges[nameof(syncType)] = syncType.Name; typeChanges[nameof(schemaInfo)] = schemaInfo.ToJObject(); foreach (dynamic dynamicData in dynamicDatas) { JObject jObjectData = InvokeSerializeDataToJson(syncType, dynamicData, schemaInfo, transaction, operationType, synchronizationId, customInfo); datas.Add(jObjectData); long lastUpdated = jObjectData[schemaInfo.PropertyInfoLastUpdated.Name].Value <long>(); long?deleted = jObjectData[schemaInfo.PropertyInfoDeleted.Name].Value <long?>(); if (lastUpdated > maxTimeStamp) { maxTimeStamp = lastUpdated; } if (deleted.HasValue && deleted.Value > maxTimeStamp) { maxTimeStamp = deleted.Value; } logChanges.Add(SyncLog.SyncLogData.FromJObject(jObjectData, syncType, schemaInfo)); } typeChanges[nameof(datas)] = datas; } CommitTransaction(syncType, transaction, operationType, synchronizationId, customInfo); } catch (Exception) { RollbackTransaction(syncType, transaction, operationType, synchronizationId, customInfo); throw; } finally { EndTransaction(syncType, transaction, operationType, synchronizationId, customInfo); } return(typeChanges, datas.Count, maxTimeStamp, logChanges); }
internal (Type localSyncType, List <object> appliedIds, List <object> deletedIds) ApplyTypeChanges(List <string> log, List <SyncLog.SyncLogData> inserts, List <SyncLog.SyncLogData> updates, List <SyncLog.SyncLogData> deletes, List <SyncLog.SyncLogConflict> conflicts, JObject typeChanges, string synchronizationId, Dictionary <string, object> customInfo, string sourceDatabaseInstanceId, string destinationDatabaseInstanceId) { if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { if (!string.IsNullOrEmpty(sourceDatabaseInstanceId) || !string.IsNullOrEmpty(destinationDatabaseInstanceId)) { throw new Exception($"{SyncConfiguration.TimeStampStrategy.ToString()} must have {nameof(sourceDatabaseInstanceId)} and {nameof(destinationDatabaseInstanceId)} equals to null"); } } else if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { if (string.IsNullOrEmpty(sourceDatabaseInstanceId) || string.IsNullOrEmpty(destinationDatabaseInstanceId)) { throw new Exception($"{SyncConfiguration.TimeStampStrategy.ToString()} must have {nameof(sourceDatabaseInstanceId)} and {nameof(destinationDatabaseInstanceId)} both not null"); } } else { throw new NotImplementedException(SyncConfiguration.TimeStampStrategy.ToString()); } List <object> appliedIds = new List <object>(); List <object> deletedIds = new List <object>(); Dictionary <string, long> databaseInstanceMaxTimeStamps = new Dictionary <string, long>(); if (inserts == null) { inserts = new List <SyncLog.SyncLogData>(); } if (updates == null) { updates = new List <SyncLog.SyncLogData>(); } if (deletes == null) { deletes = new List <SyncLog.SyncLogData>(); } if (conflicts == null) { conflicts = new List <SyncLog.SyncLogConflict>(); } string syncTypeName = typeChanges["syncType"].Value <string>(); JObject jObjectSchemaInfo = typeChanges["schemaInfo"].Value <JObject>(); SyncConfiguration.SchemaInfo schemaInfo = SyncConfiguration.SchemaInfo.FromJObject(jObjectSchemaInfo); string localSyncTypeName = syncTypeName; if (!string.IsNullOrEmpty(schemaInfo.SyncSchemaAttribute.MapToClassName)) { localSyncTypeName = schemaInfo.SyncSchemaAttribute.MapToClassName; } Type localSyncType = SyncConfiguration.SyncTypes.Where(w => w.Name == localSyncTypeName).FirstOrDefault(); if (localSyncType == null) { throw new SyncEngineConstraintException($"Unable to find SyncType: {localSyncTypeName} in SyncConfiguration"); } SyncConfiguration.SchemaInfo localSchemaInfo = GetSchemaInfo(SyncConfiguration, localSyncType); OperationType operationType = OperationType.ApplyChanges; object transaction = StartTransaction(localSyncType, operationType, synchronizationId, customInfo); try { IQueryable queryable = InvokeGetQueryable(localSyncType, transaction, operationType, synchronizationId, customInfo); JArray datas = typeChanges["datas"].Value <JArray>(); log.Add($"Data Count: {datas.Count}"); for (int i = 0; i < datas.Count; i++) { JObject jObjectData = datas[i].Value <JObject>(); JValue id = jObjectData[schemaInfo.PropertyInfoId.Name].Value <JValue>(); long lastUpdated = jObjectData[schemaInfo.PropertyInfoLastUpdated.Name].Value <long>(); long? deletedGlobalTimeStamp = null; bool deletedDatabaseTimeStamp = false; string databaseInstanceId = null; if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { deletedGlobalTimeStamp = jObjectData[schemaInfo.PropertyInfoDeleted.Name].Value <long?>(); } if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { deletedDatabaseTimeStamp = jObjectData[schemaInfo.PropertyInfoDeleted.Name].Value <bool>(); databaseInstanceId = jObjectData[schemaInfo.PropertyInfoDatabaseInstanceId.Name].Value <string>(); } object localId = TransformIdType(localSyncType, id, transaction, operationType, synchronizationId, customInfo); dynamic dynamicData = queryable.Where($"{localSchemaInfo.PropertyInfoId.Name} == @0", localId).FirstOrDefault(); object localData = (object)dynamicData; if (localData == null) { object newData = InvokeDeserializeJsonToNewData(localSyncType, jObjectData, transaction, operationType, synchronizationId, customInfo); newData.GetType().GetProperty(localSchemaInfo.PropertyInfoId.Name).SetValue(newData, localId); newData.GetType().GetProperty(localSchemaInfo.PropertyInfoLastUpdated.Name).SetValue(newData, lastUpdated); if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { newData.GetType().GetProperty(localSchemaInfo.PropertyInfoDeleted.Name).SetValue(newData, deletedGlobalTimeStamp); } if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { newData.GetType().GetProperty(localSchemaInfo.PropertyInfoDeleted.Name).SetValue(newData, deletedDatabaseTimeStamp); newData.GetType().GetProperty(localSchemaInfo.PropertyInfoDatabaseInstanceId.Name).SetValue(newData, GetCorrectDatabaseInstanceId(databaseInstanceId, sourceDatabaseInstanceId, destinationDatabaseInstanceId, databaseInstanceMaxTimeStamps, lastUpdated)); } PersistData(localSyncType, newData, true, transaction, operationType, synchronizationId, customInfo); if (!appliedIds.Contains(localId)) { appliedIds.Add(localId); } inserts.Add(SyncLog.SyncLogData.FromJObject(InvokeSerializeDataToJson(localSyncType, newData, localSchemaInfo, transaction, operationType, synchronizationId, customInfo), localSyncType, localSchemaInfo)); } else { bool isDeleted = false; if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { if (deletedGlobalTimeStamp != null) { isDeleted = true; } } if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { if (deletedDatabaseTimeStamp) { isDeleted = true; } } if (!isDeleted) { ConflictType updateConflictType = ConflictType.NoConflict; long localLastUpdated = (long)localData.GetType().GetProperty(localSchemaInfo.PropertyInfoLastUpdated.Name).GetValue(localData); if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { if (localLastUpdated > lastUpdated) { updateConflictType = ConflictType.ExistingDataIsNewerThanIncomingData; } } if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { string localDatabaseInstanceId = (string)localData.GetType().GetProperty(localSchemaInfo.PropertyInfoDatabaseInstanceId.Name).GetValue(localData); string correctDatabaseInstanceId = GetCorrectDatabaseInstanceId(databaseInstanceId, sourceDatabaseInstanceId, destinationDatabaseInstanceId, null, 0); if (localDatabaseInstanceId == correctDatabaseInstanceId) { if (localLastUpdated > lastUpdated) { updateConflictType = ConflictType.ExistingDataIsNewerThanIncomingData; } } else { updateConflictType = ConflictType.ExistingDataIsUpdatedByDifferentDatabaseInstanceId; } } object existingData = InvokeDeserializeJsonToExistingData(localSyncType, jObjectData, localData, localId, transaction, operationType, updateConflictType, synchronizationId, customInfo, localSchemaInfo); if (existingData == null && updateConflictType == ConflictType.NoConflict) { throw new SyncEngineConstraintException($"{nameof(DeserializeJsonToExistingData)} must not return null for conflictType equals to {ConflictType.NoConflict.ToString()}"); } if (existingData != null) { existingData.GetType().GetProperty(localSchemaInfo.PropertyInfoLastUpdated.Name).SetValue(existingData, lastUpdated); if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { existingData.GetType().GetProperty(localSchemaInfo.PropertyInfoDatabaseInstanceId.Name).SetValue(existingData, GetCorrectDatabaseInstanceId(databaseInstanceId, sourceDatabaseInstanceId, destinationDatabaseInstanceId, databaseInstanceMaxTimeStamps, lastUpdated)); } PersistData(localSyncType, existingData, false, transaction, operationType, synchronizationId, customInfo); if (!appliedIds.Contains(localId)) { appliedIds.Add(localId); } updates.Add(SyncLog.SyncLogData.FromJObject(InvokeSerializeDataToJson(localSyncType, existingData, localSchemaInfo, transaction, operationType, synchronizationId, customInfo), localSyncType, localSchemaInfo)); } else { log.Add($"CONFLICT Detected: {updateConflictType.ToString()}. Id: {id}"); conflicts.Add(new SyncLog.SyncLogConflict(updateConflictType, SyncLog.SyncLogData.FromJObject(jObjectData, localSyncType, schemaInfo))); } } else { object existingData = InvokeDeserializeJsonToExistingData(localSyncType, jObjectData, localData, localId, transaction, operationType, ConflictType.NoConflict, synchronizationId, customInfo, localSchemaInfo); if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.GlobalTimeStamp) { existingData.GetType().GetProperty(localSchemaInfo.PropertyInfoDeleted.Name).SetValue(existingData, deletedGlobalTimeStamp); } if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { existingData.GetType().GetProperty(localSchemaInfo.PropertyInfoDeleted.Name).SetValue(existingData, deletedDatabaseTimeStamp); existingData.GetType().GetProperty(localSchemaInfo.PropertyInfoDatabaseInstanceId.Name).SetValue(existingData, GetCorrectDatabaseInstanceId(databaseInstanceId, sourceDatabaseInstanceId, destinationDatabaseInstanceId, databaseInstanceMaxTimeStamps, lastUpdated)); } PersistData(localSyncType, existingData, false, transaction, operationType, synchronizationId, customInfo); if (!appliedIds.Contains(localId)) { appliedIds.Add(localId); } if (!deletedIds.Contains(localId)) { deletedIds.Add(localId); } deletes.Add(SyncLog.SyncLogData.FromJObject(InvokeSerializeDataToJson(localSyncType, existingData, localSchemaInfo, transaction, operationType, synchronizationId, customInfo), localSyncType, localSchemaInfo)); } } } if (SyncConfiguration.TimeStampStrategy == SyncConfiguration.TimeStampStrategyEnum.DatabaseTimeStamp) { foreach (var item in databaseInstanceMaxTimeStamps) { KnowledgeInfo knowledgeInfo = GetAllKnowledgeInfos(synchronizationId, customInfo).Where(w => w.DatabaseInstanceId == item.Key).FirstOrDefault(); if (knowledgeInfo != null && knowledgeInfo.MaxTimeStamp > item.Value) { continue; } if (knowledgeInfo == null && item.Key == destinationDatabaseInstanceId) { throw new SyncEngineConstraintException("Unexpected Knowledge Info State on Destination. Destination is not provisioned yet."); } if (knowledgeInfo == null) { knowledgeInfo = new KnowledgeInfo() { DatabaseInstanceId = item.Key, IsLocal = false }; } knowledgeInfo.MaxTimeStamp = item.Value; CreateOrUpdateKnowledgeInfo(knowledgeInfo, synchronizationId, customInfo); } } CommitTransaction(localSyncType, transaction, operationType, synchronizationId, customInfo); } catch (Exception) { RollbackTransaction(localSyncType, transaction, operationType, synchronizationId, customInfo); throw; } finally { EndTransaction(localSyncType, transaction, operationType, synchronizationId, customInfo); } return(localSyncType, appliedIds, deletedIds); }
internal (JObject typeChanges, int typeChangesCount, List <SyncLog.SyncLogData> logChanges) GetTypeChangesByKnowledge(Type syncType, string localDatabaseInstanceId, List <KnowledgeInfo> remoteKnowledgeInfos, string synchronizationId, Dictionary <string, object> customInfo) { if (string.IsNullOrEmpty(synchronizationId)) { throw new NullReferenceException(nameof(synchronizationId)); } if (customInfo == null) { customInfo = new Dictionary <string, object>(); } List <SyncLog.SyncLogData> logChanges = new List <SyncLog.SyncLogData>(); SyncConfiguration.SchemaInfo schemaInfo = GetSchemaInfo(SyncConfiguration, syncType); JObject typeChanges = null; JArray datas = new JArray(); OperationType operationType = OperationType.GetChanges; object transaction = StartTransaction(syncType, operationType, synchronizationId, customInfo); try { IQueryable queryable = InvokeGetQueryable(syncType, transaction, operationType, synchronizationId, customInfo); string predicate = ""; string predicateUnknown = ""; for (int i = 0; i < remoteKnowledgeInfos.Count; i++) { KnowledgeInfo info = remoteKnowledgeInfos[i]; string knownDatabaseInstanceId = null; if (info.DatabaseInstanceId == localDatabaseInstanceId) { knownDatabaseInstanceId = "null"; } else { knownDatabaseInstanceId = $"\"{info.DatabaseInstanceId}\""; } if (!string.IsNullOrEmpty(predicate)) { predicate += " || "; } predicate += "("; predicate += $"{schemaInfo.PropertyInfoDatabaseInstanceId.Name} = {knownDatabaseInstanceId} "; predicate += $" && {schemaInfo.PropertyInfoLastUpdated.Name} > {info.MaxTimeStamp}"; predicate += ")"; if (!string.IsNullOrEmpty(predicateUnknown)) { predicateUnknown += " && "; } predicateUnknown += $"{schemaInfo.PropertyInfoDatabaseInstanceId.Name} != {knownDatabaseInstanceId}"; } queryable = queryable.Where($"{predicate} || ({predicateUnknown})"); queryable = queryable.OrderBy($"{schemaInfo.PropertyInfoDeleted.Name}, {schemaInfo.PropertyInfoLastUpdated.Name}"); List <dynamic> dynamicDatas = queryable.ToDynamicList(); if (dynamicDatas.Count > 0) { typeChanges = new JObject(); typeChanges[nameof(syncType)] = syncType.Name; typeChanges[nameof(schemaInfo)] = schemaInfo.ToJObject(); foreach (dynamic dynamicData in dynamicDatas) { JObject jObjectData = InvokeSerializeDataToJson(syncType, dynamicData, schemaInfo, transaction, operationType, synchronizationId, customInfo); datas.Add(jObjectData); logChanges.Add(SyncLog.SyncLogData.FromJObject(jObjectData, syncType, schemaInfo)); } typeChanges[nameof(datas)] = datas; } CommitTransaction(syncType, transaction, operationType, synchronizationId, customInfo); } catch (Exception) { RollbackTransaction(syncType, transaction, operationType, synchronizationId, customInfo); throw; } finally { EndTransaction(syncType, transaction, operationType, synchronizationId, customInfo); } return(typeChanges, datas.Count, logChanges); }