Динамическое обновление CRM 2011

Running Dynamics CRM 2011. 3. Необходимо периодически обновлять миллионы клиентских записей (дельта-обновления). Использование стандартного обновления (один за другим) занимает несколько недель. Также мы не хотим напрямую касаться БД, так как это может сломать материал в будущем.

Существует ли метод массового обновления в API-интерфейсе Dynamics CRM 2011/REST API, который мы можем использовать? (WhatWhereHow)

Ответы

Ответ 1

Я понимаю, что это пост старше 2 лет, но я могу добавить к нему, если кто-то еще прочитает его и имеет аналогичную потребность.

Ответ Питера Маджида нацелен на то, что процессы CRM запрашивают по одной записи за раз. Нет массового редактирования, который работает так, как вы ищите. Я рекомендую вам не касаться базы данных напрямую, если вам нужна поддержка Microsoft.

Если вы просматриваете периодические обновления миллионов записей, у вас есть несколько вариантов. Подумайте о том, как использовать Scribe или создать собственную собственную утилиту импорта или script с помощью SDK CRM.

Scribe, вероятно, будет вашим лучшим вариантом, поскольку он экономичен для импорта данных и позволит вам легко обновлять и вставлять из того же файла.

Если вы пишете свою собственную .Net/SDK-based утилиту, я бы предложил сделать ее многопоточным и либо программно разбить ваш входной файл в памяти или на диске, и каждый поток работать с его собственным подмножеством данных - это, конечно, если порядок выполнения не должен быть хронологическим в соответствии с содержимым входного файла. Если вы можете разделить и захватить входной файл по нескольким потокам, вы можете значительно сократить общее время выполнения. Кроме того, если ваша корпоративная политика позволяет вам иметь доступ к одному из CRM-серверов, и вы можете разместить свой код непосредственно на сервере и выполнить его оттуда - вы можете устранить сетевую задержку между рабочей станцией, на которой запущен код и веб-службы CRM.

И последнее, но не менее важное: если этот большой объем данных импорта поступает из другой системы, вы можете написать плагин CRM для запуска в сообщениях Retrieve и RetrieveMultiple (события) в CRM для вашей конкретной сущности, программно получить требуемые данные из другой системы (и если другая система недоступна - просто используйте кешированную копию в CRM) и сохраняйте CRM в актуальном состоянии в режиме реального времени или на основе "последнего кэширования". Это, конечно, больше усилий по кодированию, но это потенциально исключает необходимость запуска большого задания синхронизации каждые несколько недель.

Ответ 2

Да и нет, в основном нет. Кто-то может исправить меня, если я ошибаюсь, и в этом случае я с удовольствием отредактирую/удалю свой ответ, но все, что сделано в Dynamics CRM, выполняется по одному. Он даже не пытается обрабатывать встроенные вставки/обновления/удаления. Поэтому, если вы не начнете напрямую работать с DB, вам потребуются недели.

webservice разрешает "массовые" вставки/удаляет/обновляет, но я помещаю "bulk" в кавычки, потому что все, что он делает, настраивает асинхронный процесс, где он выполняет все соответствующие операции с данными - yep - по одному за раз. Там раздел SDK, который обращается к подобному управлению данными (связанному). И чтобы обновить записи таким образом, вам придется сначала перенести накладные расходы на выбор всех данных, которые вы хотите обновить, а затем создать файл xml, который содержит данные, и, наконец, обновить данные (помните: одна строка за раз). Таким образом, на самом деле было бы более эффективно просто прокручивать ваши данные и выдавать запрос Update для каждого из вас.

(Отмечу, что наша организация не испытала каких-либо незабываемых проблем, связанных с прямым доступом к БД, чтобы справиться с тем, что нет в SDK, и я не видел ничего в своих личных интернет-чтениях, которые предполагают, что у других есть.)

Edit:

Смотрите iFirefly answer ниже для некоторых других отличных способов решения этой проблемы.

