/// <summary> /// This is a common place where a real id is assigned to all client inserts. /// </summary> /// <param name="clientUploads">Collection of entities to check</param> private static void AssignRealIdsForClientInserts(IList <IOfflineEntity> clientUploads) { // Iterate over entities that dont have a ID which means its an client insert foreach (IOfflineEntity entity in clientUploads.Where(e => string.IsNullOrEmpty(e.ServiceMetadata.Id))) { entity.ServiceMetadata.Id = WebUtil.GenerateOfflineEntityId(entity); } }
// This method ensures that we have a valid feed through the formatters. The BodyWriter delegate in WCF // seems to not recover from an unhandled exception caused by the formatters and sends out an empty response to the caller. // This is a workaround for this issue until we find a better solution. private SyncWriter GetSyncWriterWithContents() { // Get the appropriate SyncWriter instance based on the serialization format. SyncWriter oDataWriter = WebUtil.GetSyncWriter(_responseSerializationFormat, _baseUri); oDataWriter.StartFeed(_getChangesResponse.IsLastBatch, _getChangesResponse.ServerBlob); // Write entities for response. foreach (var entity in _getChangesResponse.EntityList) { // Set the Id and add item with a null tempId. entity.ServiceMetadata.Id = WebUtil.GenerateOfflineEntityId(entity); oDataWriter.AddItem(entity, null /*tempId*/); } return(oDataWriter); }
// This method ensures that we have a valid feed through the formatters. The BodyWriter delegate in WCF // seems to not recover from an unhandled exception caused by the formatters and sends out an empty response to the caller. // This is a workaround for this issue until we find a better solution. private SyncWriter GetSyncWriterWithContents() { var conflictEntryKeys = new List <string>(); var errorEntryKeys = new List <string>(); var primaryKeyToIncomingEntitiesMapping = new Dictionary <string, IOfflineEntity>(); // Save the mapping between entity PK string -> entity foreach (var entity in _incomingEntities) { string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity); if (primaryKeyToIncomingEntitiesMapping.ContainsKey(primaryKey)) { throw SyncServiceException.CreateInternalServerError(Strings.MultipleEntriesWithSamePrimaryKeyInIncomingRequest); } primaryKeyToIncomingEntitiesMapping.Add(primaryKey, entity); } if (_rejectedEntities != null) { foreach (var entity in _rejectedEntities.Keys) { string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity); if (primaryKeyToIncomingEntitiesMapping.ContainsKey(primaryKey)) { throw SyncServiceException.CreateInternalServerError(Strings.MultipleEntriesWithSamePrimaryKeyInIncomingRequest); } primaryKeyToIncomingEntitiesMapping.Add(primaryKey, entity); } } // Get the appropriate SyncWriter instance based on the serialization format. var oDataWriter = WebUtil.GetSyncWriter(_responseSerializationFormat, _baseUri); oDataWriter.StartFeed(_applyChangesResponse.IsLastBatch, _applyChangesResponse.ServerBlob); // Write conflict entities. foreach (var entity in _applyChangesResponse.Conflicts) { // Add the primary key string to the conflictEntryKey list. // The primary keys are the same for both Live and Losing entities. conflictEntryKeys.Add(ReflectionUtility.GetPrimaryKeyString(entity.LiveEntity)); string tempId; // If the client change lost, then we need to set the Id property // only if the property was not null/empty in the incoming request. string entityId = WebUtil.GenerateOfflineEntityId(entity.LiveEntity); // Set the Id property of the Live entity (server's copy). entity.LiveEntity.ServiceMetadata.Id = entityId; // Set the Id property of the Losing entity to the incoming entity's Id value entity.LosingEntity.ServiceMetadata.Id = entityId; // get the original tempId. Null value is ok. _idToTempIdMapping.TryGetValue(entityId, out tempId); if (entity.Resolution == SyncConflictResolution.ServerWins) { // The losing entity is the client's copy. // When resolution is ServerWins, we only need to set the losing change tempId. oDataWriter.AddConflictItem(entity.LiveEntity, null /*tempId*/, entity.LosingEntity, tempId, entity.Resolution); } // If the client change won, then just set the Id property since an insert would have succeeded. else { // When resolution is ClientWins, we only need to set the LiveEntity tempId. oDataWriter.AddConflictItem(entity.LiveEntity, tempId, entity.LosingEntity, null /* tempId */, entity.Resolution); } } // Write error entities. foreach (var syncError in _applyChangesResponse.Errors) { Debug.Assert(null != syncError.LiveEntity); Debug.Assert(null != syncError.ErrorEntity); string entityId = WebUtil.GenerateOfflineEntityId(syncError.LiveEntity); // Set the Id for Live and Losing entity. syncError.LiveEntity.ServiceMetadata.Id = entityId; syncError.ErrorEntity.ServiceMetadata.Id = entityId; string primaryKeyString = ReflectionUtility.GetPrimaryKeyString(syncError.ErrorEntity); // Add the string to the error key list. errorEntryKeys.Add(primaryKeyString); string tempId; _idToTempIdMapping.TryGetValue(entityId, out tempId); oDataWriter.AddErrorItem(syncError.LiveEntity, syncError.ErrorEntity, tempId, syncError.Description); } // Write all the inserted records here by iterating over the _incomingNewInsertEntities list foreach (var entity in _incomingNewInsertEntities) { string entityTempId; // Get the tempId of the entity. _idToTempIdMapping.TryGetValue(WebUtil.GenerateOfflineEntityId(entity), out entityTempId); // Write the output to the SyncWriter. oDataWriter.AddItem(entity, entityTempId); } return(oDataWriter); }
/// <summary> /// Read and parse the incoming request stream for a POST request. /// </summary> private void ReadIncomingRequestStreamForPost() { if (null == _serviceHost.RequestStream || !_serviceHost.RequestStream.CanRead) { SyncTracer.Info("Request stream for HTTP POST is empty, null or cannot be read."); return; } try { var reader = WebUtil.GetSyncReader(_serviceHost.GetRequestContentSerializationFormat(), _serviceHost.RequestStream, _configuration.TypeToTableGlobalNameMapping.Keys.ToArray()); reader.Start(); while (reader.Next()) { switch (reader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = reader.GetItem(); if (entity.ServiceMetadata.IsTombstone) { if (String.IsNullOrEmpty(entity.ServiceMetadata.Id)) { throw SyncServiceException.CreateBadRequestError(Strings.TombstoneEntityHasNoId); } WebUtil.ParseIdStringAndPopulateKeyFields(entity, _serviceHost.ServiceBaseUri); } _entityList.Add(entity); bool hasTempId = false; if (reader.HasTempId()) { // Save the entity id to tempId mapping for use later when writing response. _idToTempIdMapping.Add(WebUtil.GenerateOfflineEntityId(entity), reader.GetTempId()); hasTempId = true; } // Make sure, we have atleast one of Id or TempId if (String.IsNullOrEmpty(entity.ServiceMetadata.Id) && !hasTempId) { throw SyncServiceException.CreateBadRequestError(Strings.BothIdAndTempIdAreMissing); } break; case ReaderItemType.SyncBlob: _syncBlob = reader.GetServerBlob(); break; } } } catch (XmlException exception) { SyncTracer.Warning("XmlException: {0}", WebUtil.GetExceptionMessage(exception)); throw SyncServiceException.CreateBadRequestError(Strings.BadRequestPayload); } }