Guid.NewGuid () возвращает повторяющиеся значения при использовании в цикле Parallel.For - PullRequest
4 голосов
/ 13 февраля 2020

У меня есть приложение, которое работает с API. Таким образом, он выполняет запрос для всех идентификаторов в объекте, а затем должен запрашивать каждый элемент по одному для каждого идентификатора. Я делаю это в Parallel.For l oop и добавляю данные каждого элемента в строку в таблице данных. Затем я использую sqlbulkcopy для отправки таблицы данных на серверную таблицу SQL.

Если я делаю это без использования Parallel.For, он прекрасно работает. Однако, с Parallel.For, эта строка:

workrow["id"] = Guid.NewGuid();

генерирует дубликаты Guids. Он делает это часто и приводит к тому, что данные не загружаются в серверную таблицу SQL, поскольку строка идентификатора в SQL является первичным ключом и не допускает дублирования. Я попытался заблокировать:

                    lock (lockobject)
                    {
                        workrow["id"] = Guid.NewGuid();
                    }

Это не помогло.
Я пытался не назначать идентификатор этому полю в надежде, что SQL сгенерирует его (у него есть newid () в этом поле ). Это говорит о том, что он не может вставить ноль. Кажется, я не могу просто удалить поле id из таблицы данных, потому что при выполнении sqlbulkcopy столбцы не выравниваются.

Может ли кто-нибудь помочь мне здесь? Мне либо нужно выяснить, как заставить Guid.NewGuid () перестать создавать дубликаты, ИЛИ мне нужно найти способ не передавать идентификатор (всегда первое поле в таблице данных), чтобы SQL генерировал идентификатор .

Вот код, который я использую для генерации одной из таблиц:

        public static DataTable MakeWorkflowTable()
        {
            DataTable Workflow = new DataTable("Workflow");
            DataColumn id = new DataColumn("id", System.Type.GetType("System.Guid"));
            Workflow.Columns.Add(id);
            DataColumn OrgInfoID = new DataColumn("OrgInfoID", System.Type.GetType("System.Guid"));
            Workflow.Columns.Add(OrgInfoID);
            DataColumn Name = new DataColumn("Name", System.Type.GetType("System.String"));
            Workflow.Columns.Add(Name);
            DataColumn Active = new DataColumn("Active", System.Type.GetType("System.String"));
            Workflow.Columns.Add(Active);
            DataColumn Description = new DataColumn("Description", System.Type.GetType("System.String"));
            Workflow.Columns.Add(Description);
            DataColumn Object = new DataColumn("Object", System.Type.GetType("System.String"));
            Workflow.Columns.Add(Object);
            DataColumn Formula = new DataColumn("Formula", System.Type.GetType("System.String"));
            Workflow.Columns.Add(Formula);
            DataColumn ManageableState = new DataColumn("ManageableState", System.Type.GetType("System.String"));
            Workflow.Columns.Add(ManageableState);
            DataColumn NameSpacePrefix = new DataColumn("NameSpacePrefix", System.Type.GetType("System.String"));
            Workflow.Columns.Add(NameSpacePrefix);
            DataColumn TDACount = new DataColumn("TDACount", System.Type.GetType("System.Int32"));
            Workflow.Columns.Add(TDACount);
            DataColumn TriggerType = new DataColumn("TriggerType", System.Type.GetType("System.String"));
            Workflow.Columns.Add(TriggerType);
            DataColumn CreatedDate = new DataColumn("CreatedDate", System.Type.GetType("System.DateTime"));
            Workflow.Columns.Add(CreatedDate);
            DataColumn CreatedBy = new DataColumn("CreatedBy", System.Type.GetType("System.String"));
            Workflow.Columns.Add(CreatedBy);
            DataColumn LastModifiedDate = new DataColumn("LastModifiedDate", System.Type.GetType("System.DateTime"));
            Workflow.Columns.Add(LastModifiedDate);
            DataColumn LastModifiedBy = new DataColumn("LastModifiedBy", System.Type.GetType("System.String"));
            Workflow.Columns.Add(LastModifiedBy);
            return Workflow;
        }

Вот код, который я использую для отправки на сервер SQL:

        public static void SendDTtoDB(ref DataTable dt, ref SqlConnection cnn, string TableName)
        {
            using (SqlBulkCopy bulkCopy = new SqlBulkCopy(cnn))
            {
                bulkCopy.DestinationTableName =
                    TableName;
                try
                {
                    bulkCopy.WriteToServer(dt);
                    dt.Clear();
                }
                catch (Exception e)
                {
                    logger.Warn("SendDTtoDB {TableName}: ORGID: {ORGID} : {Message}", TableName, dt.Rows[0]["OrgInfoID"], e.Message.ToString());
                    if (e.Message.ToString().Contains("PRIMARY KEY"))
                    {
                        foreach(DataRow row in dt.Rows)
                        {
                            logger.Warn("ID: {id}", row["id"]);
                        }
                    }
                }
            }

        }

Как вы можете видеть в операторе catch, я настроил его на запись идентификаторов в журнал, чтобы я мог видеть их для себя и, конечно же, там есть дубликат. Так расстраивает! Я действительно не хочу вынимать Parallel.For и однопоточно, если мне не нужно.