Ответ 3

Я понимаю, что это старый вопрос, но он поднимается высоко на "CRM Bulk Update", поэтому здесь следует упомянуть версию Rollup 12 ExtecuteMultiple - ее не собирается обойти вашу проблему (массивный объем), потому что, как отмечают iFirefly и Peter, CRM делает все по одному за раз. Он делает все запросы в одном конверте, позволяя CRM обрабатывать выполнение каждого обновления и уменьшать количество раундов между вашим приложением и сервером, если вы делаете запрос Update для каждой записи.

Ответ 4

Не уверен, как это будет происходить с миллионами записей, но вы можете выбрать свои записи, а затем нажмите кнопку "Изменить" на ленте. Это вызовет диалог "Редактировать несколько записей". Любые сделанные вами изменения будут применены ко всем вашим записям.

Ответ 5

API BulkUpdate хорошо работает для меня; он в 10 раз быстрее, чем обновление записей по одному. Ниже приведен фрагмент, который выполняет массовое обновление:

    public override ExecuteMultipleResponse BulkUpdate(List<Entity> entities)
    {
        ExecuteMultipleRequest request = new ExecuteMultipleRequest()
        {
            Settings = new ExecuteMultipleSettings()
            {
                ContinueOnError = true,
                ReturnResponses = true
            },
            Requests = new OrganizationRequestCollection()
        };

        for (int i = 0; i < entities.Count; i++)
        {
            request.Requests.Add(new UpdateRequest() { Target = entities[i] });
        }

        return (ExecuteMultipleResponse) ServiceContext.Execute(request);
    }

Ответ 6

Это довольно старый вопрос, но никто не упомянул постигший (но и самый сложный) способ обновления/создания огромного количества записей в CRM 201X - с использованием встроенной функции импорта, что вполне возможно с помощью CRM SDK. В статье есть прекрасная статья MSDN: https://msdn.microsoft.com/en-us/library/gg328321(v=crm.5).aspx. Короче говоря, вы должны:

1) Создайте файл Excel, содержащий данные, которые вы хотите импортировать (просто экспортируйте некоторые данные из CRM 201X и проверьте, как выглядит структура, помните, что первые три столбца скрыты)

2) создать объект Import Map (указать созданный файл)

3) При необходимости создайте сопоставления столбцов

4) Создайте объект Import и ImportFile, предоставляя правильные сопоставления

5) Разбирайте данные с помощью ParseImportRequest

6) Данные Tranform с использованием TransformImportRequest

7) Импорт данных с помощью ImportRecordsImportRequest

Это были шаги для CRM 2011, теперь в 2017 году у нас больше доступных версий, и между ними есть небольшие различия. Проверьте образец, доступный в MSDN и SDK: https://msdn.microsoft.com/en-us/library/hh547396(v=crm.5).aspx

Конечно, пункт 1 будет самой сложной частью, потому что вам нужно создать файл XML или docx, идеально соответствующий тому, что ожидает CRM, но я предполагаю, что вы делаете это из внешнего приложения, поэтому вы можете использовать некоторые замечательные .NET, которые упростят работу.

Я никогда не видел ничего быстрее, чем стандартный импорт CRM, когда дело доходит до обновления/создания записей, даже если вы отправляетесь на запросы parallelism и пакетного обновления.

Если что-то пойдет не так с сайтами MSDN, я публикую здесь пример из приведенной выше ссылки, в котором показано, как программно импортировать данные в CRM:

using System;
using System.ServiceModel;
using System.Collections.Generic;
using System.Linq;

// These namespaces are found in the Microsoft.Xrm.Sdk.dll assembly
// located in the SDK\bin folder of the SDK download.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;

// These namespaces are found in the Microsoft.Crm.Sdk.Proxy.dll assembly
// located in the SDK\bin folder of the SDK download.
using Microsoft.Crm.Sdk.Messages;

