/// <summary> /// Get the entity from the incoming request that matches the primary key string of the entity passed as a parameter. /// </summary> /// <param name="primaryKeyToIncomingEntitiesMapping">Dictionary of mapping between primary key and the actual entities from incoming request.</param> /// <param name="entity">Entity for which to search a match in the incoming request.</param> /// <param name="isConflict">Indicates if this is called during conflict processing. Used to select appropriate error messages.</param> /// <returns>Entity from the incoming request.</returns> private static IOfflineEntity GetEntityFromIncomingRequest(Dictionary <string, IOfflineEntity> primaryKeyToIncomingEntitiesMapping, IOfflineEntity entity, bool isConflict) { // find the actual entity from the input list. var entityListInRequest = primaryKeyToIncomingEntitiesMapping.Where(e => e.Key.Equals(ReflectionUtility.GetPrimaryKeyString(entity), StringComparison.InvariantCultureIgnoreCase)).ToList(); // If no match is found, then throw an error. if (0 == entityListInRequest.Count) { if (isConflict) { throw SyncServiceException.CreateInternalServerError(Strings.ConflictEntityMissingInIncomingRequest); } throw SyncServiceException.CreateInternalServerError(Strings.ErrorEntityMissingInIncomingRequest); } // If the entity corresponding to the key is null, then throw an error. if (null == entityListInRequest[0].Value) { if (isConflict) { throw SyncServiceException.CreateInternalServerError(Strings.ConflictEntityMissingInIncomingRequest); } throw SyncServiceException.CreateInternalServerError(Strings.ErrorEntityMissingInIncomingRequest); } return(entityListInRequest[0].Value); }
/// <summary> /// Check Metadata /// </summary> private void CheckEntityServiceMetadataAndTempIds(AsyncArgsWrapper wrapper, IOfflineEntity entity, string tempId) { // Check service ID if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) { throw new CacheControllerException( string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); } // If an entity has a temp id then it should not be a tombstone if (entity.GetServiceMetadata().IsTombstone) { throw new CacheControllerException(string.Format( "Service returned a tempId '{0}' in tombstoned entity.", tempId)); } // Check that the tempId was sent by client if (!wrapper.TempIdToEntityMapping.ContainsKey(tempId)) { throw new CacheControllerException( "Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); } // Once received, remove the tempId from the mapping list. wrapper.TempIdToEntityMapping.Remove(tempId); }
private void CheckEntityServiceMetadataAndTempIds(Dictionary <string, IOfflineEntity> tempIdToEntityMapping, IOfflineEntity entity, string tempId, ChangeSetResponse response) { // Check service ID if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { throw new CacheControllerException(string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); } // If an entity has a temp id then it should not be a tombstone if (entity.ServiceMetadata.IsTombstone) { throw new CacheControllerException(string.Format("Service returned a tempId '{0}' in tombstoned entity.", tempId)); } // Check that the tempId was sent by client if (!tempIdToEntityMapping.ContainsKey(tempId)) { throw new CacheControllerException("Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); } // Add the entity to the Updated list. response.AddUpdatedItem(entity); // Once received, remove the tempId from the mapping list. tempIdToEntityMapping.Remove(tempId); }
/// <summary> /// Build the OData Atom primary keystring representation /// </summary> /// <param name="live">Entity for which primary key is required</param> /// <returns>String representation of the primary key</returns> public static string GetPrimaryKeyString(IOfflineEntity live) { StringBuilder builder = new StringBuilder(); string sep = string.Empty; foreach (PropertyInfo keyInfo in GetPrimaryKeysPropertyInfoMapping(live.GetType())) { if (keyInfo.PropertyType == FormatterConstants.GuidType) { builder.AppendFormat("{0}{1}=guid'{2}'", sep, keyInfo.Name, keyInfo.GetValue(live, null)); } else if (keyInfo.PropertyType == FormatterConstants.StringType) { builder.AppendFormat("{0}{1}='{2}'", sep, keyInfo.Name, keyInfo.GetValue(live, null)); } else { builder.AppendFormat("{0}{1}={2}", sep, keyInfo.Name, keyInfo.GetValue(live, null)); } if (string.IsNullOrEmpty(sep)) { sep = ", "; } } return(builder.ToString()); }
/// <summary> /// Writes the <entry/> tag and all its related elements. /// </summary> /// <param name="live">Actual entity whose value is to be emitted.</param> /// <param name="tempId">The temporary Id if any</param> /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param> /// <returns>XElement representation of the entry element</returns> private XElement WriteEntry(IOfflineEntity live, string tempId, bool emitPartial) { string typeName = live.GetType().FullName; if (!live.ServiceMetadata.IsTombstone) { var entryElement = new XElement(FormatterConstants.AtomXmlNamespace + C.Entry); entryElement.Add(new XAttribute(C.Caption, typeName)); if (!emitPartial) { // Write the entity contents WriteEntityContents(entryElement, live); } return(entryElement); } // Write the at:deleted-entry tombstone element var tombstoneElement = new XElement(FormatterConstants.AtomXmlNamespace + C.Tombstone, new XAttribute(C.Caption, typeName)); Guid id; if (!Guid.TryParse(live.ServiceMetadata.Id, out id)) { string[] split = live.ServiceMetadata.Id.Split('\''); id = Guid.Parse(split[1]); } tombstoneElement.Add(id.ToString()); return(tombstoneElement); }
/// <summary> /// Adds an IOfflineEntity and its associated Conflicting/Error entity as an Atom entry element /// </summary> /// <param name="live">Live Entity</param> /// <param name="liveTempId">TempId for the live entity</param> /// <param name="conflicting">Conflicting entity that will be sent in synnConflict or syncError extension</param> /// <param name="conflictingTempId">TempId for the conflicting entity</param> /// <param name="desc">Error description or the conflict resolution</param> /// <param name="isConflict">Denotes if its an errorElement or conflict. Used only when <paramref name="desc"/> is not null</param> /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param> public override void WriteItemInternal(IOfflineEntity live, string liveTempId, IOfflineEntity conflicting, string conflictingTempId, string desc, bool isConflict, bool emitMetadataOnly) { XElement entryElement = WriteEntry(live, null, liveTempId, emitMetadataOnly); if (conflicting != null) { XElement conflictElement = new XElement(((isConflict) ? FormatterConstants.JsonSyncConflictElementName : FormatterConstants.JsonSyncErrorElementName), new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); // Write the confliction resolution or errorElement. conflictElement.Add(new XElement(((isConflict) ? FormatterConstants.ConflictResolutionElementName : FormatterConstants.ErrorDescriptionElementName), new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), desc)); // Write the confliction resolution or errorElement. XElement conflictingEntryElement = new XElement(((isConflict) ? FormatterConstants.ConflictEntryElementName : FormatterConstants.ErrorEntryElementName), new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); // Write the Conflicting entry in WriteEntry(conflicting, conflictingEntryElement, conflictingTempId, false /*emitPartial*/); // Add the conflicting entry data in to the conflict element conflictElement.Add(conflictingEntryElement); // Add the conflict element to the live entry entryElement.Add(conflictElement); } _results.Add(entryElement); }
/// <summary> /// This writes the public contents of the Entity in the properties element. /// </summary> /// <param name="entity">Entity</param> /// <returns>XElement representation of the properties element</returns> XElement WriteEntityContents(IOfflineEntity entity) { XElement contentElement = new XElement(FormatterConstants.ODataMetadataNamespace + FormatterConstants.PropertiesElementName); // Write only the primary keys if its an tombstone PropertyInfo[] properties = ReflectionUtility.GetPropertyInfoMapping(entity.GetType()); // Write individual properties to the feed, foreach (PropertyInfo fi in properties) { string edmType = FormatterUtilities.GetEdmType(fi.PropertyType); object value = fi.GetValue(entity, null); string valString = value as string; if (valString != null) { value = AtomHelper.CleanInvalidXmlChars(valString); } Type propType = fi.PropertyType; if (fi.PropertyType.IsGenericType && fi.PropertyType.Name.Equals(FormatterConstants.NullableTypeName, StringComparison.InvariantCulture)) { // Its a Nullable<T> property propType = fi.PropertyType.GetGenericArguments()[0]; } if (value == null) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubIsNullElementName, true))); } else if (propType == FormatterConstants.DateTimeType || propType == FormatterConstants.TimeSpanType || propType == FormatterConstants.DateTimeOffsetType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), FormatterUtilities.ConvertDateTimeForType_Atom(value, propType))); } else if (propType != FormatterConstants.ByteArrayType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), value)); } else { byte[] bytes = (byte[])value; contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), Convert.ToBase64String(bytes))); } } return(contentElement); }
/// <summary> /// Used when rejecting an item during upload request. Rejection may be due to validation errors or other /// business rules. The runtime will send an appropriate SyncError for this entity back to client. /// </summary> /// <param name="entity">The entity to reject</param> /// <param name="rejectDescription">Reason for rejecting the change</param> public void RejectChange(IOfflineEntity entity, string rejectDescription) { WebUtil.CheckArgumentNull(entity, "entity"); WebUtil.CheckArgumentNull(rejectDescription, "rejectDescription"); WebUtil.CheckArgumentEmpty(rejectDescription, "rejectDescription"); this._rejectedEntities[entity] = rejectDescription; }
internal void AddItem(IOfflineEntity iOfflineEntity) { if (Data == null) { Data = new List <IOfflineEntity>(); } Data.Add(iOfflineEntity); }
internal void AddItem(IOfflineEntity iOfflineEntity) { if (Data == null) { Data = new List<IOfflineEntity>(); } Data.Add(iOfflineEntity); }
public static IOfflineEntity GetObjectForType(EntryInfoWrapper wrapper, Type[] knownTypes) { Type entityType; ConstructorInfo ctorInfo; // See if its cached first. if (!_stringToCtorInfoMapping.TryGetValue(wrapper.TypeName, out ctorInfo)) { // Its not cached. Try to look for it then in list of known types. if (knownTypes != null) { entityType = knownTypes.FirstOrDefault(e => e.FullName.Equals(wrapper.TypeName, StringComparison.CurrentCultureIgnoreCase)); if (entityType == null) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unable to find a matching type for entry '{0}' in list of KnownTypes.", wrapper.TypeName)); } } else { throw new InvalidOperationException(string.Format("Unable to find a matching type for entry '{0}' in the loaded assemblies. Specify the type name in the KnownTypes argument to the SyncReader instance.", wrapper.TypeName)); } // Reflect this entity and get necessary info GetPropertyInfoMapping(entityType); ctorInfo = _stringToCtorInfoMapping[wrapper.TypeName]; } else { entityType = ctorInfo.DeclaringType; } // Invoke the ctor object obj = ctorInfo.Invoke(null); // Set the parameters only for non tombstone items if (!wrapper.IsTombstone) { IEnumerable <PropertyInfo> props = GetPropertyInfoMapping(entityType); foreach (PropertyInfo pinfo in props) { string value; if (wrapper.PropertyBag.TryGetValue(pinfo.Name, out value)) { pinfo.SetValue(obj, GetValueFromType(pinfo.PropertyType, value), null); } } } IOfflineEntity entity = (IOfflineEntity)obj; entity.SetServiceMetadata(new OfflineEntityMetadata(wrapper.IsTombstone, wrapper.Id, wrapper.ETag, wrapper.EditUri)); return(entity); }
/// <summary> /// This is called for every conflict/error with the client entity. /// We use this function to check if the error was a client insert and if yes then remove it from /// the list of _incomingNewInsertEntities. This way we know which entities are successful uploads so /// we can pass this info to the UploadResponseInterceptor.OutgoingChanges list. /// </summary> /// <param name="entity">The client version of the entity. We match the actual reference via its primary key</param> internal void ClientChangeFailedToApply(IOfflineEntity entity) { string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity); // Check to see if this element is in the insert list and if yes then remove it IOfflineEntity matchedEntity = this._incomingNewInsertEntities.Where( e => ReflectionUtility.GetPrimaryKeyString(e).Equals(primaryKey, StringComparison.InvariantCulture)).FirstOrDefault(); if (matchedEntity != null) { this._incomingNewInsertEntities.Remove(matchedEntity); } }
/// <summary> /// Generate the Id for an entity. The format currently is the OData Id. /// i.e. http://baseUri/tableName(primarykeylist) /// </summary> /// <param name="entity"></param> /// <returns></returns> internal static string GenerateOfflineEntityId(IOfflineEntity entity) { var primaryKeyString = ReflectionUtility.GetPrimaryKeyString(entity); if (String.IsNullOrEmpty(primaryKeyString)) { throw SyncServiceException.CreateInternalServerError( String.Format("GetPrimaryKeyString method returned an empty string for entity type {0}.", entity.GetType())); } return(String.Format(@"{0}/{1}({2})", "http://bitmobile.com/" + AppDomain.CurrentDomain.FriendlyName /*WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri*/, entity.GetType().Name, primaryKeyString)); }
/// <summary> /// Copies the individual properties from the entity back in to the DataTable's first row. /// This should be used only when merging a user conflict resolution back in to a DataRow. /// This returns the merged results as an object array /// </summary> /// <param name="entity">Entity from which to read values</param> /// <param name="table">The Table whose first DataRow will be updated.</param> /// <returns>The contents of the DataRow as an object array</returns> internal object[] CopyEntityToDataRow(IOfflineEntity entity, DataTable table) { Debug.Assert(table.Rows.Count == 1, "table.Rows.Count ==1"); if (table.Rows.Count != 1) { throw new InvalidOperationException("Cannot copy Entity to a DataTable whose row count != 1"); } Dictionary <string, string> mappingInfo = _localToGlobalPropertyMapping[entity.GetType()]; // Check for tombstones bool isRowDeleted = false; if (table.Rows[0].RowState == DataRowState.Deleted) { isRowDeleted = true; table.Rows[0].RejectChanges(); } // Retrieve the current row values object[] rowValues = table.Rows[0].ItemArray; PropertyInfo[] properties = entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); for (int i = 0; i < table.Columns.Count; i++) { // Iterate over each non sync column and read its value from the entity if (IsSyncSpecificColumn(table.Columns[i].ColumnName)) { continue; } // Read the value of the property string columnName = null; mappingInfo.TryGetValue(table.Columns[i].ColumnName, out columnName); columnName = columnName ?? table.Columns[i].ColumnName; // Retrieve the PropertyInfo PropertyInfo info = properties.Where(e => e.Name.Equals(columnName, StringComparison.Ordinal)).FirstOrDefault(); Debug.Assert(info != null, "PropertyInfo is not null."); // Get the property's value and put it in the object array rowValues[i] = info.GetValue(entity, null) ?? DBNull.Value; } // Write the row values back to the DataRow table.Rows[0].ItemArray = rowValues; if (isRowDeleted) { table.Rows[0].Delete(); } return(rowValues); }
/// <summary> /// Called to add a Sync conflict item /// </summary> /// <param name="winningEntry">the winning entity</param> /// <param name="winningEntryTempId">the winning entity's tempId</param> /// <param name="losingEntry">The losing entity</param> /// <param name="losingEntryTempId">The losing entity's tempId</param> /// <param name="resolution">The conflict resolution aplied by the server</param> public virtual void AddConflictItem(IOfflineEntity winningEntry, string winningEntryTempId, IOfflineEntity losingEntry, string losingEntryTempId, SyncConflictResolution resolution) { if (winningEntry == null) { throw new ArgumentNullException("winningEntry"); } if (losingEntry == null) { throw new ArgumentNullException("losingEntry"); } WriteItemInternal(winningEntry, winningEntryTempId, losingEntry /*conflicting*/, losingEntryTempId, resolution.ToString() /*desc*/, true /*isconflict*/, false /*emitMetadataOnly*/); }
public SyncConflictResolution ConflictHandler(SyncConflictContext context, out IOfflineEntity mergedEntity) { // Add a dummy header to the response indicating that the interceptor method was invoked. context.ResponseHeaders.Add("ConflictInterceptorFired", "true"); // The mergedEntity is used as the winner of the conflict // when this method returns SyncConflictResolution.Merge as the resolution. // For other resolutions such as SyncConflictResolution.ClientWins and SyncConflictResolution.ServerWins // the mergedEntity should be set to null. mergedEntity = null; return(SyncConflictResolution.ClientWins); }
/// <summary> /// Called to add a Sync Error item /// </summary> /// <param name="liveEntry">Live version of the entity</param> /// <param name="errorEntry">Version of the entity that caused the error.</param> /// <param name="errorEntryTempId">TempIf for the entity that caused the error.</param> /// <param name="errorDescription">Description of error.</param> public virtual void AddErrorItem(IOfflineEntity liveEntry, IOfflineEntity errorEntry, string errorEntryTempId, string errorDescription) { if (liveEntry == null) { throw new ArgumentNullException("liveEntry"); } if (errorEntry == null) { throw new ArgumentNullException("errorEntry"); } WriteItemInternal(liveEntry, null /*liveEntryTempId*/, errorEntry /*conflicting*/, errorEntryTempId, errorDescription /*desc*/, false /*isconflict*/, false /*emitMetadataOnly*/); }
/// <summary> /// Called to add a particular Entity /// </summary> /// <param name="entry">Entity to add to serialize to the stream</param> /// <param name="tempId">TempId for the Entity</param> /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param> public virtual void AddItem(IOfflineEntity entry, string tempId, bool emitMetadataOnly) { if (entry == null) { throw new ArgumentNullException("entry"); } if(string.IsNullOrEmpty(entry.ServiceMetadata.Id) && entry.ServiceMetadata.IsTombstone) { // Skip sending tombstones that dont have an Id as these were local create + delete. return; } WriteItemInternal(entry, tempId, null /*conflicting*/, null/*conflictingTempId*/, null /*desc*/, false /*isconflict*/, emitMetadataOnly); }
/// <summary> /// Called to add a particular Entity /// </summary> /// <param name="entry">Entity to add to serialize to the stream</param> /// <param name="tempId">TempId for the Entity</param> /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param> public virtual void AddItem(IOfflineEntity entry, string tempId, bool emitMetadataOnly) { if (entry == null) { throw new ArgumentNullException("entry"); } if (string.IsNullOrEmpty(entry.ServiceMetadata.Id) && entry.ServiceMetadata.IsTombstone) { // Skip sending tombstones that dont have an Id as these were local create + delete. return; } WriteItemInternal(entry, tempId, null /*conflicting*/, null /*conflictingTempId*/, null /*desc*/, false /*isconflict*/, emitMetadataOnly); }
/// <summary> /// This function loops the rejected entites and sends back a SyncError for each entity. For each entity it does the following /// 1. Retrieve the current version in server. /// 1.a If its null then it copies the primary key to a new object and marks it as tombstone. /// 2. Adds the SyncError to existing list of SyncErrors. /// </summary> /// <param name="sqlProvider"></param> private void ProcessRejectedEntities(SqlSyncProviderService sqlProvider) { if (this._rejectedEntities == null || this._rejectedEntities.Count == 0) { return; } try { List <IOfflineEntity> serverVersions = sqlProvider.GetCurrentServerVersionForEntities(this._rejectedEntities.Keys); if (serverVersions.Count != this._rejectedEntities.Count) { // Ensure we get a server version for each entity we passed throw new InvalidOperationException("Did not get server versions for all rejected entities."); } for (int i = 0; i < this._rejectedEntities.Keys.Count; i++) { IOfflineEntity server = serverVersions[i]; IOfflineEntity client = this._rejectedEntities.Keys.ElementAt(i); if (server == null) { // This means the server didnt contain a version for the entity. Need to send a tombstone back then // create a new object and copy the values over ConstructorInfo constructorInfo = client.GetType().GetConstructor(Type.EmptyTypes); server = (IOfflineEntity)constructorInfo.Invoke(null); // Copy the primary key values over foreach (PropertyInfo info in ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(client.GetType())) { info.SetValue(server, info.GetValue(client, null), null); } server.ServiceMetadata.IsTombstone = true; } SyncError error = new SyncError() { ErrorEntity = client, LiveEntity = server, Description = this._rejectedEntities[client] }; _applyChangesResponse.Errors.Add(error); } } catch (Exception e) { throw new InvalidOperationException("Error in reading server row values. " + e.Message); } }
/// <summary> /// Adds an IOfflineEntity and its associated Conflicting/Error entity as an Atom entry element /// </summary> /// <param name="live">Live Entity</param> /// <param name="liveTempId">TempId for the live entity</param> /// <param name="conflicting">Conflicting entity that will be sent in synnConflict or syncError extension</param> /// <param name="conflictingTempId">TempId for the conflicting entity</param> /// <param name="desc">Error description or the conflict resolution</param> /// <param name="isConflict">Denotes if its an errorElement or conflict. Used only when <paramref name="desc"/> is not null</param> /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param> public override void WriteItemInternal(IOfflineEntity live, string liveTempId, IOfflineEntity conflicting, string conflictingTempId, string desc, bool isConflict, bool emitMetadataOnly) { XElement entryElement = WriteEntry(live, liveTempId, emitMetadataOnly); if (conflicting != null) { XElement conflictElement = new XElement(FormatterConstants.SyncNamespace + ((isConflict) ? FormatterConstants.SyncConlflictElementName : FormatterConstants.SyncErrorElementName)); // Write the confliction resolution or errorElement. conflictElement.Add(new XElement(FormatterConstants.SyncNamespace + ((isConflict) ? FormatterConstants.ConflictResolutionElementName : FormatterConstants.ErrorDescriptionElementName), desc)); // Write the confliction resolution or errorElement. XElement conflictingEntryElement = new XElement(FormatterConstants.SyncNamespace + ((isConflict) ? FormatterConstants.ConflictEntryElementName : FormatterConstants.ErrorEntryElementName)); conflictingEntryElement.Add(WriteEntry(conflicting, conflictingTempId, false /*emitPartial*/)); conflictElement.Add(conflictingEntryElement); entryElement.Add(conflictElement); } _root.Add(entryElement); }
/// <summary> /// This writes the public contents of the Entity in the properties element. /// </summary> /// <param name="element"></param> /// <param name="entity">Entity</param> /// <returns>XElement representation of the properties element</returns> void WriteEntityContents(XElement element, IOfflineEntity entity) { // Write only the primary keys if its an tombstone PropertyInfo[] properties = ReflectionUtility.GetPropertyInfoMapping(entity.GetType()); // Write individual properties to the feed, foreach (PropertyInfo fi in properties.OrderBy(val => val.Name.StartsWith("__") ? val.Name.Substring(2) : val.Name)) { object value = fi.GetValue(entity, null); Type propType = fi.PropertyType; if (fi.PropertyType.IsGenericType && fi.PropertyType.Name.Equals(FormatterConstants.NullableTypeName, StringComparison.InvariantCulture)) { // Its a Nullable<T> property propType = fi.PropertyType.GetGenericArguments()[0]; } if (value == null) { element.Add(new XElement(FormatterConstants.AtomXmlNamespace + C.NullableProperty)); } else if (propType == FormatterConstants.DateTimeType || propType == FormatterConstants.TimeSpanType || propType == FormatterConstants.DateTimeOffsetType) { element.Add(new XElement(FormatterConstants.AtomXmlNamespace + C.Property, FormatterUtilities.ConvertDateTimeForType_Atom(value, propType))); } else if (propType != FormatterConstants.ByteArrayType) { element.Add(new XElement(FormatterConstants.AtomXmlNamespace + C.Property, value)); } else { byte[] bytes = (byte[])value; element.Add(new XElement(FormatterConstants.AtomXmlNamespace + C.Property, Convert.ToBase64String(bytes))); } } }
/// <summary> /// This writes the public contents of the Entity in the properties element. /// </summary> /// <param name="entity">Entity</param> /// <returns>XElement representation of the properties element</returns> XElement WriteEntityContents(IOfflineEntity entity) { XElement contentElement = new XElement(FormatterConstants.ODataMetadataNamespace + FormatterConstants.PropertiesElementName); // Write only the primary keys if its an tombstone var ent = (IEntity)entity; EntityType type = ent.EntityType; // Write individual properties to the feed, foreach (EntityField field in type.Fields) { Type propType = field.Type; string name = field.Name; object value = ent.GetValue(field.Name); if (propType == typeof(IDbRef)) { propType = typeof(Guid); var dbRef = (IDbRef)value; value = dbRef.Id; name = "__" + name; } string edmType = FormatterUtilities.GetEdmType(propType); if (edmType == null) { continue; } if (propType.IsGenericType && propType.Name.Equals(FormatterConstants.NullableTypeName, StringComparison.InvariantCulture)) { // Its a Nullable<T> property propType = propType.GetGenericArguments()[0]; } if (value == null) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubIsNullElementName, true))); } else if (propType == FormatterConstants.DateTimeType || propType == FormatterConstants.TimeSpanType || propType == FormatterConstants.DateTimeOffsetType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), FormatterUtilities.ConvertDateTimeForType_Atom(value, propType))); } else if (propType != FormatterConstants.ByteArrayType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), value)); } else { byte[] bytes = (byte[])value; contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), Convert.ToBase64String(bytes))); } } return(contentElement); }
/// <summary> /// Adds an IOfflineEntity and its associated Conflicting/Error entity as an Atom entry element /// </summary> /// <param name="live">Live Entity</param> /// <param name="liveTempId">TempId for the live entity</param> /// <param name="conflicting">Conflicting entity that will be sent in synnConflict or syncError extension</param> /// <param name="conflictingTempId">TempId for the conflicting entity</param> /// <param name="desc">Error description or the conflict resolution</param> /// <param name="isConflict">Denotes if its an errorElement or conflict. Used only when <paramref name="desc"/> is not null</param> /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param> public override void WriteItemInternal(IOfflineEntity live, string liveTempId, IOfflineEntity conflicting, string conflictingTempId, string desc, bool isConflict, bool emitMetadataOnly) { XElement entryElement = WriteEntry(live, null, liveTempId, emitMetadataOnly); if (conflicting != null) { XElement conflictElement = new XElement(((isConflict) ? FormatterConstants.JsonSyncConflictElementName : FormatterConstants.JsonSyncErrorElementName), new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); // Write the confliction resolution or errorElement. conflictElement.Add(new XElement(((isConflict) ? FormatterConstants.ConflictResolutionElementName : FormatterConstants.ErrorDescriptionElementName), new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), desc)); // Write the confliction resolution or errorElement. XElement conflictingEntryElement = new XElement(((isConflict) ? FormatterConstants.ConflictEntryElementName : FormatterConstants.ErrorEntryElementName), new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); // Write the Conflicting entry in WriteEntry(conflicting, conflictingEntryElement, conflictingTempId, false/*emitPartial*/); // Add the conflicting entry data in to the conflict element conflictElement.Add(conflictingEntryElement); // Add the conflict element to the live entry entryElement.Add(conflictElement); } _results.Add(entryElement); }
public void AddUpdatedItem(IOfflineEntity item) { this._updatedItems.Add(item); }
/// <summary> /// This writes the public contents of the Entity in the properties element. /// </summary> /// <param name="entity">Entity</param> /// <returns>XElement representation of the properties element</returns> XElement WriteEntityContents(IOfflineEntity entity) { XElement contentElement = new XElement(FormatterConstants.ODataMetadataNamespace + FormatterConstants.PropertiesElementName); // Write only the primary keys if its an tombstone IEnumerable<PropertyInfo> properties = ReflectionUtility.GetPropertyInfoMapping(entity.GetType()); // Write individual properties to the feed, foreach (PropertyInfo fi in properties) { string edmType = FormatterUtilities.GetEdmType(fi.PropertyType); object value = fi.GetValue(entity, null); Type propType = fi.PropertyType; if(fi.PropertyType.IsGenericType() && fi.PropertyType.Name.Equals(FormatterConstants.NullableTypeName, StringComparison.CurrentCultureIgnoreCase)) { // Its a Nullable<T> property propType = fi.PropertyType.GetGenericArguments()[0]; } if (value == null) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubIsNullElementName, true))); } else if (propType == FormatterConstants.DateTimeType || propType == FormatterConstants.TimeSpanType || propType == FormatterConstants.DateTimeOffsetType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), FormatterUtilities.ConvertDateTimeForType_Atom(value, propType))); } else if (propType != FormatterConstants.ByteArrayType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), value)); } else { byte[] bytes = (byte[])value; contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + fi.Name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), Convert.ToBase64String(bytes))); } } return contentElement; }
public abstract void WriteItemInternal(IOfflineEntity live, string liveTempId, IOfflineEntity conflicting, string conflictingTempId, string desc, bool isConflict, bool emitMetadataOnly);
/// <summary> /// Check Metadata /// </summary> private void CheckEntityServiceMetadataAndTempIds(AsyncArgsWrapper wrapper, IOfflineEntity entity, string tempId) { // Check service ID if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) throw new CacheControllerException( string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); // If an entity has a temp id then it should not be a tombstone if (entity.GetServiceMetadata().IsTombstone) throw new CacheControllerException(string.Format( "Service returned a tempId '{0}' in tombstoned entity.", tempId)); // Check that the tempId was sent by client if (!wrapper.TempIdToEntityMapping.ContainsKey(tempId)) throw new CacheControllerException( "Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); // Once received, remove the tempId from the mapping list. wrapper.TempIdToEntityMapping.Remove(tempId); }
/// <summary> /// Writes the <entry/> tag and all its related elements. /// </summary> /// <param name="live">Actual entity whose value is to be emitted.</param> /// <param name="tempId">The temporary Id if any</param> /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param> /// <returns>XElement representation of the entry element</returns> private XElement WriteEntry(IOfflineEntity live, string tempId, bool emitPartial) { var entity = (IEntity)live; string typeName = string.Format("{0}.{1}.{2}", RootNamespace, entity.EntityType.Schema, entity.EntityType.Name); if (!live.ServiceMetadata.IsTombstone) { XElement entryElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubEntryElementName); // Add Etag if (!string.IsNullOrEmpty(live.ServiceMetadata.ETag)) { entryElement.Add(new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.EtagElementName, live.ServiceMetadata.ETag)); } // Add TempId element if (!string.IsNullOrEmpty(tempId)) { entryElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.TempIdElementName, tempId)); } // Add Id element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubIdElementName, string.IsNullOrEmpty(live.ServiceMetadata.Id) ? string.Empty : live.ServiceMetadata.Id)); // Add title element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubTitleElementName, new XAttribute(FormatterConstants.AtomPubTypeElementName, "text"))); // Add updated element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubUpdatedElementName, XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Utc))); // Add author element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubAuthorElementName, new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubNameElementName))); // Write the <link> element entryElement.Add( new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubLinkElementName, new XAttribute(FormatterConstants.AtomPubRelAttrName, FormatterConstants.AtomPubEditLinkAttributeName), new XAttribute(FormatterConstants.AtomPubTitleElementName, typeName), new XAttribute(FormatterConstants.AtomPubHrefAttrName, (live.ServiceMetadata.EditUri != null) ? live.ServiceMetadata.EditUri.ToString() : string.Empty))); // Write the <category> element entryElement.Add( new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubCategoryElementName, new XAttribute(FormatterConstants.AtomPubTermAttrName, typeName), new XAttribute(FormatterConstants.AtomPubSchemaAttrName, FormatterConstants.ODataSchemaNamespace))); XElement contentElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubContentElementName); if (!emitPartial) { // Write the entity contents contentElement.Add(WriteEntityContents(live)); } // Add the contents entity to the outer entity. entryElement.Add(contentElement); return entryElement; } else { // Write the at:deleted-entry tombstone element XElement tombstoneElement = new XElement(FormatterConstants.AtomDeletedEntryNamespace + FormatterConstants.AtomDeletedEntryElementName); tombstoneElement.Add(new XElement(FormatterConstants.AtomNamespaceUri + FormatterConstants.AtomReferenceElementName, live.ServiceMetadata.Id)); tombstoneElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.AtomPubCategoryElementName, typeName)); return tombstoneElement; } }
/// <summary> /// Remove an entity from a conflicts collection. The conflicts collection contains all the /// SyncConflict instances that are detected when applying changes to the server. /// </summary> /// <param name="tableName">Global table name of the entity</param> /// <param name="entity">Entity to remove from the conflicts collection</param> private void RemoveEntityFromConflictCollection(string tableName, IOfflineEntity entity) { SyncId errorEntitySyncId = GenerateSyncIdForConflictingEntity(tableName, entity); // Lookup the conflict entity in the dictionary that we record conflicts. SyncConflict conflict; _syncEntityIdToConflictMapping.TryGetValue(errorEntitySyncId, out conflict); // If a conflict was found... if (null != conflict) { _conflictToSyncEntityIdMapping.Remove(conflict); _syncEntityIdToConflictMapping.Remove(errorEntitySyncId); // Remove the item from the conflicts collection. _conflicts.Remove(conflict); } }
private void CheckEntityServiceMetadataAndTempIds(Dictionary<string, IOfflineEntity> tempIdToEntityMapping, IOfflineEntity entity, string tempId, ChangeSetResponse response) { // Check service ID if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { throw new CacheControllerException(string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); } // If an entity has a temp id then it should not be a tombstone if (entity.ServiceMetadata.IsTombstone) { throw new CacheControllerException(string.Format("Service returned a tempId '{0}' in tombstoned entity.", tempId)); } // Check that the tempId was sent by client if (!tempIdToEntityMapping.ContainsKey(tempId)) { throw new CacheControllerException("Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); } // Add the entity to the Updated list. response.AddUpdatedItem(entity); // Once received, remove the tempId from the mapping list. tempIdToEntityMapping.Remove(tempId); }
/// <summary> /// Utility for invoking user code for conflict interceptors /// </summary> /// <param name="context">The context to pass as parameter to user code</param> /// <param name="mergedVersion">The merged version for Merge resolution</param> /// <param name="entityType">Entity type of the conflict being raised</param> /// <returns>Actual resolution picked by user</returns> internal SyncConflictResolution? InvokeConflictInterceptor(SyncConflictContext context, Type entityType, out IOfflineEntity mergedVersion) { SyncInterceptorsInfoWrapper wrapper = null; if (this.SyncInterceptors.TryGetValue(context.ScopeName, out wrapper)) { // Look for unfiltered Conflict and if that is null then look for filtered one. // Its an error to have both unfiltered and filtered ConflictInterceptor so both cannot be set. MethodInfo methodInfo = wrapper.ConflictInterceptor ?? wrapper.GetConflictInterceptor(entityType); if (methodInfo != null) { object[] inputParams = new object[] { context, null }; SyncConflictResolution resolution = (SyncConflictResolution)InvokeUserInterceptorMethod(methodInfo, OperationContext.Current.InstanceContext.GetServiceInstance(), inputParams); // Merged version is in the second parameter which is passed by reference. Look it up mergedVersion = (IOfflineEntity)inputParams[1]; return resolution; } } mergedVersion = null; return null; }
/// <summary> /// Writes the Json object tag and all its related elements. /// </summary> /// <param name="live">Actual entity whose value is to be emitted.</param> /// <param name="entryElement">This is the parent entry element that is needs to go in to. Will be null for regular items and non null for /// conflict/error items only</param> /// <param name="tempId">The tempId for the element if passed in by the client.</param> /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param> /// <returns>XElement representation of the entry element</returns> private XElement WriteEntry(IOfflineEntity live, XElement entryElement, string tempId, bool emitPartial) { string typeName = live.GetType().FullName; if (entryElement == null) { entryElement = new XElement("item", new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); } // Write the _metadata object for this entry XElement entryMetadata = new XElement(FormatterConstants.JsonSyncEntryMetadataElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); // Add the tempId to metadata if (!string.IsNullOrEmpty(tempId)) { entryMetadata.Add(new XElement(FormatterConstants.TempIdElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), tempId)); } // Add the uri to metadata entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryUriElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), string.IsNullOrEmpty(live.ServiceMetadata.Id) ? string.Empty : live.ServiceMetadata.Id)); // Add the etag to metadata if (!string.IsNullOrEmpty(live.ServiceMetadata.ETag)) { entryMetadata.Add(new XElement(FormatterConstants.EtagElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.ServiceMetadata.ETag)); } // Add the edituri to metadata if (live.ServiceMetadata.EditUri != null) { entryMetadata.Add(new XElement(FormatterConstants.EditUriElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.ServiceMetadata.EditUri)); } // Add the type to metadata entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryTypeElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), typeName)); // Write the tombstone element if (live.ServiceMetadata.IsTombstone) { entryMetadata.Add(new XElement(FormatterConstants.IsDeletedElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Boolean), true)); } else if (!emitPartial) { // Write the entity contents only when its not a tombstone WriteEntityContentsToElement(entryElement, live); } // Add the metadata to the entry element entryElement.Add(entryMetadata); return(entryElement); }
/// <summary> /// Callback for the Upload HttpWebRequest.BeginGetResponse call /// </summary> /// <param name="asyncResult">IAsyncResult object</param> void OnUploadGetResponseCompleted(IAsyncResult asyncResult) { AsyncArgsWrapper wrapper = asyncResult.AsyncState as AsyncArgsWrapper; wrapper.UploadResponse = new ChangeSetResponse(); HttpWebResponse response = null; try { try { response = wrapper.WebRequest.EndGetResponse(asyncResult) as HttpWebResponse; } catch (WebException we) { wrapper.UploadResponse.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.UploadResponse.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); // CreateInstance the SyncReader if (ApplicationContext.Current.Settings.BitMobileFormatterDisabled) { _syncReader = new ODataAtomReader(responseStream, _knownTypes); } else { _syncReader = new BMReader(responseStream, _knownTypes); } // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = this._syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (this._syncReader.HasTempId() && this._syncReader.HasConflictTempId()) { throw new CacheControllerException(string.Format("Service returned a TempId '{0}' in both live and conflicting entities.", this._syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (this._syncReader.HasTempId()) { tempId = this._syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId); } // If conflict if (this._syncReader.HasConflict()) { Conflict conflict = this._syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (this._syncReader.HasConflictTempId()) { tempId = this._syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId); } // Add conflict wrapper.UploadResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (this._syncReader.HasConflictTempId() && entity.ServiceMetadata.IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.ServiceMetadata.IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { wrapper.UploadResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: wrapper.UploadResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.UploadResponse.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Writes the Json object tag and all its related elements. /// </summary> /// <param name="live">Actual entity whose value is to be emitted.</param> /// <param name="entryElement">This is the parent entry element that is needs to go in to. Will be null for regular items and non null for /// conflict/error items only</param> /// <param name="tempId">The tempId for the element if passed in by the client.</param> /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param> /// <returns>XElement representation of the entry element</returns> private XElement WriteEntry(IOfflineEntity live, XElement entryElement, string tempId, bool emitPartial) { string typeName = live.GetType().FullName; if (entryElement == null) { entryElement = new XElement("item", new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); } // Write the _metadata object for this entry XElement entryMetadata = new XElement(FormatterConstants.JsonSyncEntryMetadataElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object)); // Add the tempId to metadata if (!string.IsNullOrEmpty(tempId)) { entryMetadata.Add(new XElement(FormatterConstants.TempIdElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), tempId)); } // Add the uri to metadata entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryUriElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), string.IsNullOrEmpty(live.ServiceMetadata.Id) ? string.Empty : live.ServiceMetadata.Id)); // Add the etag to metadata if (!string.IsNullOrEmpty(live.ServiceMetadata.ETag)) { entryMetadata.Add(new XElement(FormatterConstants.EtagElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.ServiceMetadata.ETag)); } // Add the edituri to metadata if (live.ServiceMetadata.EditUri != null) { entryMetadata.Add(new XElement(FormatterConstants.EditUriElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.ServiceMetadata.EditUri)); } // Add the type to metadata entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryTypeElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), typeName)); // Write the tombstone element if (live.ServiceMetadata.IsTombstone) { entryMetadata.Add(new XElement(FormatterConstants.IsDeletedElementName, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Boolean), true)); } else if (!emitPartial) { // Write the entity contents only when its not a tombstone WriteEntityContentsToElement(entryElement, live); } // Add the metadata to the entry element entryElement.Add(entryMetadata); return entryElement; }
/// <summary> /// Adds an IOfflineEntity and its associated Conflicting/Error entity as an Atom entry element /// </summary> /// <param name="live">Live Entity</param> /// <param name="liveTempId">TempId for the live entity</param> /// <param name="conflicting">Conflicting entity that will be sent in synnConflict or syncError extension</param> /// <param name="conflictingTempId">TempId for the conflicting entity</param> /// <param name="desc">Error description or the conflict resolution</param> /// <param name="isConflict">Denotes if its an errorElement or conflict. Used only when <paramref name="desc"/> is not null</param> /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param> public override void WriteItemInternal(IOfflineEntity live, string liveTempId, IOfflineEntity conflicting, string conflictingTempId, string desc, bool isConflict, bool emitMetadataOnly) { XElement entryElement = WriteEntry(live, liveTempId, emitMetadataOnly); if (conflicting != null) { XElement conflictElement = new XElement(FormatterConstants.SyncNamespace + ((isConflict) ? FormatterConstants.SyncConlflictElementName : FormatterConstants.SyncErrorElementName)); // Write the confliction resolution or errorElement. conflictElement.Add(new XElement(FormatterConstants.SyncNamespace + ((isConflict) ? FormatterConstants.ConflictResolutionElementName : FormatterConstants.ErrorDescriptionElementName), desc)); // Write the confliction resolution or errorElement. XElement conflictingEntryElement = new XElement(FormatterConstants.SyncNamespace + ((isConflict) ? FormatterConstants.ConflictEntryElementName : FormatterConstants.ErrorEntryElementName)); conflictingEntryElement.Add(WriteEntry(conflicting, conflictingTempId, false/*emitPartial*/)); conflictElement.Add(conflictingEntryElement); entryElement.Add(conflictElement); } _root.Add(entryElement); }
/// <summary> /// Called to add a Sync Error item /// </summary> /// <param name="liveEntry">Live version of the entity</param> /// <param name="errorEntry">Version of the entity that caused the error.</param> /// <param name="errorEntryTempId">TempIf for the entity that caused the error.</param> /// <param name="errorDescription">Description of error.</param> public virtual void AddErrorItem(IOfflineEntity liveEntry, IOfflineEntity errorEntry, string errorEntryTempId, string errorDescription) { if (liveEntry == null) { throw new ArgumentNullException("liveEntry"); } if (errorEntry == null) { throw new ArgumentNullException("errorEntry"); } WriteItemInternal(liveEntry, null/*liveEntryTempId*/, errorEntry/*conflicting*/, errorEntryTempId, errorDescription/*desc*/, false /*isconflict*/, false/*emitMetadataOnly*/); }
/// <summary> /// This writes the public contents of the Entity in the properties element. /// </summary> /// <param name="entity">Entity</param> /// <returns>XElement representation of the properties element</returns> XElement WriteEntityContents(IOfflineEntity entity) { XElement contentElement = new XElement(FormatterConstants.ODataMetadataNamespace + FormatterConstants.PropertiesElementName); // Write only the primary keys if its an tombstone var ent = (IEntity)entity; IEntityType type = ent.EntityType; // Write individual properties to the feed, foreach (EntityField field in type.Fields) { Type propType = field.Type; string name = field.Name; object value = ent.GetValue(field.Name); if (propType == typeof(IDbRef)) { propType = typeof (Guid); var dbRef = (IDbRef) value; value = dbRef.Id; name = "__" + name; } string edmType = FormatterUtilities.GetEdmType(propType); if (edmType == null) continue; if (propType.IsGenericType && propType.Name.Equals(FormatterConstants.NullableTypeName, StringComparison.InvariantCulture)) { // Its a Nullable<T> property propType = propType.GetGenericArguments()[0]; } if (value == null) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubIsNullElementName, true))); } else if (propType == FormatterConstants.DateTimeType || propType == FormatterConstants.TimeSpanType || propType == FormatterConstants.DateTimeOffsetType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), FormatterUtilities.ConvertDateTimeForType_Atom(value, propType))); } else if (propType != FormatterConstants.ByteArrayType) { contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), value)); } else { byte[] bytes = (byte[])value; contentElement.Add( new XElement(FormatterConstants.ODataDataNamespace + name, new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.AtomPubTypeElementName, edmType), Convert.ToBase64String(bytes))); } } return contentElement; }
/// <summary> /// Called to add a particular Entity /// </summary> /// <param name="entry">Entity to add to serialize to the stream</param> /// <param name="tempId">TempId for the Entity</param> public virtual void AddItem(IOfflineEntity entry, string tempId) { this.AddItem(entry, tempId, false); }
/// <summary> /// Utility for invoking user code for conflict interceptors /// </summary> /// <param name="context">The context to pass as parameter to user code</param> /// <param name="mergedVersion">The merged version for Merge resolution</param> /// <param name="entityType">Entity type of the conflict being raised</param> /// <returns>Actual resolution picked by user</returns> internal SyncConflictResolution?InvokeConflictInterceptor(SyncConflictContext context, Type entityType, out IOfflineEntity mergedVersion) { SyncInterceptorsInfoWrapper wrapper = null; if (this.SyncInterceptors.TryGetValue(context.ScopeName, out wrapper)) { // Look for unfiltered Conflict and if that is null then look for filtered one. // Its an error to have both unfiltered and filtered ConflictInterceptor so both cannot be set. MethodInfo methodInfo = wrapper.ConflictInterceptor ?? wrapper.GetConflictInterceptor(entityType); if (methodInfo != null) { object[] inputParams = new object[] { context, null }; SyncConflictResolution resolution = (SyncConflictResolution)InvokeUserInterceptorMethod(methodInfo, OperationContext.Current.InstanceContext.GetServiceInstance(), inputParams); // Merged version is in the second parameter which is passed by reference. Look it up mergedVersion = (IOfflineEntity)inputParams[1]; return(resolution); } } mergedVersion = null; return(null); }
/// <summary> /// This writes the public contents of the Entity to the passed in XElement. /// </summary> /// <param name="contentElement">The XElement to which the type properties is added to</param> /// <param name="entity">Entity</param> /// <returns>XElement representation of the properties element</returns> void WriteEntityContentsToElement(XElement contentElement, IOfflineEntity entity) { PropertyInfo[] properties = ReflectionUtility.GetPropertyInfoMapping(entity.GetType()); // Write individual properties to the feed, foreach (PropertyInfo fi in properties) { object objValue = fi.GetValue(entity, null); if (objValue == null) { contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Null), objValue)); } else if (fi.PropertyType == FormatterConstants.CharType || fi.PropertyType == FormatterConstants.StringType || fi.PropertyType == FormatterConstants.GuidType) { contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), objValue)); } else if (fi.PropertyType == FormatterConstants.DateTimeType || fi.PropertyType == FormatterConstants.TimeSpanType || fi.PropertyType == FormatterConstants.DateTimeOffsetType) { contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), FormatterUtilities.ConvertDateTimeForType_Json(objValue, fi.PropertyType))); } else if (fi.PropertyType == FormatterConstants.BoolType) { contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Boolean), objValue)); } else if (fi.PropertyType == FormatterConstants.ByteArrayType) { byte[] bytes = (byte[])objValue; contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), Convert.ToBase64String(bytes))); } else if (fi.PropertyType.IsGenericType && fi.PropertyType.Name.Equals(FormatterConstants.NullableTypeName, StringComparison.InvariantCulture)) { // Its a Nullable<T> property Type genericParamType = fi.PropertyType.GetGenericArguments()[0]; string elementType = JsonElementTypes.Number; if (genericParamType == FormatterConstants.BoolType) { elementType = JsonElementTypes.Boolean; } else if (genericParamType == FormatterConstants.DateTimeType || genericParamType == FormatterConstants.TimeSpanType || genericParamType == FormatterConstants.DateTimeOffsetType) { contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), FormatterUtilities.ConvertDateTimeForType_Json(objValue, genericParamType))); continue; } else if (genericParamType == FormatterConstants.CharType || genericParamType == FormatterConstants.GuidType) { elementType = JsonElementTypes.String; } contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, elementType), objValue)); } else // Its a number { contentElement.Add( new XElement(fi.Name, new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Number), objValue)); } } }
/// <summary> /// Called to add a Sync conflict item /// </summary> /// <param name="winningEntry">the winning entity</param> /// <param name="winningEntryTempId">the winning entity's tempId</param> /// <param name="losingEntry">The losing entity</param> /// <param name="losingEntryTempId">The losing entity's tempId</param> /// <param name="resolution">The conflict resolution aplied by the server</param> public virtual void AddConflictItem(IOfflineEntity winningEntry, string winningEntryTempId, IOfflineEntity losingEntry, string losingEntryTempId, SyncConflictResolution resolution) { if (winningEntry == null) { throw new ArgumentNullException("winningEntry"); } if (losingEntry == null) { throw new ArgumentNullException("losingEntry"); } WriteItemInternal(winningEntry, winningEntryTempId, losingEntry/*conflicting*/, losingEntryTempId, resolution.ToString() /*desc*/, true/*isconflict*/, false/*emitMetadataOnly*/); }
/// <summary> /// Get the entity from the incoming request that matches the primary key string of the entity passed as a parameter. /// </summary> /// <param name="primaryKeyToIncomingEntitiesMapping">Dictionary of mapping between primary key and the actual entities from incoming request.</param> /// <param name="entity">Entity for which to search a match in the incoming request.</param> /// <param name="isConflict">Indicates if this is called during conflict processing. Used to select appropriate error messages.</param> /// <returns>Entity from the incoming request.</returns> private static IOfflineEntity GetEntityFromIncomingRequest(Dictionary<string, IOfflineEntity> primaryKeyToIncomingEntitiesMapping, IOfflineEntity entity, bool isConflict) { // find the actual entity from the input list. var entityListInRequest = primaryKeyToIncomingEntitiesMapping.Where(e => e.Key.Equals(ReflectionUtility.GetPrimaryKeyString(entity), StringComparison.InvariantCultureIgnoreCase)).ToList(); // If no match is found, then throw an error. if (0 == entityListInRequest.Count) { if (isConflict) { throw SyncServiceException.CreateInternalServerError(Strings.ConflictEntityMissingInIncomingRequest); } throw SyncServiceException.CreateInternalServerError(Strings.ErrorEntityMissingInIncomingRequest); } // If the entity corresponding to the key is null, then throw an error. if (null == entityListInRequest[0].Value) { if (isConflict) { throw SyncServiceException.CreateInternalServerError(Strings.ConflictEntityMissingInIncomingRequest); } throw SyncServiceException.CreateInternalServerError(Strings.ErrorEntityMissingInIncomingRequest); } return entityListInRequest[0].Value; }
/// <summary> /// Writes the <entry/> tag and all its related elements. /// </summary> /// <param name="live">Actual entity whose value is to be emitted.</param> /// <param name="tempId">The temporary Id if any</param> /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param> /// <returns>XElement representation of the entry element</returns> private XElement WriteEntry(IOfflineEntity live, string tempId, bool emitPartial) { var entity = (IEntity)live; string typeName = string.Format("{0}.{1}.{2}", RootNamespace, entity.EntityType.Schema, entity.EntityType.Name); if (!live.ServiceMetadata.IsTombstone) { XElement entryElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubEntryElementName); // Add Etag if (!string.IsNullOrEmpty(live.ServiceMetadata.ETag)) { entryElement.Add(new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.EtagElementName, live.ServiceMetadata.ETag)); } // Add TempId element if (!string.IsNullOrEmpty(tempId)) { entryElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.TempIdElementName, tempId)); } // Add Id element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubIdElementName, string.IsNullOrEmpty(live.ServiceMetadata.Id) ? string.Empty : live.ServiceMetadata.Id)); // Add title element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubTitleElementName, new XAttribute(FormatterConstants.AtomPubTypeElementName, "text"))); // Add updated element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubUpdatedElementName, XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Utc))); // Add author element entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubAuthorElementName, new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubNameElementName))); // Write the <link> element entryElement.Add( new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubLinkElementName, new XAttribute(FormatterConstants.AtomPubRelAttrName, FormatterConstants.AtomPubEditLinkAttributeName), new XAttribute(FormatterConstants.AtomPubTitleElementName, typeName), new XAttribute(FormatterConstants.AtomPubHrefAttrName, (live.ServiceMetadata.EditUri != null) ? live.ServiceMetadata.EditUri.ToString() : string.Empty))); // Write the <category> element entryElement.Add( new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubCategoryElementName, new XAttribute(FormatterConstants.AtomPubTermAttrName, typeName), new XAttribute(FormatterConstants.AtomPubSchemaAttrName, FormatterConstants.ODataSchemaNamespace))); XElement contentElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubContentElementName); if (!emitPartial) { // Write the entity contents contentElement.Add(WriteEntityContents(live)); } // Add the contents entity to the outer entity. entryElement.Add(contentElement); return(entryElement); } else { // Write the at:deleted-entry tombstone element XElement tombstoneElement = new XElement(FormatterConstants.AtomDeletedEntryNamespace + FormatterConstants.AtomDeletedEntryElementName); tombstoneElement.Add(new XElement(FormatterConstants.AtomNamespaceUri + FormatterConstants.AtomReferenceElementName, live.ServiceMetadata.Id)); tombstoneElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.AtomPubCategoryElementName, typeName)); return(tombstoneElement); } }
//Note: Removed ref here private void AddEntityToDataSet(IOfflineEntity objectToRead, DataSet dataSet, string tableName) { Type t = objectToRead.GetType(); PropertyInfo[] properties = t.GetProperties(); Dictionary <string, string> globalToLocalMappingInfo = _globalToLocalPropertyMapping[t]; Dictionary <string, string> localToGlobalMappingInfo = _localToGlobalPropertyMapping[t]; // We need to create the table if it doesn't already exist DataTable dataTable = dataSet.Tables[tableName]; if (dataTable == null) { dataSet.Tables.Add(tableName); dataTable = dataSet.Tables[tableName]; // Create the columns of the table based off the // properties we reflected from the type foreach (PropertyInfo property in properties) { // Do not add service related properties. if (IsOfflineEntityServiceProperty(property.PropertyType)) { continue; } Type type = property.PropertyType; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable <>)) { type = property.PropertyType.GetGenericArguments()[0]; } if (globalToLocalMappingInfo.ContainsKey(property.Name)) { dataTable.Columns.Add(globalToLocalMappingInfo[property.Name], type); } else { dataTable.Columns.Add(property.Name, type); } } // SQL Provider does not set primary keys on DataTable. So set Primary Keys if its not set ReflectionUtility.SetDataTablePrimaryKeys(dataTable, objectToRead.GetType(), globalToLocalMappingInfo); } dataTable.BeginLoadData(); // Now the table should exist so add records to it. var columnArray = new object[dataTable.Columns.Count]; for (int i = 0; i <= columnArray.Length - 1; i++) { // The property name to be set on the IOfflineEntity is the ColumnName unless there is a // SyncEntityPropertyMappingAttribute for the property in which case the name of that property is used instead. string colName = dataTable.Columns[i].ColumnName; if (localToGlobalMappingInfo.ContainsKey(colName)) { colName = localToGlobalMappingInfo[colName]; } if (objectToRead.ServiceMetadata.IsTombstone && !dataTable.PrimaryKey.Contains(dataTable.Columns[i])) { columnArray[i] = DBNull.Value; } else { columnArray[i] = t.InvokeMember(colName, BindingFlags.GetProperty, null, objectToRead, new object[0]); if (columnArray[i] != null) { if (columnArray[i].Equals(new Guid("00000000-0000-0000-0000-000000000000"))) { columnArray[i] = DBNull.Value; } } } } // Add the row to the table in the dataset DataRow row = dataTable.LoadDataRow(columnArray, true); // If the entity is a tombstone, set the DataRowState property by calling the Delete or SetAdded method. if (objectToRead.ServiceMetadata.IsTombstone) { row.Delete(); } else { row.SetAdded(); } dataTable.EndLoadData(); }
/// <summary> /// Copies the individual properties from the entity back in to the DataTable's first row. /// This should be used only when merging a user conflict resolution back in to a DataRow. /// This returns the merged results as an object array /// </summary> /// <param name="entity">Entity from which to read values</param> /// <param name="table">The Table whose first DataRow will be updated.</param> /// <returns>The contents of the DataRow as an object array</returns> internal object[] CopyEntityToDataRow(IOfflineEntity entity, DataTable table) { Debug.Assert(table.Rows.Count == 1, "table.Rows.Count ==1"); if (table.Rows.Count != 1) { throw new InvalidOperationException("Cannot copy Entity to a DataTable whose row count != 1"); } Dictionary<string, string> mappingInfo = _localToGlobalPropertyMapping[entity.GetType()]; // Check for tombstones bool isRowDeleted = false; if (table.Rows[0].RowState == DataRowState.Deleted) { isRowDeleted = true; table.Rows[0].RejectChanges(); } // Retrieve the current row values object[] rowValues = table.Rows[0].ItemArray; PropertyInfo[] properties = entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); for (int i = 0; i < table.Columns.Count; i++) { // Iterate over each non sync column and read its value from the entity if (IsSyncSpecificColumn(table.Columns[i].ColumnName)) { continue; } // Read the value of the property string columnName = null; mappingInfo.TryGetValue(table.Columns[i].ColumnName, out columnName); columnName = columnName ?? table.Columns[i].ColumnName; // Retrieve the PropertyInfo PropertyInfo info = properties.Where(e => e.Name.Equals(columnName, StringComparison.Ordinal)).FirstOrDefault(); Debug.Assert(info != null, "PropertyInfo is not null."); // Get the property's value and put it in the object array rowValues[i] = info.GetValue(entity, null) ?? DBNull.Value; } // Write the row values back to the DataRow table.Rows[0].ItemArray = rowValues; if (isRowDeleted) { table.Rows[0].Delete(); } return rowValues; }
/// <summary> /// Build the OData Atom primary keystring representation /// </summary> /// <param name="live">Entity for which primary key is required</param> /// <returns>String representation of the primary key</returns> public static string GetPrimaryKeyString(IOfflineEntity live) { StringBuilder builder = new StringBuilder(); string sep = string.Empty; foreach (PropertyInfo keyInfo in GetPrimaryKeysPropertyInfoMapping(live.GetType())) { if (keyInfo.PropertyType == FormatterConstants.GuidType) builder.AppendFormat("{0}{1}=guid'{2}'", sep, keyInfo.Name, keyInfo.GetValue(live, null)); else if (keyInfo.PropertyType == FormatterConstants.StringType) builder.AppendFormat("{0}{1}='{2}'", sep, keyInfo.Name, keyInfo.GetValue(live, null)); else builder.AppendFormat("{0}{1}={2}", sep, keyInfo.Name, keyInfo.GetValue(live, null)); if(string.IsNullOrEmpty(sep)) sep = ", "; } return builder.ToString(); }
internal void GetEntityFromDataRow(DataColumnCollection columnCollection, DataRow row, IOfflineEntity objectToConvert) { Type t = objectToConvert.GetType(); Dictionary<string, string> mappingInfo = _localToGlobalPropertyMapping[t]; bool isDeleted = false; if (row.RowState == DataRowState.Deleted) { isDeleted = true; row.RejectChanges(); } // Note: Call BeginEdit only after check for Deleted row state, // otherwise this call will crash. row.BeginEdit(); for (Int32 i = 0; i <= columnCollection.Count - 1; i++) { if (IsSyncSpecificColumn(columnCollection[i].ColumnName)) { continue; } //NOTE: the datarow column names must match exactly (including case) to the IOfflineEntity's property names object columnValue = row[columnCollection[i].ColumnName]; if (DBNull.Value != columnValue) { t.InvokeMember((mappingInfo.ContainsKey(columnCollection[i].ColumnName)) ? mappingInfo[columnCollection[i].ColumnName] : columnCollection[i].ColumnName, BindingFlags.SetProperty, null, objectToConvert, new[] {columnValue}); } } if (isDeleted) { row.Delete(); // Mark the IsTombstone field if the RowState was deleted. objectToConvert.ServiceMetadata.IsTombstone = true; } row.EndEdit(); }
internal void AddUpdatedItem(IOfflineEntity item) { this._updatedItems.Add(item); }
//Note: Removed ref here private void AddEntityToDataSet(IOfflineEntity objectToRead, DataSet dataSet, string tableName) { Type t = objectToRead.GetType(); PropertyInfo[] properties = t.GetProperties(); Dictionary<string, string> globalToLocalMappingInfo = _globalToLocalPropertyMapping[t]; Dictionary<string, string> localToGlobalMappingInfo = _localToGlobalPropertyMapping[t]; // We need to create the table if it doesn't already exist DataTable dataTable = dataSet.Tables[tableName]; if (dataTable == null) { dataSet.Tables.Add(tableName); dataTable = dataSet.Tables[tableName]; // Create the columns of the table based off the // properties we reflected from the type foreach (PropertyInfo property in properties) { // Do not add service related properties. if (IsOfflineEntityServiceProperty(property.PropertyType)) { continue; } Type type = property.PropertyType; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = property.PropertyType.GetGenericArguments()[0]; } if (globalToLocalMappingInfo.ContainsKey(property.Name)) { dataTable.Columns.Add(globalToLocalMappingInfo[property.Name], type); } else { dataTable.Columns.Add(property.Name, type); } } // SQL Provider does not set primary keys on DataTable. So set Primary Keys if its not set ReflectionUtility.SetDataTablePrimaryKeys(dataTable, objectToRead.GetType(), globalToLocalMappingInfo); } dataTable.BeginLoadData(); // Now the table should exist so add records to it. var columnArray = new object[dataTable.Columns.Count]; for (int i = 0; i <= columnArray.Length - 1; i++) { // The property name to be set on the IOfflineEntity is the ColumnName unless there is a // SyncEntityPropertyMappingAttribute for the property in which case the name of that property is used instead. string colName = dataTable.Columns[i].ColumnName; if (localToGlobalMappingInfo.ContainsKey(colName)) { colName = localToGlobalMappingInfo[colName]; } if (objectToRead.ServiceMetadata.IsTombstone && !dataTable.PrimaryKey.Contains(dataTable.Columns[i])) { columnArray[i] = DBNull.Value; } else { columnArray[i] = t.InvokeMember(colName, BindingFlags.GetProperty, null, objectToRead, new object[0]); if (columnArray[i] != null) { if (columnArray[i].Equals(new Guid("00000000-0000-0000-0000-000000000000"))) columnArray[i] = DBNull.Value; } } } // Add the row to the table in the dataset DataRow row = dataTable.LoadDataRow(columnArray, true); // If the entity is a tombstone, set the DataRowState property by calling the Delete or SetAdded method. if (objectToRead.ServiceMetadata.IsTombstone) { row.Delete(); } else { row.SetAdded(); } dataTable.EndLoadData(); }
/// <summary> /// Generate and save the SyncId of the LiveEntity. /// This value is used later after all changes are applied to project on the latest /// server knowledge and add positive exceptions to the updated client knowledge that is sent in the response. /// </summary> /// <param name="tableName">Table name that the entity represents</param> /// <param name="c">Conflicting entity for which we need to save the SyncId.</param> private SyncId GenerateSyncIdForConflictingEntity(string tableName, IOfflineEntity c) { Debug.Assert(null != c, "null != c"); var pkValues = new List<object>(); // Get the primary key values from the LiveEntity Type entityType = c.GetType(); // The ordering of keys here is assumed to be the same order in which SyncId's are generated. // Otherwise, behavior is undefined and incorrect positive exceptions are added. PropertyInfo[] primaryKeyPropertyInfoMapping = ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(entityType); foreach (var propertyInfo in primaryKeyPropertyInfoMapping) { pkValues.Add(propertyInfo.GetValue(c, null)); } // Generate the SyncId for the conflicting item. SyncId rowId = SyncUtil.InitRowId(tableName, pkValues); return rowId; }
/// <summary> /// Writes the <entry/> tag and all its related elements. /// </summary> /// <param name="live">Actual entity whose value is to be emitted.</param> /// <param name="tempId">The temporary Id if any</param> /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param> /// <returns>XElement representation of the entry element</returns> private XElement WriteEntry(IOfflineEntity live, string tempId, bool emitPartial) { string typeName = live.GetType().FullName; if (!live.ServiceMetadata.IsTombstone) { var entryElement = new XElement(FormatterConstants.AtomXmlNamespace + C.Entry); entryElement.Add(new XAttribute(C.Caption, typeName)); if (!emitPartial) { // Write the entity contents WriteEntityContents(entryElement, live); } return entryElement; } // Write the at:deleted-entry tombstone element var tombstoneElement = new XElement(FormatterConstants.AtomXmlNamespace + C.Tombstone, new XAttribute(C.Caption, typeName)); Guid id; if (!Guid.TryParse(live.ServiceMetadata.Id, out id)) { string[] split = live.ServiceMetadata.Id.Split('\''); id = Guid.Parse(split[1]); } tombstoneElement.Add(id.ToString()); return tombstoneElement; }