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 (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); }