namespace Microsoft.Crm.Sdk.Samples
{    
    /// <summary>
    /// This sample shows how to define a complex mapping for importing and then use the
    /// Microsoft Dynamics CRM 2011 API to bulk import records with that mapping.
    /// </summary>
    public class ImportWithCreate
    {
        #region Class Level Members

        private OrganizationServiceProxy _serviceProxy;
        private DateTime _executionDate;

        #endregion

        /// <summary>
        /// This method first connects to the organization service. Afterwards,
        /// auditing is enabled on the organization, account entity, and a couple
        /// of attributes.
        /// </summary>
        /// <param name="serverConfig">Contains server connection information.</param>
        /// <param name="promptforDelete">When True, the user will be prompted to delete all
        /// created entities.</param>
        public void Run(ServerConnection.Configuration serverConfig, bool promptforDelete)
        {
            using (_serviceProxy = ServerConnection.GetOrganizationProxy(serverConfig))
            {
                // This statement is required to enable early bound type support.
                _serviceProxy.EnableProxyTypes();

                // Log the start time to ensure deletion of records created during execution.
                _executionDate = DateTime.Today;
                ImportRecords();
                DeleteRequiredRecords(promptforDelete);
            }
        }

        /// <summary>
        /// Imports records to Microsoft Dynamics CRM from the specified .csv file.
        /// </summary>
        public void ImportRecords()
        {
            // Create an import map.
            ImportMap importMap = new ImportMap()
            {
                Name = "Import Map " + DateTime.Now.Ticks.ToString(),
                Source = "Import Accounts.csv",
                Description = "Description of data being imported",
                EntitiesPerFile =
                    new OptionSetValue((int)ImportMapEntitiesPerFile.SingleEntityPerFile),
                EntityState = EntityState.Created
            };
            Guid importMapId = _serviceProxy.Create(importMap);

            // Create column mappings.

            #region Column One Mappings
            // Create a column mapping for a 'text' type field.
            ColumnMapping colMapping1 = new ColumnMapping()
            {
                // Set source properties.
                SourceAttributeName = "src_name",
                SourceEntityName = "Account_1",

                // Set target properties.
                TargetAttributeName = "name",
                TargetEntityName = Account.EntityLogicalName,

                // Relate this column mapping with the data map.
                ImportMapId =
                    new EntityReference(ImportMap.EntityLogicalName, importMapId),

                // Force this column to be processed.
                ProcessCode =
                    new OptionSetValue((int)ColumnMappingProcessCode.Process)
            };

            // Create the mapping.
            Guid colMappingId1 = _serviceProxy.Create(colMapping1);
            #endregion

            #region Column Two Mappings
            // Create a column mapping for a 'lookup' type field.
            ColumnMapping colMapping2 = new ColumnMapping()
            {
                // Set source properties.
                SourceAttributeName = "src_parent",
                SourceEntityName = "Account_1",

                // Set target properties.
                TargetAttributeName = "parentaccountid",
                TargetEntityName = Account.EntityLogicalName,

                // Relate this column mapping with the data map.
                ImportMapId =
                    new EntityReference(ImportMap.EntityLogicalName, importMapId),

                // Force this column to be processed.
                ProcessCode =
                    new OptionSetValue((int)ColumnMappingProcessCode.Process),
            };

            // Create the mapping.
            Guid colMappingId2 = _serviceProxy.Create(colMapping2);

            // Because we created a column mapping of type lookup, we need to specify lookup details in a lookupmapping.
            // One lookupmapping will be for the parent account, and the other for the current record.
            // This lookupmapping is important because without it the current record
            // cannot be used as the parent of another record.

            // Create a lookup mapping to the parent account.  
            LookUpMapping parentLookupMapping = new LookUpMapping()
            {
                // Relate this mapping with its parent column mapping.
                ColumnMappingId =
                    new EntityReference(ColumnMapping.EntityLogicalName, colMappingId2),

                // Force this column to be processed.
                ProcessCode =
                    new OptionSetValue((int)LookUpMappingProcessCode.Process),

                // Set the lookup for an account entity by its name attribute.
                LookUpEntityName = Account.EntityLogicalName,
                LookUpAttributeName = "name",
                LookUpSourceCode =
                    new OptionSetValue((int)LookUpMappingLookUpSourceCode.System)
            };

            // Create the lookup mapping.
            Guid parentLookupMappingId = _serviceProxy.Create(parentLookupMapping);

            // Create a lookup on the current record "src_name" so that this record can
            // be used as the parent account for another record being imported.
            // Without this lookup, no record using this account as its parent will be imported.
            LookUpMapping currentLookUpMapping = new LookUpMapping()
            {
                // Relate this lookup with its parent column mapping.
                ColumnMappingId =
                    new EntityReference(ColumnMapping.EntityLogicalName, colMappingId2),

                // Force this column to be processed.
                ProcessCode =
                    new OptionSetValue((int)LookUpMappingProcessCode.Process),

                // Set the lookup for the current record by its src_name attribute.
                LookUpAttributeName = "src_name",
                LookUpEntityName = "Account_1",
                LookUpSourceCode =
                    new OptionSetValue((int)LookUpMappingLookUpSourceCode.Source)
            };

            // Create the lookup mapping
            Guid currentLookupMappingId = _serviceProxy.Create(currentLookUpMapping);
            #endregion

            #region Column Three Mappings
            // Create a column mapping for a 'picklist' type field
            ColumnMapping colMapping3 = new ColumnMapping()
            {
                // Set source properties
                SourceAttributeName = "src_addresstype",
                SourceEntityName = "Account_1",

                // Set target properties
                TargetAttributeName = "address1_addresstypecode",
                TargetEntityName = Account.EntityLogicalName,

                // Relate this column mapping with its parent data map
                ImportMapId =
                    new EntityReference(ImportMap.EntityLogicalName, importMapId),

                // Force this column to be processed
                ProcessCode =
                    new OptionSetValue((int)ColumnMappingProcessCode.Process)
            };

            // Create the mapping
            Guid colMappingId3 = _serviceProxy.Create(colMapping3);

            // Because we created a column mapping of type picklist, we need to specify picklist details in a picklistMapping
            PickListMapping pickListMapping1 = new PickListMapping()
            {
                SourceValue = "bill",
                TargetValue = 1,

                // Relate this column mapping with its column mapping data map
                ColumnMappingId =
                    new EntityReference(ColumnMapping.EntityLogicalName, colMappingId3),

                // Force this column to be processed
                ProcessCode =
                    new OptionSetValue((int)PickListMappingProcessCode.Process)
            };

            // Create the mapping
            Guid picklistMappingId1 = _serviceProxy.Create(pickListMapping1);

            // Need a picklist mapping for every address type code expected
            PickListMapping pickListMapping2 = new PickListMapping()
            {
                SourceValue = "ship",
                TargetValue = 2,

                // Relate this column mapping with its column mapping data map
                ColumnMappingId =
                    new EntityReference(ColumnMapping.EntityLogicalName, colMappingId3),

                // Force this column to be processed
                ProcessCode =
                    new OptionSetValue((int)PickListMappingProcessCode.Process)
            };

            // Create the mapping
            Guid picklistMappingId2 = _serviceProxy.Create(pickListMapping2);
            #endregion

            // Create Import
            Import import = new Import()
            {
                // IsImport is obsolete; use ModeCode to declare Create or Update.
                ModeCode = new OptionSetValue((int)ImportModeCode.Create),
                Name = "Importing data"
            };
            Guid importId = _serviceProxy.Create(import);

            // Create Import File.
            ImportFile importFile = new ImportFile()
            {
                Content = BulkImportHelper.ReadCsvFile("Import Accounts.csv"), // Read contents from disk.
                Name = "Account record import",
                IsFirstRowHeader = true,
                ImportMapId = new EntityReference(ImportMap.EntityLogicalName, importMapId),
                UseSystemMap = false,
                Source = "Import Accounts.csv",
                SourceEntityName = "Account_1",
                TargetEntityName = Account.EntityLogicalName,
                ImportId = new EntityReference(Import.EntityLogicalName, importId),
                EnableDuplicateDetection = false,
                FieldDelimiterCode =
                    new OptionSetValue((int)ImportFileFieldDelimiterCode.Comma),
                DataDelimiterCode =
                    new OptionSetValue((int)ImportFileDataDelimiterCode.DoubleQuote),
                ProcessCode =
                    new OptionSetValue((int)ImportFileProcessCode.Process)
            };

            // Get the current user to set as record owner.
            WhoAmIRequest systemUserRequest = new WhoAmIRequest();
            WhoAmIResponse systemUserResponse =
                (WhoAmIResponse)_serviceProxy.Execute(systemUserRequest);

            // Set the owner ID.                
            importFile.RecordsOwnerId =
                new EntityReference(SystemUser.EntityLogicalName, systemUserResponse.UserId);

            Guid importFileId = _serviceProxy.Create(importFile);

            // Retrieve the header columns used in the import file.
            GetHeaderColumnsImportFileRequest headerColumnsRequest = new GetHeaderColumnsImportFileRequest()
            {
                ImportFileId = importFileId
            };
            GetHeaderColumnsImportFileResponse headerColumnsResponse =
                (GetHeaderColumnsImportFileResponse)_serviceProxy.Execute(headerColumnsRequest);

            // Output the header columns.
            int columnNum = 1;
            foreach (string headerName in headerColumnsResponse.Columns)
            {
                Console.WriteLine("Column[" + columnNum.ToString() + "] = " + headerName);
                columnNum++;
            }

            // Parse the import file.
            ParseImportRequest parseImportRequest = new ParseImportRequest()
            {
                ImportId = importId
            };
            ParseImportResponse parseImportResponse =
                (ParseImportResponse)_serviceProxy.Execute(parseImportRequest);
            Console.WriteLine("Waiting for Parse async job to complete");
            BulkImportHelper.WaitForAsyncJobCompletion(_serviceProxy, parseImportResponse.AsyncOperationId);
            BulkImportHelper.ReportErrors(_serviceProxy, importFileId);

            // Retrieve the first two distinct values for column 1 from the parse table.
            // NOTE: You must create the parse table first using the ParseImport message.
            // The parse table is not accessible after ImportRecordsImportResponse is called.
            GetDistinctValuesImportFileRequest distinctValuesRequest = new GetDistinctValuesImportFileRequest()
            {
                columnNumber = 1,
                ImportFileId = importFileId,
                pageNumber = 1,
                recordsPerPage = 2,
            };
            GetDistinctValuesImportFileResponse distinctValuesResponse =
                (GetDistinctValuesImportFileResponse)_serviceProxy.Execute(distinctValuesRequest);

            // Output the distinct values.  In this case: (column 1, row 1) and (column 1, row 2).
            int cellNum = 1;
            foreach (string cellValue in distinctValuesResponse.Values)
            {
                Console.WriteLine("(1, " + cellNum.ToString() + "): " + cellValue);
                Console.WriteLine(cellValue);
                cellNum++;
            }

            // Retrieve data from the parse table.
            // NOTE: You must create the parse table first using the ParseImport message.
            // The parse table is not accessible after ImportRecordsImportResponse is called.
            RetrieveParsedDataImportFileRequest parsedDataRequest = new RetrieveParsedDataImportFileRequest()
            {
                ImportFileId = importFileId,
                PagingInfo = new PagingInfo()
                {
                    // Specify the number of entity instances returned per page.
                    Count = 2,
                    // Specify the number of pages returned from the query.
                    PageNumber = 1,
                    // Specify a total number of entity instances returned.
                    PagingCookie = "1"
                }
            };

            RetrieveParsedDataImportFileResponse parsedDataResponse =
                (RetrieveParsedDataImportFileResponse)_serviceProxy.Execute(parsedDataRequest);

            // Output the first two rows retrieved.
            int rowCount = 1;
            foreach (string[] rows in parsedDataResponse.Values)
            {
                int colCount = 1;
                foreach (string column in rows)
                {
                    Console.WriteLine("(" + rowCount.ToString() + "," + colCount.ToString() + ") = " + column);
                    colCount++;
                }
                rowCount++;
            }

            // Transform the import
            TransformImportRequest transformImportRequest = new TransformImportRequest()
            {
                ImportId = importId
            };
            TransformImportResponse transformImportResponse =
                (TransformImportResponse)_serviceProxy.Execute(transformImportRequest);
            Console.WriteLine("Waiting for Transform async job to complete");
            BulkImportHelper.WaitForAsyncJobCompletion(_serviceProxy, transformImportResponse.AsyncOperationId);
            BulkImportHelper.ReportErrors(_serviceProxy, importFileId);

            // Upload the records.
            ImportRecordsImportRequest importRequest = new ImportRecordsImportRequest()
            {
                ImportId = importId
            };
            ImportRecordsImportResponse importResponse =
                (ImportRecordsImportResponse)_serviceProxy.Execute(importRequest);
            Console.WriteLine("Waiting for ImportRecords async job to complete");
            BulkImportHelper.WaitForAsyncJobCompletion(_serviceProxy, importResponse.AsyncOperationId);
            BulkImportHelper.ReportErrors(_serviceProxy, importFileId);
        }

