示例#1
0
        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)
                {
                    var entityClrType = graphNodeGroup.Key;
                    var entityType    = context.Model.FindEntityType(entityClrType);

                    if (OwnedTypeUtil.IsOwnedInSameTableAsOwner(entityType))
                    {
                        continue;
                    }

                    // 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 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();
                    }
                }
            }
        }