Esempio n. 1
0
        public static Task ExecuteAsync <T>(DbContext context, IList <T> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress, CancellationToken cancellationToken) where T : class
        {
            if (operationType != OperationType.Truncate && entities.Count == 0)
            {
                return(Task.CompletedTask);
            }
            TableInfo tableInfo = TableInfo.CreateInstance(context, entities, operationType, bulkConfig);

            if (operationType == OperationType.Insert && !tableInfo.BulkConfig.SetOutputIdentity)
            {
                return(SqlBulkOperation.InsertAsync(context, entities, tableInfo, progress, cancellationToken));
            }
            else if (operationType == OperationType.Read)
            {
                return(SqlBulkOperation.ReadAsync(context, entities, tableInfo, progress, cancellationToken));
            }
            else if (operationType == OperationType.Truncate)
            {
                return(SqlBulkOperation.TruncateAsync(context, tableInfo));
            }
            else
            {
                return(SqlBulkOperation.MergeAsync(context, entities, tableInfo, operationType, progress, cancellationToken));
            }
        }
        public static Task ExecuteAsync(DbContext context, Type type, IList <object> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress, CancellationToken cancellationToken)
        {
            using (ActivitySources.StartExecuteActivity(operationType, entities.Count))
            {
                if (operationType != OperationType.Truncate && entities.Count == 0)
                {
                    return(Task.CompletedTask);
                }

                TableInfo tableInfo = TableInfo.CreateInstance(context, type, entities, operationType, bulkConfig);

                if (operationType == OperationType.Insert && !tableInfo.BulkConfig.SetOutputIdentity)
                {
                    return(SqlBulkOperation.InsertAsync(context, type, entities, tableInfo, progress, cancellationToken));
                }
                else if (operationType == OperationType.Read)
                {
                    return(SqlBulkOperation.ReadAsync(context, type, entities, tableInfo, progress, cancellationToken));
                }
                else if (operationType == OperationType.Truncate)
                {
                    return(SqlBulkOperation.TruncateAsync(context, tableInfo, cancellationToken));
                }
                else
                {
                    return(SqlBulkOperation.MergeAsync(context, type, entities, tableInfo, operationType, progress, cancellationToken));
                }
            }
        }
Esempio n. 3
0
        public static Task ExecuteAsync <T>(DbContext context, IList <T> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress) where T : class
        {
            TableInfo tableInfo = TableInfo.CreateInstance(context, entities, operationType, bulkConfig);

            if (operationType == OperationType.Insert && !tableInfo.BulkConfig.SetOutputIdentity)
            {
                return(SqlBulkOperation.InsertAsync <T>(context, entities, tableInfo, progress));
            }
            else
            {
                return(SqlBulkOperation.MergeAsync <T>(context, entities, tableInfo, operationType, progress));
            }
        }
Esempio n. 4
0
        public static async Task ExecuteAsync <T>(DbContext context, Type type, IList <T> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress, CancellationToken cancellationToken = default) where T : class
        {
            type ??= typeof(T);
            using (ActivitySources.StartExecuteActivity(operationType, entities.Count))
            {
                if (entities.Count == 0 && operationType != OperationType.InsertOrUpdateOrDelete && operationType != OperationType.Truncate && operationType != OperationType.SaveChanges)
                {
                    return;
                }

                if (operationType == OperationType.SaveChanges)
                {
                    await DbContextBulkTransactionSaveChanges.SaveChangesAsync(context, bulkConfig, progress, cancellationToken);
                }
                else if (bulkConfig?.IncludeGraph == true)
                {
                    await DbContextBulkTransactionGraphUtil.ExecuteWithGraphAsync(context, entities, operationType, bulkConfig, progress, cancellationToken);
                }
                else
                {
                    TableInfo tableInfo = TableInfo.CreateInstance(context, type, entities, operationType, bulkConfig);

                    if (operationType == OperationType.Insert && !tableInfo.BulkConfig.SetOutputIdentity)
                    {
                        await SqlBulkOperation.InsertAsync(context, type, entities, tableInfo, progress, cancellationToken);
                    }
                    else if (operationType == OperationType.Read)
                    {
                        await SqlBulkOperation.ReadAsync(context, type, entities, tableInfo, progress, cancellationToken);
                    }
                    else if (operationType == OperationType.Truncate)
                    {
                        await SqlBulkOperation.TruncateAsync(context, tableInfo, cancellationToken);
                    }
                    else
                    {
                        await SqlBulkOperation.MergeAsync(context, type, entities, tableInfo, operationType, progress, cancellationToken);
                    }
                }
            }
        }
        public static async Task ExecuteAsync(DbContext context, Type type, IList <object> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress, CancellationToken cancellationToken)
        {
            using (ActivitySources.StartExecuteActivity(operationType, entities.Count))
            {
                if (operationType != OperationType.Truncate && entities.Count == 0)
                {
                    return;
                }

                if (bulkConfig?.IncludeGraph == true)
                {
                    await DbContextBulkTransactionGraphUtil.ExecuteWithGraphAsync(context, entities, operationType, bulkConfig, progress, cancellationToken);
                }
                else
                {
                    TableInfo tableInfo = TableInfo.CreateInstance(context, type, entities, operationType, bulkConfig);

                    if (operationType == OperationType.Insert && !tableInfo.BulkConfig.SetOutputIdentity)
                    {
                        await SqlBulkOperation.InsertAsync(context, type, entities, tableInfo, progress, cancellationToken);
                    }
                    else if (operationType == OperationType.Read)
                    {
                        await SqlBulkOperation.ReadAsync(context, type, entities, tableInfo, progress, cancellationToken);
                    }
                    else if (operationType == OperationType.Truncate)
                    {
                        await SqlBulkOperation.TruncateAsync(context, tableInfo, cancellationToken);
                    }
                    else
                    {
                        await SqlBulkOperation.MergeAsync(context, type, entities, tableInfo, operationType, progress, cancellationToken);
                    }
                }
            }
        }