        /// <summary>
        /// Deletes any entity records that were created for this sample.
        /// <param name="prompt">Indicates whether to prompt the user 
        /// to delete the records created in this sample.</param>
        /// </summary>
        public void DeleteRequiredRecords(bool prompt)
        {
            bool toBeDeleted = true;

            if (prompt)
            {
                // Ask the user if the created entities should be deleted.
                Console.Write("\nDo you want these entity records deleted? (y/n) [y]: ");
                String answer = Console.ReadLine();
                if (answer.StartsWith("y") ||
                    answer.StartsWith("Y") ||
                    answer == String.Empty)
                {
                    toBeDeleted = true;
                }
                else
                {
                    toBeDeleted = false;
                }
            }

            if (toBeDeleted)
            {
                // Retrieve all account records created in this sample.
                QueryExpression query = new QueryExpression()
                {
                    EntityName = Account.EntityLogicalName,
                    Criteria = new FilterExpression()
                    {
                        Conditions =
                        {
                            new ConditionExpression("createdon", ConditionOperator.OnOrAfter, _executionDate),
                        }
                    },
                    ColumnSet = new ColumnSet(false)
                };
                var accountsCreated = _serviceProxy.RetrieveMultiple(query).Entities;

                // Delete all records created in this sample.
                foreach (var account in accountsCreated)
                {
                    _serviceProxy.Delete(Account.EntityLogicalName, account.Id);
                }

                Console.WriteLine("Entity record(s) have been deleted.");
            }
        }
        #region Main method

