private void VerifyKeysInFetchXml(Record recordsConfig)
        {
            log.Log($"Verifying keys exist in record ...");

            log.Log($"Retrieving keys for {recordsConfig.LogicalName} ...");
            var keys = metadataHelper.GetAlternateKeys(recordsConfig.LogicalName)?.ToArray();

            log.Log(
                $"Finished retrieving keys for {recordsConfig.LogicalName}. Keys: {keys?.Aggregate((e1, e2) => $"{e1},{e2}")}");

            if (keys == null)
            {
                log.LogWarning($"Can't find alterate keys definition for entity: '{recordsConfig.LogicalName}'.");
                return;
            }

            foreach (var key in keys)
            {
                if (!Regex.IsMatch(recordsConfig.FetchXml, $"<attribute\\s*name\\s*=\\s*(\"|'){key}(\"|')") &&
                    Regex.IsMatch(recordsConfig.FetchXml, $"<attribute\\s*name\\s*=\\s*(\"|').*(\"|')"))
                {
                    throw new Exception($"Can't find alterate key '{key}' in FetchXML of entity {recordsConfig.LogicalName}.");
                }
            }

            log.Log($"Finished verifying keys exist in record.");
        }
Example #2
0
        private IEnumerable <EntityReference> GetFilteredRecords(string logicalName,
                                                                 IEnumerable <ExportedEntityDefinition> recordDefinitions, RecordsFilterMode mode)
        {
            log.Log($"Getting filtered records ...");

            var uniqueRecords = new List <EntityReference>();

            log.Log($"Getting existing records for '{logicalName}'...");

            log.Log($"Getting ID field name ...");
            var idField = MetadataHelpers
                          .GetEntityAttribute <string>(service, logicalName, MetadataHelpers.EntityAttribute.PrimaryIdAttribute,
                                                       CrmService.OrgId);

            log.Log($"ID field name: {idField}.");

            log.Log($"Getting alternate key names ...");
            var alternateKeys = metadataHelper.GetAlternateKeys(logicalName).ToArray();

            log.Log($"Found {alternateKeys.Length} keys.");

            var definitions             = recordDefinitions.ToArray();
            var idDefinitions           = definitions.Where(d => !d.IsUseAlternateKeys || !d.Record.KeyAttributes.Any()).ToArray();
            var alternateKeyDefinitions = definitions.Where(d => d.IsUseAlternateKeys && d.Record.KeyAttributes.Any()).ToArray();

            Parallel.ForEach(idDefinitions.GroupBy(d => d.QueryKey),
                             new ParallelOptions
            {
                MaxDegreeOfParallelism = CrmService.Threads
            },
                             group =>
            {
                queries.TryGetValue(group.Key, out var fetchXml);
                fetchXml = BuildExistingRecordsFetchXml(fetchXml, idField);

                foreach (var relationKey in group.GroupBy(g => g.RelationId, g => g).Select(g => g.Key))
                {
                    var updatedFetchXml = UpdateRelatedConditionInFetchXml(fetchXml, relationKey);
                    var existingRecords = RetrieveRecords(updatedFetchXml);

                    switch (mode)
                    {
                    case RecordsFilterMode.UniqueInDestination:
                        uniqueRecords.AddRange(existingRecords.Select(r => r.Id).Except(group.Select(d => d.Record.Id))
                                               .Select(id => new EntityReference(logicalName, id)));
                        break;

                    case RecordsFilterMode.UniqueInSource:
                        uniqueRecords.AddRange(group.Select(d => d.Record.Id).Except(existingRecords.Select(r => r.Id))
                                               .Select(id => new EntityReference(logicalName, id)));
                        break;

                    case RecordsFilterMode.Common:
                        uniqueRecords.AddRange(group.Select(d => d.Record.Id).Intersect(existingRecords.Select(r => r.Id))
                                               .Select(id => new EntityReference(logicalName, id)));
                        break;

                    default:
                        throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unique records mode is out of range.");
                    }
                }
            });

            Parallel.ForEach(alternateKeyDefinitions.GroupBy(d => d.QueryKey),
                             new ParallelOptions
            {
                MaxDegreeOfParallelism = CrmService.Threads
            },
                             group =>
            {
                queries.TryGetValue(group.Key, out var fetchXml);
                fetchXml = BuildExistingRecordsFetchXml(fetchXml, idField, alternateKeys);

                foreach (var relationKey in group.GroupBy(g => g.RelationId, g => g).Select(g => g.Key))
                {
                    var updatedFetchXml = UpdateRelatedConditionInFetchXml(fetchXml, relationKey);
                    var existingRecords = RetrieveRecords(updatedFetchXml);

                    var exportedRecords = group.Select(d => d.Record);
                    IEnumerable <Guid> existingExceptExportedIds;

                    switch (mode)
                    {
                    case RecordsFilterMode.UniqueInDestination:
                        existingExceptExportedIds = existingRecords
                                                    .Where(r => exportedRecords.All(e => alternateKeys.All(
                                                                                        a =>
                        {
                            var firstValue  = r.GetAttributeValue <object>(a);
                            var secondValue = e.GetAttributeValue <object>(a);

                            return((firstValue != null && secondValue != null && !firstValue.Equals(secondValue)) ||
                                   (firstValue != secondValue));
                        })))
                                                    .Select(r => r.Id);
                        break;

                    case RecordsFilterMode.UniqueInSource:
                        existingExceptExportedIds = exportedRecords
                                                    .Where(r => existingRecords.All(e => alternateKeys.All(
                                                                                        a =>
                        {
                            var firstValue  = r.GetAttributeValue <object>(a);
                            var secondValue = e.GetAttributeValue <object>(a);

                            return((firstValue != null && secondValue != null && !firstValue.Equals(secondValue)) ||
                                   (firstValue != secondValue));
                        })))
                                                    .Select(r => r.Id);
                        break;

                    default:
                        throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unique records mode is out of range.");
                    }

                    uniqueRecords.AddRange(existingExceptExportedIds
                                           .Select(id => new EntityReference(logicalName, id)));
                }
            });

            log.Log($"Got existing records for '{logicalName}'. Count: {uniqueRecords.Count}");

            return(uniqueRecords);
        }