Esempio n. 6
0
        private static async Task ExecuteWithGraphAsync_Impl(DbContext context, IEnumerable <object> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress, CancellationToken?cancellationToken, bool isAsync = true)
        {
            if (operationType != OperationType.Insert &&
                operationType != OperationType.InsertOrUpdate &&
                operationType != OperationType.InsertOrUpdateDelete &&
                operationType != OperationType.Update)
            {
                throw new InvalidBulkConfigException($"{nameof(BulkConfig)}.{nameof(BulkConfig.IncludeGraph)} only supports Insert or Update operations.");
            }

            // Sqlite bulk merge adapter does not support multiple objects of the same type with a zero value primary key
            if (SqlAdaptersMapping.GetDatabaseType(context) == DbServer.Sqlite)
            {
                throw new NotSupportedException("Sqlite is not currently supported due to its BulkInsert implementation.");
            }

            // If this is set to false, won't be able to propogate new primary keys to the relationships
            bulkConfig.SetOutputIdentity = true;

            // If this is set to false, wont' be able to support some code first model types as EFCore uses shadow properties when a relationship's foreign keys arent explicitly defined
            bulkConfig.EnableShadowProperties = true;

            var rootGraphItems = GraphUtil.GetOrderedGraph(context, entities);

            if (rootGraphItems == null)
            {
                return;
            }

            // Inserting an entity graph must be done within a transaction otherwise the database could end up in a bad state
            var hasExistingTransaction = context.Database.CurrentTransaction != null;
            var transaction            = context.Database.CurrentTransaction ?? (isAsync ? await context.Database.BeginTransactionAsync() : context.Database.BeginTransaction());

            try
            {
                foreach (var actionGraphItem in rootGraphItems)
                {
                    var entitiesToAction = GetUniqueEntities(context, actionGraphItem).ToList();
                    var tableInfo        = TableInfo.CreateInstance(context, actionGraphItem.EntityClrType, entitiesToAction, operationType, bulkConfig);

                    if (isAsync)
                    {
                        await SqlBulkOperation.MergeAsync(context, actionGraphItem.EntityClrType, entitiesToAction, tableInfo, operationType, progress, cancellationToken.Value);
                    }
                    else
                    {
                        SqlBulkOperation.Merge(context, actionGraphItem.EntityClrType, entitiesToAction, tableInfo, operationType, progress);
                    }

                    // Loop through the dependants and update their foreign keys with the PK values of the just inserted / merged entities
                    foreach (var graphEntity in actionGraphItem.Entities)
                    {
                        var entity       = graphEntity.Entity;
                        var parentEntity = graphEntity.ParentEntity;

                        // If the parent entity is null its the root type of the object graph.
                        if (parentEntity is null)
                        {
                            foreach (var navigation in actionGraphItem.Relationships)
                            {
                                // If this relationship requires the parents value to exist
                                if (navigation.ParentNavigation.IsDependentToPrincipal() == false)
                                {
                                    foreach (var navGraphEntity in navigation.Entities)
                                    {
                                        if (navGraphEntity.ParentEntity != entity)
                                        {
                                            continue;
                                        }

                                        SetForeignKeyForRelationship(context, navigation.ParentNavigation,
                                                                     navGraphEntity.Entity, entity);
                                    }
                                }
                            }
                        }
                        else
                        {
                            var navigation = actionGraphItem.ParentNavigation;

                            if (navigation.IsDependentToPrincipal())
                            {
                                SetForeignKeyForRelationship(context, navigation,
                                                             dependent: parentEntity,
                                                             principal: entity);
                            }
                            else
                            {
                                SetForeignKeyForRelationship(context, navigation,
                                                             dependent: entity,
                                                             principal: parentEntity);
                            }
                        }
                    }
                }

                if (hasExistingTransaction == false)
                {
                    if (isAsync)
                    {
                        await transaction.CommitAsync();
                    }
                    else
                    {
                        transaction.Commit();
                    }
                }
            }
            finally
            {
                if (hasExistingTransaction == false)
                {
                    if (isAsync)
                    {
                        await transaction.DisposeAsync();
                    }
                    else
                    {
                        transaction.Dispose();
                    }
                }
            }
        }
        private static async Task ExecuteWithGraphAsync(DbContext context, IEnumerable <object> entities, OperationType operationType, BulkConfig bulkConfig, Action <decimal> progress, CancellationToken cancellationToken, bool isAsync)
        {
            if (operationType != OperationType.Insert &&
                operationType != OperationType.InsertOrUpdate &&
                operationType != OperationType.InsertOrUpdateDelete &&
                operationType != OperationType.Update)
            {
                throw new InvalidBulkConfigException($"{nameof(BulkConfig)}.{nameof(BulkConfig.IncludeGraph)} only supports Insert or Update operations.");
            }

            // Sqlite bulk merge adapter does not support multiple objects of the same type with a zero value primary key
            if (SqlAdaptersMapping.GetDatabaseType(context) == DbServer.Sqlite)
            {
                throw new NotSupportedException("Sqlite is not currently supported due to its BulkInsert implementation.");
            }

            bulkConfig.PreserveInsertOrder = true; // Required for SetOutputIdentity ('true' is default but here explicitly assigned again in case it was changed to 'false' in BulkConfing)
            bulkConfig.SetOutputIdentity   = true; // If this is set to false, won't be able to propogate new primary keys to the relationships

            // If this is set to false, wont' be able to support some code first model types as EFCore uses shadow properties when a relationship's foreign keys arent explicitly defined
            bulkConfig.EnableShadowProperties = true;

            var graphNodes = GraphUtil.GetTopologicallySortedGraph(context, entities);

            if (graphNodes == null)
            {
                return;
            }

            // Inserting an entity graph must be done within a transaction otherwise the database could end up in a bad state
            var hasExistingTransaction = context.Database.CurrentTransaction != null;
            var transaction            = context.Database.CurrentTransaction ?? (isAsync ? await context.Database.BeginTransactionAsync() : context.Database.BeginTransaction());

            try
            {
                // Group the graph nodes by entity type so we can merge them into the database in batches, in the correct order of dependency (topological order)
                var graphNodesGroupedByType = graphNodes.GroupBy(y => y.Entity.GetType());

                foreach (var graphNodeGroup in graphNodesGroupedByType)
                {
                    // It is possible the object graph contains duplicate entities (by primary key) but the entities are different object instances in memory.
                    // This an happen when deserializing a nested JSON tree for example. So filter out the duplicates.
                    var entitiesToAction = GetUniqueEntities(context, graphNodeGroup.Select(y => y.Entity)).ToList();
                    var entityClrType    = graphNodeGroup.Key;
                    var tableInfo        = TableInfo.CreateInstance(context, entityClrType, entitiesToAction, operationType, bulkConfig);

                    if (isAsync)
                    {
                        await SqlBulkOperation.MergeAsync(context, entityClrType, entitiesToAction, tableInfo, operationType, progress, cancellationToken);
                    }
                    else
                    {
                        SqlBulkOperation.Merge(context, entityClrType, entitiesToAction, tableInfo, operationType, progress);
                    }

                    // Set the foreign keys for dependents so they may be inserted on the next loop
                    var dependentsOfSameType = SetForeignKeysForDependentsAndYieldSameTypeDependents(context, entityClrType, graphNodeGroup).ToList();

                    // If there are any dependents of the same type (parent child relationship), then save those dependent entities again to commit the fk values
                    if (dependentsOfSameType.Any())
                    {
                        var dependentTableInfo = TableInfo.CreateInstance(context, entityClrType, dependentsOfSameType, operationType, bulkConfig);

                        if (isAsync)
                        {
                            await SqlBulkOperation.MergeAsync(context, entityClrType, dependentsOfSameType, dependentTableInfo, operationType, progress, cancellationToken);
                        }
                        else
                        {
                            SqlBulkOperation.Merge(context, entityClrType, dependentsOfSameType, dependentTableInfo, operationType, progress);
                        }
                    }
                }

                if (hasExistingTransaction == false)
                {
                    if (isAsync)
                    {
                        await transaction.CommitAsync();
                    }
                    else
                    {
                        transaction.Commit();
                    }
                }
            }
            finally
            {
                if (hasExistingTransaction == false)
                {
                    if (isAsync)
                    {
                        await transaction.DisposeAsync();
                    }
                    else
                    {
                        transaction.Dispose();
                    }
                }
            }
        }