/// <summary> /// Apply changes sent by a client to the server. /// </summary> /// <param name="serverBlob">Blob sent in the incoming request</param> /// <param name="entities">Changes from the client</param> /// <returns>Response containing the new knowledge and conflict/error information.</returns> public ApplyChangesResponse ApplyChanges(byte[] serverBlob, List<IOfflineEntity> entities) { WebUtil.CheckArgumentNull(serverBlob, "serverBlob"); WebUtil.CheckArgumentNull(entities, "entities"); if (0 == serverBlob.Length) { throw new InvalidOperationException("serverBlob is empty"); } var syncBlob = new SyncBlob(); SyncBlob incomingBlob = SyncBlob.DeSerialize(serverBlob); PopulateClientScopeNameAndSyncId(incomingBlob); // Set the scope name in the response blob. syncBlob.ClientScopeName = incomingBlob.ClientScopeName; // If the requested scope does not exists, then throw an error since we // don't initialize scopes on upload requests. if (!CheckIfScopeExists()) { throw SyncServiceException.CreateResourceNotFound("Scope does not exist"); } byte[] clientKnowledgeBlob = incomingBlob.ClientKnowledge; // Initialize a SqlSyncProvider object. _sqlSyncProvider = CreateSqlSyncProviderInstance(_clientScopeName, _serverConnectionString, _configuration.SyncObjectSchema); var response = new ApplyChangesResponse(); // Deserialize the knowledge or create new empty knowledge. SyncKnowledge clientKnowledge = GetSyncKnowledgeFromBlob(clientKnowledgeBlob); // If there are no entities to upload, then return the client knowledge as is. if (entities.Count == 0) { response.Conflicts = new List<SyncConflict>(); response.Errors = new List<SyncError>(); syncBlob.ClientKnowledge = clientKnowledge.Serialize(); response.ServerBlob = syncBlob.Serialize(); return response; } // Client never has any forgotten knowledge. So create a new one. var forgottenKnowledge = new ForgottenKnowledge(_sqlSyncProvider.IdFormats, clientKnowledge); // Convert the entities to dataset using the custom converter. DataSet changesDS = _converter.ConvertEntitiesToDataSet(entities); var stats = new SyncSessionStatistics(); var sessionContext = new SyncSessionContext(_sqlSyncProvider.IdFormats, new SyncCallbacks()); _sqlSyncProvider.BeginSession(SyncProviderPosition.Remote, sessionContext); ulong tickCount = 0; SyncKnowledge updatedClientKnowldege; try { uint batchSize; SyncKnowledge serverKnowledge; // This gives us the server knowledge. _sqlSyncProvider.GetSyncBatchParameters(out batchSize, out serverKnowledge); var changeBatch = new ChangeBatch(_sqlSyncProvider.IdFormats, clientKnowledge, forgottenKnowledge); changeBatch.SetLastBatch(); //Note: There is a possiblity of (-ve) item exceptions , between two uploads from the // same client (for example: in case of RI failures). This would result in an incorrect value if the function // FindMinTickCountForReplica is used to get the last tickcount. So, we need to ignore the -ve item exceptions // when finding the tickcount for the client replica from the server knowledge. /* Logic: * SyncKnowledge.GetKnowledgeForItemId could be used for itemid Zero and then we can find the mintickcount for client replica id. * This does not however seem to work, so we use the KnowledgeInspector and enumerate over each ClockVector * and find the client clockvector and get its tickcount. * * Assumption: The above approach assumes that we don't have any positive exceptions in the knowledge. */ try { // Check if the client replica key exists. uint clientReplicaKey = serverKnowledge.ReplicaKeyMap.LookupReplicaKey(_clientSyncId); var ki = new KnowledgeInspector(1, serverKnowledge); var clockVector = (ClockVector)ki.ScopeClockVector; int noOfReplicaKeys = clockVector.Count; for (int i = noOfReplicaKeys - 1; i >= 0; i--) { if (clockVector[i].ReplicaKey == clientReplicaKey) { tickCount = clockVector[i].TickCount; break; } } } catch (ReplicaNotFoundException exception) { SyncTracer.Info("ReplicaNotFoundException. NEW CLIENT. Exception details: {0}", WebUtil.GetExceptionMessage(exception)); // If the knowedge does not contain the client replica (first apply), initialize tickcount to zero. tickCount = 0; } // Increment the tickcount tickCount++; // update the made with knowledge to include the new tickcount. updatedClientKnowldege = new SyncKnowledge(_sqlSyncProvider.IdFormats, _clientSyncId, tickCount); updatedClientKnowldege.Combine(clientKnowledge); // The incoming data does not have metadata for each item, so we need to create it at this point. AddSyncColumnsToDataSet(changesDS, tickCount); // Make DbSyncContext var dbSyncContext = new DbSyncContext { IsDataBatched = false, IsLastBatch = true, DataSet = changesDS, MadeWithKnowledge = updatedClientKnowldege, MadeWithForgottenKnowledge = forgottenKnowledge, ScopeProgress = new DbSyncScopeProgress() }; _conflicts = new List<SyncConflict>(); _syncErrors = new List<SyncError>(); // Subscribe to the ApplyChangeFailed event to handle conflicts. _sqlSyncProvider.ApplyChangeFailed += SqlSyncProviderApplyChangeFailed; // Subscribe to the ChangesApplied event to read the server tickcount incase there are any conflicts. _sqlSyncProvider.ChangesApplied += SqlSyncProviderChangesApplied; //NOTE: The ConflictResolutionPolicy pass into the method is IGNORED. // Conflicts can be logged by subscribing to the failed events _sqlSyncProvider.ProcessChangeBatch(Microsoft.Synchronization.ConflictResolutionPolicy.DestinationWins, changeBatch, dbSyncContext, new SyncCallbacks(), stats); if (0 != _conflicts.Count) { _sqlSyncProvider.GetSyncBatchParameters(out batchSize, out serverKnowledge); // The way the current P2P provider works, versions are bumped up when conflicts are resolved on the server. // This would result in us sending the changes to the client on the next download request. We want // to not enumerate that change again on the next request from the same client. // The solution is to get the server knowledge after all changes are applied and then // project the knowledge of each conflictign item and add it as a positive exception to the updated client knowledge. AddConflictItemsKnowledgeToClientKnowledge(updatedClientKnowldege, serverKnowledge); } } finally { _sqlSyncProvider.EndSession(sessionContext); } // Don't send any updates to the server knowledge since the client has not got any updates yet. // This updated knowledge will only include an update to the client tickcount. // The client would obtain the server knowledge when it does a get changes. // If we include the serverknowlege, the client would never get any items that are // between the current server knowledge and the client known server knowledge. syncBlob.ClientKnowledge = updatedClientKnowldege.Serialize(); response.ServerBlob = syncBlob.Serialize(); response.Conflicts = _conflicts; response.Errors = _syncErrors; return response; }
/// <summary> /// When overridden in a derived class, gets a change batch that contains item metadata for items that are not contained in the specified knowledge from the destination provider. /// </summary> /// <param name="batchSize">The number of changes to include in the change batch.</param> /// <param name="destinationKnowledge">The knowledge from the destination provider. This knowledge must be mapped by calling <see cref="M:Microsoft.Synchronization.SyncKnowledge.MapRemoteKnowledgeToLocal(Microsoft.Synchronization.SyncKnowledge)"/> on the source knowledge before it can be used for change enumeration.</param> /// <param name="changeDataRetriever">Returns an object that can be used to retrieve change data. It can be an <see cref="T:Microsoft.Synchronization.IChangeDataRetriever"/> object or a provider-specific object.</param> /// <returns> /// A change batch that contains item metadata for items that are not contained in the specified knowledge from the destination provider. Cannot be a null. /// </returns> public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever) { byte[] rawDestinationKnowledge = destinationKnowledge.Serialize(); byte[] rawChangeDataRetriever; byte[] rawChangeBatch = _syncService.GetChangeBatch(batchSize, rawDestinationKnowledge, out rawChangeDataRetriever); CachedChangeDataRetriever cachedRetriever = SerializerHelper.BinaryDeserialize <CachedChangeDataRetriever>(rawChangeDataRetriever); changeDataRetriever = cachedRetriever; return(ChangeBatch.Deserialize(IdFormats, rawChangeBatch)); }
/// <summary> /// When overridden in a derived class, gets a change batch that contains item metadata for items that have IDs greater than the specified lower bound, as part of a full enumeration. /// </summary> /// <param name="batchSize">The number of changes to include in the change batch.</param> /// <param name="lowerEnumerationBound">The lower bound for item IDs. This method returns changes that have IDs greater than or equal to this ID value.</param> /// <param name="knowledgeForDataRetrieval">If an item change is contained in this knowledge object, data for that item already exists on the destination replica.</param> /// <param name="changeDataRetriever">Returns an object that can be used to retrieve change data. It can be an <see cref="T:Microsoft.Synchronization.IChangeDataRetriever"/> object or a be provider-specific object.</param> /// <returns> /// A change batch that contains item metadata for items that have IDs greater than the specified lower bound, as part of a full enumeration. /// </returns> public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever) { byte[] rawLowerEnumBound = lowerEnumerationBound.RawId; byte[] rawKnowledgeForDataRetrieval = knowledgeForDataRetrieval.Serialize(); byte[] rawChangeDataRetriever; byte[] rawFullEnumerationChangeBatch = _syncService.GetFullEnumerationChangeBatch(batchSize, rawLowerEnumBound, rawKnowledgeForDataRetrieval, out rawChangeDataRetriever); CachedChangeDataRetriever cachedRetriever = SerializerHelper.BinaryDeserialize <CachedChangeDataRetriever>(rawChangeDataRetriever); changeDataRetriever = cachedRetriever; return(FullEnumerationChangeBatch.Deserialize(IdFormats, rawFullEnumerationChangeBatch)); }
/// <summary> /// When overridden in a derived class, gets a change batch that contains item metadata for items that have IDs greater than the specified lower bound, as part of a full enumeration. /// </summary> /// <param name="batchSize">The number of changes to include in the change batch.</param> /// <param name="lowerEnumerationBound">The lower bound for item IDs. This method returns changes that have IDs greater than or equal to this ID value.</param> /// <param name="knowledgeForDataRetrieval">If an item change is contained in this knowledge object, data for that item already exists on the destination replica.</param> /// <param name="changeDataRetriever">Returns an object that can be used to retrieve change data. It can be an <see cref="T:Microsoft.Synchronization.IChangeDataRetriever"/> object or a be provider-specific object.</param> /// <returns> /// A change batch that contains item metadata for items that have IDs greater than the specified lower bound, as part of a full enumeration. /// </returns> public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever) { byte[] rawLowerEnumBound = lowerEnumerationBound.RawId; byte[] rawKnowledgeForDataRetrieval = knowledgeForDataRetrieval.Serialize(); byte[] rawChangeDataRetriever; byte[] rawFullEnumerationChangeBatch = _syncService.GetFullEnumerationChangeBatch(batchSize, rawLowerEnumBound, rawKnowledgeForDataRetrieval, out rawChangeDataRetriever); CachedChangeDataRetriever cachedRetriever = SerializerHelper.BinaryDeserialize<CachedChangeDataRetriever>(rawChangeDataRetriever); changeDataRetriever = cachedRetriever; return FullEnumerationChangeBatch.Deserialize(IdFormats, rawFullEnumerationChangeBatch); }
/// <summary> /// When overridden in a derived class, gets a change batch that contains item metadata for items that are not contained in the specified knowledge from the destination provider. /// </summary> /// <param name="batchSize">The number of changes to include in the change batch.</param> /// <param name="destinationKnowledge">The knowledge from the destination provider. This knowledge must be mapped by calling <see cref="M:Microsoft.Synchronization.SyncKnowledge.MapRemoteKnowledgeToLocal(Microsoft.Synchronization.SyncKnowledge)"/> on the source knowledge before it can be used for change enumeration.</param> /// <param name="changeDataRetriever">Returns an object that can be used to retrieve change data. It can be an <see cref="T:Microsoft.Synchronization.IChangeDataRetriever"/> object or a provider-specific object.</param> /// <returns> /// A change batch that contains item metadata for items that are not contained in the specified knowledge from the destination provider. Cannot be a null. /// </returns> public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever) { byte[] rawDestinationKnowledge = destinationKnowledge.Serialize(); byte[] rawChangeDataRetriever; byte[] rawChangeBatch = _syncService.GetChangeBatch(batchSize, rawDestinationKnowledge, out rawChangeDataRetriever); CachedChangeDataRetriever cachedRetriever = SerializerHelper.BinaryDeserialize<CachedChangeDataRetriever>(rawChangeDataRetriever); changeDataRetriever = cachedRetriever; return ChangeBatch.Deserialize(IdFormats, rawChangeBatch); }