        /// <summary>
        /// Standard Main() method used by most SDK samples.
        /// </summary>
        /// <param name="args"></param>
        static public void Main(string[] args)
        {
            try
            {
                // Obtain the target organization web address and client logon 
                // credentials from the user.
                ServerConnection serverConnect = new ServerConnection();
                ServerConnection.Configuration config = serverConnect.GetServerConfiguration();

                var app = new ImportWithCreate();
                app.Run(config, true);
            }

            catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Timestamp: {0}", ex.Detail.Timestamp);
                Console.WriteLine("Code: {0}", ex.Detail.ErrorCode);
                Console.WriteLine("Message: {0}", ex.Detail.Message);
                Console.WriteLine("Trace: {0}", ex.Detail.TraceText);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
            }
            catch (System.TimeoutException ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Message: {0}", ex.Message);
                Console.WriteLine("Stack Trace: {0}", ex.StackTrace);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.InnerException.Message ? "No Inner Fault" : ex.InnerException.Message);
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine(ex.Message);

                // Display the details of the inner exception.
                if (ex.InnerException != null)
                {
                    Console.WriteLine(ex.InnerException.Message);

                    FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> fe = ex.InnerException
                        as FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>;
                    if (fe != null)
                    {
                        Console.WriteLine("Timestamp: {0}", fe.Detail.Timestamp);
                        Console.WriteLine("Code: {0}", fe.Detail.ErrorCode);
                        Console.WriteLine("Message: {0}", fe.Detail.Message);
                        Console.WriteLine("Trace: {0}", fe.Detail.TraceText);
                        Console.WriteLine("Inner Fault: {0}",
                            null == fe.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                    }
                }
            }
            // Additional exceptions to catch: SecurityTokenValidationException, ExpiredSecurityTokenException,
            // SecurityAccessDeniedException, MessageSecurityException, and SecurityNegotiationException.

