private static void CopyChanges(this ConnectionDetail connectionDetail, Label existingLabel, Label newLabel, List <Guid> deletedIds) { if (newLabel == null) { return; } foreach (var localizedLabel in newLabel.LocalizedLabels) { var existingLocalizedLabel = existingLabel.LocalizedLabels.SingleOrDefault(ll => ll.MetadataId == localizedLabel.MetadataId); if (existingLocalizedLabel == null) { existingLabel.LocalizedLabels.Add(localizedLabel); } else { connectionDetail.CopyChanges(existingLocalizedLabel, localizedLabel, deletedIds); } } for (var i = existingLabel.LocalizedLabels.Count - 1; i >= 0; i--) { if (deletedIds.Contains(existingLabel.LocalizedLabels[i].MetadataId.Value)) { existingLabel.LocalizedLabels.RemoveAt(i); } } if (newLabel.UserLocalizedLabel != null) { connectionDetail.CopyChanges(existingLabel.UserLocalizedLabel, newLabel.UserLocalizedLabel, deletedIds); } }
private static void CopyChanges(this ConnectionDetail connectionDetail, MetadataBase existingItem, MetadataBase newItem, List <Guid> deletedIds) { foreach (var prop in existingItem.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!Attribute.IsDefined(prop, typeof(DataMemberAttribute))) { continue; } var newValue = prop.GetValue(newItem); var existingValue = prop.GetValue(existingItem) as MetadataBase; var type = prop.PropertyType; if (type.IsArray) { type = type.GetElementType(); } if (!typeof(MetadataBase).IsAssignableFrom(type) && !typeof(Label).IsAssignableFrom(type)) { if (newItem.HasChanged != false) { prop.SetValue(existingItem, newValue); } } else if (typeof(Label).IsAssignableFrom(type)) { if (newItem.HasChanged != false) { connectionDetail.CopyChanges((Label)prop.GetValue(existingItem), (Label)newValue, deletedIds); } } else if (newValue != null) { if (prop.PropertyType.IsArray) { connectionDetail.CopyChanges(existingItem, prop, (MetadataBase[])newValue, deletedIds); } else { if (existingValue.MetadataId == ((MetadataBase)newValue).MetadataId) { connectionDetail.CopyChanges(existingValue, (MetadataBase)newValue, deletedIds); } else { prop.SetValue(existingItem, newValue); } } } else if (existingValue != null && deletedIds.Contains(existingValue.MetadataId.Value)) { prop.SetValue(existingItem, null); } } }
private static void CopyChanges(this ConnectionDetail connectionDetail, object source, PropertyInfo sourceProperty, MetadataBase[] newArray, List <Guid> deletedIds) { var existingArray = (MetadataBase[])sourceProperty.GetValue(source); var existingList = new List <MetadataBase>(existingArray); // Add any new items and update any modified ones foreach (var newItem in newArray) { var existingItem = existingList.SingleOrDefault(e => e.MetadataId == newItem.MetadataId); if (existingItem == null) { existingList.Add(newItem); } else { connectionDetail.CopyChanges(existingItem, newItem, deletedIds); } } // Store the new array var updatedArray = Array.CreateInstance(sourceProperty.PropertyType.GetElementType(), existingList.Count); for (var i = 0; i < existingList.Count; i++) { updatedArray.SetValue(existingList[i], i); } sourceProperty.SetValue(source, updatedArray); if (deletedIds.Count > 0) { connectionDetail.RemoveDeletedItems(source, sourceProperty, deletedIds); } }
/// <summary> /// Updates the <see cref="MetadataCache"/> /// </summary> /// <param name="flush">Indicates if the existing cache should be flushed and a full new copy of the metadata should be retrieved</param> public static Task UpdateMetadataCache(this ConnectionDetail connectionDetail, bool flush) { if (connectionDetail.OrganizationMajorVersion < 8) { throw new NotSupportedException("Metadata cache is only supported on Dynamics CRM 2016 or later"); } // If there's already an update in progress, don't start a new one if (!MetadataCacheLoader.IsCompleted) { return(MetadataCacheLoader); } // Load the metadata in a background task var task = new Task <MetadataCache>(() => { // Load & update metadata cache var metadataCachePath = Path.Combine(Path.GetDirectoryName(ConnectionsList.ConnectionsListFilePath), "..", "Metadata", connectionDetail.ConnectionId + ".xml.gz"); metadataCachePath = Path.IsPathRooted(metadataCachePath) ? metadataCachePath : Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName, metadataCachePath); var metadataSerializer = new DataContractSerializer(typeof(MetadataCache)); var metadataCache = _metadataCache; if (metadataCache == null && File.Exists(metadataCachePath) && !flush) { try { using (var stream = File.OpenRead(metadataCachePath)) using (var gz = new GZipStream(stream, CompressionMode.Decompress)) { metadataCache = (MetadataCache)metadataSerializer.ReadObject(gz); } } catch { // If the cache file isn't readable for any reason, throw it away and download a new copy } } // Get all the metadata that's changed since the last connection // If this query changes, increment the version number to ensure any previously cached versions are flushed const int queryVersion = 2; var metadataQuery = new RetrieveMetadataChangesRequest { ClientVersionStamp = !flush && metadataCache?.MetadataQueryVersion == queryVersion ? metadataCache?.ClientVersionStamp : null, Query = new EntityQueryExpression { Properties = new MetadataPropertiesExpression { AllProperties = true }, AttributeQuery = new AttributeQueryExpression { Properties = new MetadataPropertiesExpression { AllProperties = true } }, RelationshipQuery = new RelationshipQueryExpression { Properties = new MetadataPropertiesExpression { AllProperties = true } } }, DeletedMetadataFilters = DeletedMetadataFilters.All }; RetrieveMetadataChangesResponse metadataUpdate; try { metadataUpdate = (RetrieveMetadataChangesResponse)connectionDetail.GetServiceClient().Execute(metadataQuery); } catch (FaultException <Microsoft.Xrm.Sdk.OrganizationServiceFault> ex) { // If the last connection was too long ago, we need to request all the metadata, not just the changes if (ex.Detail.ErrorCode == unchecked ((int)0x80044352)) { _metadataCache = null; metadataQuery.ClientVersionStamp = null; metadataUpdate = (RetrieveMetadataChangesResponse)connectionDetail.GetServiceClient().Execute(metadataQuery); } else { throw; } } if (metadataCache == null || flush) { // If we didn't have a previous cache, just start a fresh one metadataCache = new MetadataCache(); metadataCache.EntityMetadata = metadataUpdate.EntityMetadata.ToArray(); metadataCache.MetadataQueryVersion = queryVersion; } else { // We had a cached version of the metadata before, so now we need to merge in the changes var deletedIds = metadataUpdate.DeletedMetadata == null ? new List <Guid>() : metadataUpdate.DeletedMetadata.SelectMany(kvp => kvp.Value).Distinct().ToList(); connectionDetail.CopyChanges(metadataCache, typeof(MetadataCache).GetProperty(nameof(Utils.MetadataCache.EntityMetadata)), metadataUpdate.EntityMetadata.ToArray(), deletedIds); } _metadataCache = metadataCache; // Save the latest metadata cache if (metadataCache.ClientVersionStamp != metadataUpdate.ServerVersionStamp || metadataCache.MetadataQueryVersion != queryVersion) { metadataCache.ClientVersionStamp = metadataUpdate.ServerVersionStamp; metadataCache.MetadataQueryVersion = queryVersion; Directory.CreateDirectory(Path.GetDirectoryName(metadataCachePath)); using (var stream = File.Create(metadataCachePath)) using (var gz = new GZipStream(stream, CompressionLevel.Optimal)) { metadataSerializer.WriteObject(gz, metadataCache); } } return(metadataCache); }); task.ConfigureAwait(false); // Store the current metadata loading task and run it MetadataCacheLoader = task; task.Start(); return(task); }