EFUtilities навальный InsertAll с дочерними объектами - PullRequest
0 голосов
/ 08 октября 2018

Я пытаюсь выполнить массовую вставку списка элементов в мой SQL Server с помощью пакета EFUtilities.Есть ли способ, которым я могу вставить объект с несколькими свойствами навигации?Все, что я нашел, это

EFBatchOperation.For(DbContext, DbContext.Products).InsertAll(batch);

EFBatchOperation.For(DbContext, DbContext.Categories).InsertAll(batch.SelectMany(p => p.Categories));

EFBatchOperation.For(DbContext, DbContext.Orders).InsertAll(batch.SelectMany(p => p.Orders));

DbContext.SaveChanges();

, который сохраняет сущности в моей БД, но FK всегда равен 0. Например, идентификатор в моей таблице продуктов равен 1, а в моей таблице категорий - 0. Так что таким образомне обрабатывает сопоставление.

1 Ответ

0 голосов
/ 08 октября 2018

Я реализовал расширение для EFUtilities, которое позволяет выполнять массовую вставку, а также возвращать сгенерированные идентификаторы и устанавливать их в исходных объектах.Моя версия включает в себя другие улучшения, такие как повышение безопасности потоков и необязательную повторную попытку типа «разделяй и властвуй» для вставленных наборов.

Хотя есть два предостережения:

  • Только первичные ключи типа long (bigint) поддерживаются.Код должен был бы корректироваться для поддержки других типов столбцов.
  • Свойства навигации не вставляются автоматически, вы должны вызывать InsertAll несколько раз.

Код слишком длинныйчтобы быть включенным здесь в целом, это может быть найдено в моем блоге: Entity Framework 6 - Массовая вставка и возврат сгенерированных первичных ключей

Наиболее важной частью является это - вставка всех данных ввременная таблица и использование insert into и output, чтобы гарантировать, что данные вставляются в правильном порядке:

private static void BulkInsertAllAndReturnIds<T>(BulkInsertionCollectionMetadata<T> items, string schema,
    string tableName,
    IList<ColumnMapping> properties, SqlConnection connection, int? batchSize)
{
    if (items.Count == 0) return;
    long dummyValue = -1000 - items.Count;
    //set dummy IDs
    foreach (var item in items)
    {
        ((IHasPrimaryKey)item).PrimaryKey = dummyValue;
        dummyValue++;
    }
    try
    {
        if (connection.State != ConnectionState.Open)
        {
            connection.Open();
        }

        //create dummy table.
        using (var tempTable = new TempTable(connection, tableName, schema))
        {
            var createTempTableSql = $"Select * Into {tempTable.TableName} From {tableName} Where 1 = 2";
            using (var command = new SqlCommand(createTempTableSql, connection))
            {
                command.ExecuteNonQuery();
            }

            //bulk insert to temp table.
            BulkInsertAll(items, schema, tempTable.TableName, properties, connection, batchSize,
                SqlBulkCopyOptions.KeepNulls | SqlBulkCopyOptions.KeepIdentity);

            //note: IsPrimaryKey is not populated in InsertAll 
            // https://github.com/MikaelEliasson/EntityFramework.Utilities/blob/a5abc50b7367d64ca541b6e7e2e6018a500b6d8d/EntityFramework.Utilities/EntityFramework.Utilities/EFBatchOperation.cs#L129

            string primaryKeyNameOnObject = ((IHasPrimaryKey)items.First()).PrimaryKeyPropertyName;
            var primaryKey = properties.Single(c => c.NameOnObject == primaryKeyNameOnObject);
            var otherColumns = properties.Where(p => p != primaryKey);
            var allValueColumns = String.Join(", ", otherColumns.Select(c => "[" + c.NameInDatabase + "]"));

            //insert to real table and get new IDs.
            //this guarantees the record IDs are generated in the right order.
            var migrateAndReturnIds =
                $@"
                insert into {tableName} ({allValueColumns})
                OUTPUT inserted.{primaryKey.NameInDatabase}
                select {allValueColumns} from {tempTable.TableName} temp
                order by temp.{primaryKey.NameInDatabase}
                ";

            var newlyGeneratedIds = new List<long>(items.Count);
            using (var migrateDataCommand = new SqlCommand(migrateAndReturnIds, connection)
            {
                CommandTimeout = 0
            })
            using (var recordIdReader = migrateDataCommand.ExecuteReader())
            {
                while (recordIdReader.Read())
                {
                    var newId = recordIdReader.GetInt64(0);
                    newlyGeneratedIds.Add(newId);
                }
            }
            //set IDs on entities.
            if (newlyGeneratedIds.Count != items.Count)
            {
                throw new MissingPrimaryKeyException("There are fewer generated record IDs than the " +
                                                        "number of items inserted to the database.");
            }
            //the order of the IDs is not guaranteed, but the values will be generated in the same as the order values in `items`
            newlyGeneratedIds.Sort();
            for (int i = 0; i < newlyGeneratedIds.Count; i++)
            {
                ((IHasPrimaryKey)items[i]).PrimaryKey = newlyGeneratedIds[i];
            }
        }
    }
    finally
    {
        //make sure the ID is 0 if the row wasn't inserted.
        foreach (var item in items)
        {
            var entity = (IHasPrimaryKey)item;
            if (entity.PrimaryKey < 0) entity.PrimaryKey = 0;
        }
    }
}

Кстати - в отличие от обычного использования Entity Framework, выне нужно вызывать SaveChanges() после использования групповых вставок - InsertAll уже сохраняет изменения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...