            finally
            {
                Console.WriteLine("Press <Enter> to exit.");
                Console.ReadLine();
            }
        }
        #endregion Main method
    }
}

Ответ 7

Я работал над очень большим проектом миграции данных для Dynamics CRM 2011. Нам нужно было загрузить около 3 миллионов записей за выходные. Я закончил создание консольного приложения (один поток) и выполнил несколько экземпляров на нескольких машинах. Каждое консольное приложение имело идентификатор (1, 2 и т.д.) И отвечало за загрузку сегментов данных на основе уникального предложения SQL WHERE, которое соответствовало идентификатору приложения.

Вы можете сделать то же самое с обновлениями. Каждый экземпляр может запрашивать подмножество записей для обновления и может выполнять обновления через SDK. Поскольку мы загрузили миллионы записей за выходные, я думаю, что вы могли бы выполнить миллионы обновлений (если относительно небольшие) всего за несколько часов.

Ответ 8

Команда Microsoft PFE по динамике CRM написала новая Другая CRM-библиотека SDK, в которой используется распараллеливание к объемным запросам выполнения, обеспечивающим безопасность потоков.

Вы можете попробовать: Parallel Execute Requests Мне было бы интересно узнать, работает ли оно и масштабируется до миллионов записей.

Ответ 9

CRM не реализует способ обновления объемных данных; есть 3 способа улучшить производительность операций массового обновления, но внутренне они не могут изменить тот факт, что записи CRM записываются один за другим. В основном идеи:

  • сократить время, потраченное на связь с сервером CRM.
  • использовать parallelism для выполнения нескольких операций одновременно
  • убедитесь, что процесс обновления НЕ запускает никаких рабочих процессов/плагинов. В противном случае вы никогда не увидите конца процесса...

3 способа повысить производительность работы в целом:

  • После RollUp 12 существует функция ExecuteMultipleRequest, которая позволяет отправлять до 1000 запросов одновременно. Это означает, что вы можете сэкономить некоторое время на отправке 1000 запросов на веб-службу CRM, однако эти запросы обрабатываются один за другим. Поэтому, если ваш CRM-сервер настроен правильно, скорее всего, этот метод не поможет слишком много.
  • Вы можете использовать экземпляр OrganizationServiceContext для массового обновления. OrganizationServiceContext реализует единицу рабочего шаблона, чтобы вы могли выполнять несколько обновлений и передавать эти операции на сервер за один вызов. Сравнивая с ExecuteMultipleRequest, он не имеет ограничения на количество запросов, но если он обнаруживает сбой во время обновления, он откатит все изменения.
  • Использовать многопоточность или многозадачность. В любом случае это ускорит скорость, но они, вероятно, вызовут некоторые сбои подключения или SQL-ошибки, поэтому вам нужно будет добавить в код некоторую логику повтора.