/// <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> /// 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> /// 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> /// 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> /// 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); } }
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(); }
/// <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))); } } }
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(); }
/// <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> /// 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; }
/// <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.GetServiceMetadata().IsTombstone) { XElement entryElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubEntryElementName); // Add Etag if (!string.IsNullOrEmpty(live.GetServiceMetadata().ETag)) { entryElement.Add(new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.EtagElementName, live.GetServiceMetadata().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.GetServiceMetadata().Id) ? string.Empty : live.GetServiceMetadata().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))); // 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.GetServiceMetadata().EditUri != null) ? live.GetServiceMetadata().EditUri.ToString() : string.Empty))); // Write the <category> element entryElement.Add( new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubCategoryElementName, new XAttribute(FormatterConstants.AtomPubTermAttrName, live.GetType().FullName), 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; } // Write the at:deleted-entry tombstone element XElement tombstoneElement = new XElement(FormatterConstants.AtomDeletedEntryNamespace + FormatterConstants.AtomDeletedEntryElementName); tombstoneElement.Add(new XElement(FormatterConstants.AtomNamespaceUri + FormatterConstants.AtomReferenceElementName, live.GetServiceMetadata().Id)); tombstoneElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.AtomPubCategoryElementName, typeName)); return tombstoneElement; }
/// <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) { 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), // TODO fix me 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, live.GetType().FullName), 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> /// 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 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); }
//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 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> /// Parse the Id string and populate key fields of the object. This is called when the client sends tombstones /// and the Key fields are not present in the input payload. /// The approach used is to parse each key field individually and set it to a property with the same name. /// For example: For http://host/service.svc/Tag(ID=1), we parse out ID=1 and then populate the ID property of the targetObject with the /// value 1. /// </summary> /// <param name="entity">Entity for which we need to set the key fields.</param> /// <param name="serviceBaseUri"> /// Base Uri of the service. The ServiceMetadata.Id property has the Uri which we want to strip off before attempting to /// parse the keys and values. /// </param> internal static void ParseIdStringAndPopulateKeyFields(IOfflineEntity entity, Uri serviceBaseUri) { Debug.Assert(null != entity); Debug.Assert(!String.IsNullOrEmpty(entity.ServiceMetadata.Id)); string idString = entity.ServiceMetadata.Id; // Remove the ServiceUri and the entity type name from the EntityId // Note: Case sensitive comparisons are made since the client isn't supposed to change the Id for an // entity. string serviceUriWithTableName = serviceBaseUri + "/" + entity.GetType().Name; // If the Id does not have the correct format of serviceUri/TableName, then we should not continue further. if (!idString.StartsWith(serviceUriWithTableName, false, CultureInfo.InvariantCulture)) { throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, idString)); } // Remove the host and the table name from the Id. // Example: http://host/service.svc/table(id=123) will become (id=123) idString = idString.Remove(0, serviceUriWithTableName.Length); // Remove leading '/' if any. After this the id string is of the format (ID=1) or (ID=guid'<guidValue>') // If there are multiple Id values then, they are comma separated.));)) if (idString.StartsWith("/")) { idString = idString.Substring(1); } // Make sure the ( and ) parenthesis exist. if (String.IsNullOrEmpty(idString) || idString[0] != '(' || idString[idString.Length - 1] != ')') { throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, entity.ServiceMetadata.Id)); } // Remove the ( and ) characters. idString = idString.Substring(1, idString.Length - 2); // Get the key properties for the entity. var keyFieldPropertyInfoList = ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(entity.GetType()); // Split the string and get individual keyvalue pair strings. They key and value are still a single string separated by '='. // for types such as Guid, the value will be prefixed with 'guid'. string[] primaryKeyValuePair = idString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); // Throw if there is a mismatch between the key count of the entity and that passed in the URI. if (primaryKeyValuePair.Length != keyFieldPropertyInfoList.Length) { throw SyncServiceException.CreateBadRequestError(String.Format(Strings.BadRequestKeyCountMismatch, entity.ServiceMetadata.Id, entity.GetType())); } // At this point, we have key value pairs of the form "ID=1". foreach (var keyValuePair in primaryKeyValuePair) { // example: ID=1 string[] keyValue = keyValuePair.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); // Every key should have only 2 components. Debug.Assert(2 == keyValue.Length); // Get the property from the key list. string key = keyValue[0].Trim(); var propertyInfo = keyFieldPropertyInfoList.Where(p => p.Name == key).FirstOrDefault(); if (null == propertyInfo) { throw SyncServiceException.CreateBadRequestError( String.Format(Strings.BadRequestKeyNotFoundInResource, key, entity.ServiceMetadata.Id, entity.GetType())); } // Get typed value of the value. object targetValue; // Parse the value based on the target type. if (!ODataIdParser.TryKeyStringToPrimitive(keyValue[1], propertyInfo.PropertyType, out targetValue)) { throw SyncServiceException.CreateBadRequestError( String.Format(Strings.UnableToParseKeyValueForProperty, keyValuePair, entity.ServiceMetadata.Id)); } // Set the property value. propertyInfo.SetValue(entity, targetValue, null); } }
//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> /// 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> /// 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> /// 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; }