Для запроса, вот код с Parallel.For

              if (qr.totalSize > 0)
                {
                    object lockobject = new object();
                    Parallel.For(0, qr.records.Length, i =>
                    {
                        ToolingService.CustomTab1 vr = new ToolingService.CustomTab1();

                        vr = (ToolingService.CustomTab1)qr.records[i];
                        string mdSOQL = "Select FullName, description, ManageableState, MasterLabel, NamespacePrefix, Type, Url, CreatedDate, CreatedBy.Name, "
                            + "LastModifiedDate, LastModifiedBy.Name From CustomTab where id='" + vr.Id + "'";
                        ToolingService.QueryResult mdqr = new ToolingService.QueryResult();
                        ToolingService.CustomTab1 vrmd = new ToolingService.CustomTab1();
                        mdqr = ts.query(mdSOQL);
                        vrmd = (ToolingService.CustomTab1)mdqr.records[0];

                        DataRow workrow = CustomTabs.NewRow();
                        lock (lockobject)
                        {
                            workrow["id"] = Guid.NewGuid();
                        }
                        workrow["OrgInfoID"] = _orgDBID;
                        workrow["FullName"] = vrmd.FullName;
                        workrow["Description"] = vrmd.Description ?? Convert.DBNull;
                        workrow["ManageableState"] = vrmd.ManageableState;
                        workrow["MasterLabel"] = vrmd.MasterLabel ?? Convert.DBNull;
                        workrow["NameSpacePrefix"] = vrmd.NamespacePrefix ?? Convert.DBNull;
                        workrow["Type"] = vrmd.Type ?? Convert.DBNull;
                        workrow["URL"] = vrmd.Url ?? Convert.DBNull;
                        workrow["CreatedDate"] = vrmd.CreatedDate ?? Convert.DBNull;
                        if (vrmd.CreatedBy == null)
                        {
                            workrow["CreatedBy"] = Convert.DBNull;
                        }
                        else
                        {
                            workrow["CreatedBy"] = vrmd.CreatedBy.Name;
                        }
                        workrow["LastModifiedDate"] = vrmd.LastModifiedDate ?? Convert.DBNull;
                        if (vrmd.LastModifiedBy == null)
                        {
                            workrow["LastModifiedBy"] = Convert.DBNull;
                        }
                        else
                        {
                            workrow["LastModifiedBy"] = vrmd.LastModifiedBy.Name;
                        }
                        lock (CustomTabs)
                        {
                            CustomTabs.Rows.Add(workrow);
                        }

                    });
                    OrgTables.SendDTtoDB(ref CustomTabs, ref _cnn, "OrgCustomTabs");

Ответы [ 2 ]

10 голосов
/ 13 февраля 2020

Я видел эту проблему раньше. С Guid.NewGuid() проблем нет, но DataTable не является потокобезопасным!

DataTable просто не предназначен или не предназначен для одновременного использования (в частности, там, где присутствует какая-либо мутация). ).

См. Безопасность потоков для DataTable

Также связано: c# Внутренний индекс DataGridView DataTable поврежден параллельно l oop

Это сделано в al oop, потому что я должен снова и снова запрашивать API, чтобы получить каждый элемент в объекте. Я делаю Parallel.For l oop, чтобы я мог ускорить процесс. Если мне нужно получать 450 или более предметов по одному, я бы хотел многопоточность для скорости. Доступ к базе данных не выполняется в l oop, просто создание таблицы данных происходит потому, что, как только я получаю данные из API, мне нужно их сохранить.

Вы можете создать тип и добавить их многопоточно в ConcurrentBag<T> или ConcurrentQueue<T> (или другую параллельную коллекцию, см. MS Docs ) - они являются поточно-ориентированными: )

Тогда после этого вы можете построить DataTable с помощью одного потока. Или, возможно, пропустите весь DataTable, если это возможно для вашего варианта использования.

4 голосов
/ 13 февраля 2020

Дело в том, что необходимость использовать lock внутри Parallel.ForEach в DataTable, что-то вроде победы над целью использования Parallel.ForEach в первую очередь; однако я удивлен, что вы не получаете исключений, когда вызываете DataRow workrow = CustomTabs.NewRow();, потому что в моем тесте я получаю исключение из-за повреждения индекса. Я должен был на самом деле обернуть звонок на NewRow внутри замка. Примерно так:

Parallel.ForEach(data, x =>
            {
                DataRow row = null;
                lock (lockRow)
                {
                    row = dt.NewRow();
                    row["Guid"] = Guid.NewGuid();
                }
...
                lock(lockObj)
                   dt.Rows.Add(row);

Где lockObj и lockRow - это 2 отдельных объекта c, например,

static  object lockObj = new  object();
static  object lockRow = new object();

И это сработало, добавив 1 миллион строк на DataTable и убедившись, что все направляющие были уникальными.

Учитывая все вышесказанное, я настоятельно рекомендую написать код, предложенный Джулианом, или создать класс, реализующий IDataReader (который вы можно использовать с SQLBulkCopy) и загружать данные, используя это.

...