├── .editorconfig ├── .gitignore ├── Directory.Build.Override.props.sample ├── Directory.Build.props ├── EFCore.BulkExtensions.sln ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── efcore-ext.snk ├── src ├── Abstraction │ ├── Bulk │ │ ├── BatchOperationExtensions.cs │ │ ├── BatchOperationMethods.cs │ │ └── BatchOptionsExtension.cs │ ├── EFCore.Bulk-3.1.csproj │ ├── EFCore.Bulk-5.0.csproj │ ├── EFCore.Bulk-6.0.csproj │ ├── Metadata │ │ └── BulkEntityTypeExtensions.cs │ ├── Query │ │ ├── BulkQueryExecutor.cs │ │ ├── NavigationExpandingExpressionVisitor.cs │ │ ├── ParameterExtractingExpressionVisitor.cs │ │ ├── QueryCompilationContextFactory.cs │ │ ├── QueryCompiler.cs │ │ └── QueryRelatedFactoryAndBypass.cs │ └── Utilities │ │ ├── Check.cs │ │ ├── ExpressionBuilder.cs │ │ ├── GenericUtility.cs │ │ ├── MergeJoin.cs │ │ ├── ReflectiveUtility.cs │ │ └── ServiceAnnotation.cs ├── InMemory │ ├── Bulk │ │ ├── BulkOperationBase.cs │ │ ├── DeleteOperation.cs │ │ ├── MergeOperation.cs │ │ ├── SelectIntoOperation.cs │ │ ├── UpdateOperation.cs │ │ └── UpsertOperation.cs │ ├── EFCore.Bulk.InMemory-3.1.csproj │ ├── EFCore.Bulk.InMemory-5.0.csproj │ ├── EFCore.Bulk.InMemory-6.0.csproj │ ├── InMemoryBatchExtensions.cs │ ├── Query │ │ ├── QueryCompiler.cs │ │ ├── QueryContextParameterVisitor.cs │ │ ├── QueryTranslationPreprocessor.cs │ │ └── QueryableMethodTranslatingExpressionVisitor.cs │ └── Storage │ │ ├── MergeRemapper.cs │ │ └── UpdateRemapper.cs ├── MySql │ ├── EFCore.Bulk.MySql-3.1.csproj │ ├── EFCore.Bulk.MySql-5.0.csproj │ ├── EFCore.Bulk.MySql-6.0.csproj │ ├── MySqlBatchExtensions.cs │ ├── QueryCompilationContextFactory.cs │ ├── QueryCompiler.cs │ └── QuerySqlGenerator.cs ├── PostgreSql │ ├── DateTimeOffsetTranslationPlugin.cs │ ├── EFCore.Bulk.PostgreSql-3.1.csproj │ ├── EFCore.Bulk.PostgreSql-5.0.csproj │ ├── EFCore.Bulk.PostgreSql-6.0.csproj │ ├── PostgreSqlBatchExtensions.cs │ ├── QueryCompilationContextFactory.cs │ ├── QueryCompiler.cs │ └── QuerySqlGenerator.cs ├── Relational │ ├── EFCore.Bulk.Relational-3.1.csproj │ ├── EFCore.Bulk.Relational-5.0.csproj │ ├── EFCore.Bulk.Relational-6.0.csproj │ ├── Metadata │ │ ├── EntityTypeExtensions.cs │ │ └── StoreObjectIdentifier.cs │ ├── Provider │ │ └── ParameterBasedSqlProcessor.cs │ ├── Query │ │ ├── ExcludedTableColumnRewritingVisitor.cs │ │ ├── ParameterValueBasedSelectExpressionOptimizer.cs │ │ ├── QuerySqlGenerator.cs │ │ ├── QueryTranslationPreprocessor.cs │ │ ├── QueryableMethodTranslatingExpressionVisitor.cs │ │ ├── RelationalBulkQueryExecutor.cs │ │ ├── RelationalParameterBasedSqlProcessor.cs │ │ ├── SearchConditionBooleanGuard.cs │ │ ├── ShapedQueryCompilingExpressionVisitor.cs │ │ ├── SqlExpressionFactoryExtensions.31.cs │ │ ├── SqlExpressionFactoryExtensions.50.cs │ │ ├── SqlExpressionFactoryExtensions.60.cs │ │ ├── SqlExpressionFactoryExtensions.cs │ │ ├── SqlExpressionVisitorV2.cs │ │ ├── SqlExpressions │ │ │ ├── AffectedRowsExpression.cs │ │ │ ├── DeleteExpression.cs │ │ │ ├── ExcludedTableColumnExpression.cs │ │ │ ├── MergeExpression.cs │ │ │ ├── SelectIntoExpression.cs │ │ │ ├── TransientExpandValuesExpression.cs │ │ │ ├── UpdateExpression.cs │ │ │ ├── UpsertExpression.cs │ │ │ ├── ValuesExpression.cs │ │ │ └── WrappedExpression.cs │ │ └── ValuesExpressionParameterExpandingVisitor.cs │ ├── Reduce │ │ ├── ColumnRewritingVisitor.cs │ │ ├── OptionsBuilderExtensions.cs │ │ ├── OptionsExtension.cs │ │ ├── ProjectionBindingPruningExpressionVisitor.cs │ │ ├── QueryTranslationPostprocessor.cs │ │ ├── SelfJoinsPredicateComparer.cs │ │ ├── SelfJoinsPruningExpressionVisitor.cs │ │ └── ShaperQueryExpressionReplacingVisitor.cs │ ├── RelationalExtensions.cs │ └── Storage │ │ ├── AnonymousExpressionFactory.cs │ │ ├── BulkRelationalCommandBuilderExtensions.cs │ │ └── ValuesRelationalParameter.cs ├── SqlServer │ ├── EFCore.Bulk.SqlServer-3.1.csproj │ ├── EFCore.Bulk.SqlServer-5.0.csproj │ ├── EFCore.Bulk.SqlServer-6.0.csproj │ ├── MathTranslationPlugin.cs │ ├── QueryCompilationContextFactory.cs │ ├── QueryCompiler.cs │ ├── QuerySqlGenerator.cs │ ├── QueryableMethodTranslatingExpressionVisitor.cs │ ├── SqlServerBatchExtensions.cs │ └── UpsertToMergeRewriter.cs ├── Sqlite │ ├── EFCore.Bulk.Sqlite-3.1.csproj │ ├── EFCore.Bulk.Sqlite-5.0.csproj │ ├── EFCore.Bulk.Sqlite-6.0.csproj │ ├── QueryCompilationContextFactory.cs │ ├── QueryCompiler.cs │ ├── QuerySqlGenerator.cs │ ├── QueryableMethodTranslatingExpressionVisitor.cs │ └── SqliteBatchExtensions.cs └── TableInfo.cs └── test ├── InMemory ├── Context.Factory.cs ├── Context.PrepareDatabase.cs ├── EFCore.Bulk.InMemory.Tests-3.1.csproj ├── EFCore.Bulk.InMemory.Tests-5.0.csproj ├── EFCore.Bulk.InMemory.Tests-6.0.csproj ├── Functional.Delete.cs ├── Functional.InsertInto.cs ├── Functional.MergeInto.cs ├── Functional.Update.cs ├── Functional.UpdateJoin.cs └── Functional.Upsert.cs ├── MySql ├── Context.Factory.cs ├── Context.PrepareDatabase.cs ├── EFCore.Bulk.MySql.Tests-3.1.csproj ├── EFCore.Bulk.MySql.Tests-5.0.csproj ├── EFCore.Bulk.MySql.Tests-6.0.csproj ├── Functional.Delete.cs ├── Functional.InsertInto.cs ├── Functional.SelfJoinsRemoval.cs ├── Functional.Update.cs ├── Functional.UpdateJoin.cs └── Functional.Upsert.cs ├── PostgreSql ├── Context.Factory.cs ├── Context.PrepareDatabase.cs ├── EFCore.Bulk.PostgreSql.Tests-3.1.csproj ├── EFCore.Bulk.PostgreSql.Tests-5.0.csproj ├── EFCore.Bulk.PostgreSql.Tests-6.0.csproj ├── Functional.Delete.cs ├── Functional.InsertInto.cs ├── Functional.SelfJoinsRemoval.cs ├── Functional.Update.cs ├── Functional.UpdateJoin.cs └── Functional.Upsert.cs ├── Relational ├── Context.CommandInterceptor.cs ├── Context.Factory.cs ├── EFCore.Bulk.Relational.Tests-3.1.csproj ├── EFCore.Bulk.Relational.Tests-5.0.csproj ├── EFCore.Bulk.Relational.Tests-6.0.csproj └── Functional.SelfJoinsRemoval.cs ├── Specifications ├── Compatibility.cs ├── Context.CommandNotifier.cs ├── Context.CommandTracer.cs ├── Context.Factory.cs ├── Context.LoggerFactory.cs ├── Context.PrepareDatabase.cs ├── Context.QueryTestBase.cs ├── EFCore.Bulk.Tests-3.1.csproj ├── EFCore.Bulk.Tests-5.0.csproj ├── EFCore.Bulk.Tests-6.0.csproj ├── Functional.Delete.cs ├── Functional.InsertInto.cs ├── Functional.MergeInto.cs ├── Functional.Update.cs ├── Functional.UpdateJoin.cs └── Functional.Upsert.cs ├── SqlServer ├── Context.Factory.cs ├── Context.PrepareDatabase.cs ├── EFCore.Bulk.SqlServer.Tests-3.1.csproj ├── EFCore.Bulk.SqlServer.Tests-5.0.csproj ├── EFCore.Bulk.SqlServer.Tests-6.0.csproj ├── Functional.Delete.cs ├── Functional.InsertInto.cs ├── Functional.MergeInto.cs ├── Functional.SelfJoinsRemoval.cs ├── Functional.Update.cs ├── Functional.UpdateJoin.cs └── Functional.Upsert.cs ├── Sqlite ├── Context.Factory.cs ├── Context.PrepareDatabase.cs ├── EFCore.Bulk.Sqlite.Tests-3.1.csproj ├── EFCore.Bulk.Sqlite.Tests-5.0.csproj ├── EFCore.Bulk.Sqlite.Tests-6.0.csproj ├── Functional.Delete.cs ├── Functional.InsertInto.cs ├── Functional.SelfJoinsRemoval.cs ├── Functional.Update.cs ├── Functional.UpdateJoin.cs └── Functional.Upsert.cs └── TestUtilities ├── DatabaseProvider.cs ├── DatabaseProviderSkipConditionAttribute.cs ├── EFCore.TestUtilities.csproj ├── TestPriorityAttribute.cs ├── TestPriorityOrderer.cs └── Xunit ├── ConditionalFactAttribute.cs ├── ConditionalFactDiscoverer.cs ├── ConditionalFactTestCase.cs ├── ConditionalTheoryAttribute.cs ├── ConditionalTheoryDiscoverer.cs ├── ConditionalTheoryTestCase.cs ├── ContextualTestCaseRunner.cs ├── ITestCondition.cs ├── Output.cs ├── PlatformSkipConditionAttribute.cs ├── TestPlatform.cs ├── UseCultureAttribute.cs └── XunitTestCaseExtensions.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_diagnostic.EF1001.severity = none 3 | csharp_style_prefer_range_operator = false:suggestion 4 | -------------------------------------------------------------------------------- /Directory.Build.Override.props.sample: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildThisFileDirectory)..\microsoft\efcore-5 5 | $(MSBuildThisFileDirectory)..\microsoft\efcore.pg-5 6 | $(MSBuildThisFileDirectory)..\microsoft\efcore.my-5 7 | $(MSBuildThisFileDirectory)..\microsoft\efcore-6 8 | $(MSBuildThisFileDirectory)..\microsoft\efcore.pg-6 9 | $(MSBuildThisFileDirectory)..\microsoft\efcore.my-6 10 | $(MSBuildThisFileDirectory)..\microsoft\efcore 11 | $(MSBuildThisFileDirectory)..\microsoft\efcore.pg 12 | $(MSBuildThisFileDirectory)..\microsoft\efcore.my 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 borisdj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - '*' 5 | tags: 6 | include: 7 | - '*' 8 | 9 | resources: 10 | repositories: 11 | - repository: "Azure Repos Mirror" 12 | type: git 13 | name: efcore/efcore-ext 14 | 15 | variables: 16 | - group: azure-repos-sync 17 | 18 | jobs: 19 | - job: Build 20 | displayName: "Build, Test and Publish" 21 | 22 | pool: 23 | vmImage: windows-2022 24 | 25 | steps: 26 | - task: PowerShell@2 27 | inputs: 28 | targetType: 'inline' 29 | script: | 30 | Write-Host "> Setting up PostgreSQL" 31 | $postgresService = Get-Service postgresql* | select -First 1 32 | Set-Service -InputObject $postgresService -StartupType Manual 33 | net start $postgresService.Name 34 | Write-Host "" 35 | Write-Host "> Setting up MySQL" 36 | mysqld --initialize-insecure 37 | mysqld install 38 | $mysqlService = Get-Service mysql* | select -First 1 39 | net start $mysqlService.Name 40 | mysql -u root --skip-password -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'Password12!'; FLUSH PRIVILEGES;" 41 | Write-Host "" 42 | Write-Host "> Listing .NET SDKs" 43 | dotnet --list-sdks 44 | displayName: 'Setup Environment' 45 | 46 | - task: DotNetCoreCLI@2 47 | inputs: 48 | command: 'build' 49 | displayName: 'Restore and Build Plugin' 50 | 51 | - task: DotNetCoreCLI@2 52 | inputs: 53 | command: 'test' 54 | arguments: '--no-restore --collect "Code coverage"' 55 | displayName: 'Run Unit Tests' 56 | 57 | - task: DotNetCoreCLI@2 58 | inputs: 59 | command: 'custom' 60 | custom: 'pack' 61 | arguments: '--no-restore -c Release -o $(Build.ArtifactStagingDirectory)' 62 | displayName: 'Package for NuGet' 63 | 64 | - task: PublishBuildArtifacts@1 65 | inputs: 66 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 67 | ArtifactName: 'drop' 68 | publishLocation: 'Container' 69 | displayName: 'Publish Artifacts' 70 | 71 | - job: Sync 72 | displayName: "Sync with Azure Repos" 73 | 74 | pool: 75 | vmImage: windows-latest 76 | 77 | steps: 78 | - task: gitmirror@0 79 | inputs: 80 | GitRepoUrl: 'https://tlylz:$(SYNC_PAT)@dev.azure.com/tlylz/efcore/_git/efcore-ext' 81 | displayName: 'Sync via Git Tools' 82 | -------------------------------------------------------------------------------- /efcore-ext.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yang-er/efcore-ext/958a175010dc8039e491a45beb82b91cfed76af0/efcore-ext.snk -------------------------------------------------------------------------------- /src/Abstraction/EFCore.Bulk-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31 6 | true 7 | Microsoft.EntityFrameworkCore.Bulk 8 | Microsoft.EntityFrameworkCore 9 | $(ExtensionPackagePrefix) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Abstraction/EFCore.Bulk-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50 6 | true 7 | Microsoft.EntityFrameworkCore.Bulk 8 | Microsoft.EntityFrameworkCore 9 | $(ExtensionPackagePrefix) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Abstraction/EFCore.Bulk-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | true 7 | Microsoft.EntityFrameworkCore.Bulk 8 | Microsoft.EntityFrameworkCore 9 | $(ExtensionPackagePrefix) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Abstraction/Metadata/BulkEntityTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace Microsoft.EntityFrameworkCore.Metadata 7 | { 8 | internal static class BulkEntityTypeExtensions 9 | { 10 | public static bool TryGuessKey(this IEntityType entityType, IReadOnlyList bindings, out IKey key) 11 | { 12 | var assignments = new HashSet(bindings.OfType().Select(a => a.Member)); 13 | 14 | foreach (var ikey in entityType.GetKeys()) 15 | { 16 | bool fulfill = true; 17 | foreach (var prop in ikey.Properties) 18 | { 19 | var member = (MemberInfo)prop.PropertyInfo ?? prop.FieldInfo; 20 | if (!assignments.Contains(member)) fulfill = false; 21 | if (prop.ValueGenerated != ValueGenerated.Never) fulfill = false; 22 | } 23 | 24 | if (fulfill) 25 | { 26 | key = ikey; 27 | return true; 28 | } 29 | } 30 | 31 | key = null; 32 | return false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Abstraction/Query/BulkQueryExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Query 4 | { 5 | /// 6 | /// The query executor for non-query commands. 7 | /// 8 | public interface IBulkQueryExecutor 9 | { 10 | /// 11 | /// Executes the non-query command. 12 | /// 13 | /// The count of affected rows. 14 | int Execute(); 15 | 16 | /// 17 | /// Asynchronously executes the non-query command. 18 | /// 19 | /// The task for executing this command, returning count of affected rows. 20 | Task ExecuteAsync(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Abstraction/Query/QueryRelatedFactoryAndBypass.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Query 2 | { 3 | /// 4 | public interface IBulkQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory 5 | { 6 | } 7 | 8 | /// 9 | public interface IBulkQueryTranslationPostprocessorFactory : IQueryTranslationPostprocessorFactory 10 | { 11 | } 12 | 13 | /// 14 | public interface IBulkQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory 15 | { 16 | } 17 | 18 | /// 19 | public interface IBulkShapedQueryCompilingExpressionVisitorFactory : IShapedQueryCompilingExpressionVisitorFactory 20 | { 21 | } 22 | 23 | /// 24 | public class BypassBulkQueryTranslationPostprocessorFactory : IBulkQueryTranslationPostprocessorFactory 25 | { 26 | private readonly IQueryTranslationPostprocessorFactory _factory; 27 | 28 | /// Bypass the factory. 29 | public BypassBulkQueryTranslationPostprocessorFactory(IQueryTranslationPostprocessorFactory factory) 30 | { 31 | _factory = factory; 32 | } 33 | 34 | /// 35 | public QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext) 36 | { 37 | return _factory.Create(queryCompilationContext); 38 | } 39 | } 40 | 41 | /// 42 | public class BypassBulkShapedQueryCompilingExpressionVisitorFactory : IBulkShapedQueryCompilingExpressionVisitorFactory 43 | { 44 | private readonly IShapedQueryCompilingExpressionVisitorFactory _factory; 45 | 46 | /// Bypass the factory. 47 | public BypassBulkShapedQueryCompilingExpressionVisitorFactory(IShapedQueryCompilingExpressionVisitorFactory factory) 48 | { 49 | _factory = factory; 50 | } 51 | 52 | /// 53 | public ShapedQueryCompilingExpressionVisitor Create(QueryCompilationContext queryCompilationContext) 54 | { 55 | return _factory.Create(queryCompilationContext); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Abstraction/Utilities/ExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Utilities 6 | { 7 | internal class ExpressionBuilder 8 | { 9 | public const BindingFlags InstanceLevel 10 | = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 11 | 12 | public ParameterExpression Parameter { get; } 13 | 14 | public Expression Current { get; private set; } 15 | 16 | private ExpressionBuilder(ParameterExpression parameterExpression) 17 | { 18 | Parameter = parameterExpression; 19 | Current = parameterExpression; 20 | } 21 | 22 | public ExpressionBuilder AccessField(string fieldName) 23 | { 24 | Current = Expression.Field( 25 | Current, 26 | Current.Type.GetField(fieldName, InstanceLevel)); 27 | 28 | return this; 29 | } 30 | 31 | public ExpressionBuilder AccessProperty(string propertyName) 32 | { 33 | Current = Expression.Property( 34 | Current, 35 | Current.Type.GetProperty(propertyName, InstanceLevel)); 36 | 37 | return this; 38 | } 39 | 40 | public ExpressionBuilder As() 41 | { 42 | Current = Expression.Convert(Current, typeof(T)); 43 | return this; 44 | } 45 | 46 | public ExpressionBuilder As(Type type) 47 | { 48 | Current = Expression.Convert(Current, type); 49 | return this; 50 | } 51 | 52 | public static ExpressionBuilder Begin() 53 | { 54 | return new ExpressionBuilder(Expression.Parameter(typeof(T), "args0")); 55 | } 56 | 57 | public TDelegate Compile() 58 | { 59 | return Expression.Lambda(Current, Parameter).Compile(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Abstraction/Utilities/ServiceAnnotation.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Bulk 2 | { 3 | /// 4 | /// An interface for annotate the original service and implementation. 5 | /// 6 | /// The service type. 7 | /// The implementation type. 8 | public interface IServiceAnnotation 9 | where TImplementation : TService 10 | where TService : class 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/InMemory/Bulk/BulkOperationBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Bulk 6 | { 7 | public abstract class BulkOperationBase : IBulkQueryExecutor 8 | { 9 | protected readonly object _queryEnumerable; 10 | protected readonly DbContext _dbContext; 11 | protected readonly QueryContext _queryContext; 12 | 13 | protected BulkOperationBase(QueryContext queryContext, object queryExecutor) 14 | { 15 | _queryEnumerable = queryExecutor; 16 | _queryContext = queryContext; 17 | _dbContext = queryContext.Context; 18 | } 19 | 20 | protected abstract void Process(TEntity entity); 21 | 22 | public virtual int Execute() 23 | { 24 | foreach (var entry in (IEnumerable)_queryEnumerable) 25 | { 26 | Process(entry); 27 | } 28 | 29 | return _dbContext.SaveChanges(); 30 | } 31 | 32 | public virtual async Task ExecuteAsync() 33 | { 34 | await foreach (var entry in (IAsyncEnumerable)_queryEnumerable) 35 | { 36 | Process(entry); 37 | } 38 | 39 | return await _dbContext.SaveChangesAsync(_queryContext.CancellationToken); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/InMemory/Bulk/DeleteOperation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Bulk 4 | { 5 | public class DeleteOperation : BulkOperationBase 6 | { 7 | public DeleteOperation(QueryContext queryContext, object queryExecutor) 8 | : base(queryContext, queryExecutor) 9 | { 10 | } 11 | 12 | protected override void Process(TEntity entity) 13 | { 14 | _dbContext.Remove(entity); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/InMemory/Bulk/MergeOperation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using Microsoft.EntityFrameworkCore.Storage.Internal; 3 | using System; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Bulk 6 | { 7 | public class MergeOperation : BulkOperationBase> 8 | { 9 | private readonly Func _insertShaper; 10 | private readonly Func _updateExtractor; 11 | private readonly bool _deleteDo; 12 | 13 | public MergeOperation( 14 | QueryContext queryContext, 15 | object queryExecutor, 16 | Func insertShaper, 17 | Func updateExtractor, 18 | bool deleteDo) 19 | : base(queryContext, queryExecutor) 20 | { 21 | _insertShaper = insertShaper; 22 | _updateExtractor = updateExtractor; 23 | _deleteDo = deleteDo; 24 | } 25 | 26 | protected override void Process(MergeRemapper entity) 27 | { 28 | if (entity.Outer == null && entity.Inner == null) 29 | { 30 | throw new InvalidProgramException("There's something wrong with System.Linq."); 31 | } 32 | else if (entity.Outer != null && entity.Inner != null && _updateExtractor != null) 33 | { 34 | _dbContext.Update(_updateExtractor(_queryContext, entity.Outer, entity.Inner)); 35 | } 36 | else if (entity.Outer != null && entity.Inner == null && _deleteDo) 37 | { 38 | _dbContext.Remove(entity.Outer); 39 | } 40 | else if (entity.Outer == null && entity.Inner != null && _insertShaper != null) 41 | { 42 | _dbContext.Add(_insertShaper(_queryContext, entity.Inner)); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/InMemory/Bulk/SelectIntoOperation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Bulk 4 | { 5 | public class SelectIntoOperation : BulkOperationBase 6 | { 7 | public SelectIntoOperation(QueryContext queryContext, object queryExecutor) 8 | : base(queryContext, queryExecutor) 9 | { 10 | } 11 | 12 | protected override void Process(TEntity entity) 13 | { 14 | _dbContext.Add(entity); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/InMemory/Bulk/UpdateOperation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using Microsoft.EntityFrameworkCore.Storage.Internal; 3 | using System; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Bulk 6 | { 7 | public class UpdateOperation : BulkOperationBase> 8 | { 9 | private readonly Func, TEntity> _updaterAndExtractor; 10 | 11 | public UpdateOperation( 12 | QueryContext queryContext, 13 | object queryExecutor, 14 | Func, TEntity> updaterAndExtractor) 15 | : base(queryContext, queryExecutor) 16 | { 17 | _updaterAndExtractor = updaterAndExtractor; 18 | } 19 | 20 | protected override void Process(UpdateRemapper source) 21 | { 22 | _dbContext.Update(_updaterAndExtractor(_queryContext, source)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/InMemory/Bulk/UpsertOperation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using Microsoft.EntityFrameworkCore.Storage.Internal; 3 | using System; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Bulk 6 | { 7 | public class UpsertOperation : BulkOperationBase> 8 | { 9 | private readonly Func _insertShaper; 10 | private readonly Func _updateExtractor; 11 | 12 | public UpsertOperation( 13 | QueryContext queryContext, 14 | object queryExecutor, 15 | Func insertShaper, 16 | Func updateExtractor) 17 | : base(queryContext, queryExecutor) 18 | { 19 | _insertShaper = insertShaper; 20 | _updateExtractor = updateExtractor; 21 | } 22 | 23 | protected override void Process(MergeRemapper entity) 24 | { 25 | var excluded = _insertShaper(_queryContext, entity.Inner); 26 | 27 | if (entity.Outer == null) 28 | { 29 | _dbContext.Add(excluded); 30 | } 31 | else if (_updateExtractor != null) 32 | { 33 | _dbContext.Update(_updateExtractor(_queryContext, entity.Outer, excluded)); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/InMemory/EFCore.Bulk.InMemory-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31;IN_MEMORY 6 | Microsoft.EntityFrameworkCore.Bulk.InMemory 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).InMemory 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/InMemory/EFCore.Bulk.InMemory-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50;IN_MEMORY 6 | Microsoft.EntityFrameworkCore.Bulk.InMemory 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).InMemory 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/InMemory/EFCore.Bulk.InMemory-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60;IN_MEMORY 6 | Microsoft.EntityFrameworkCore.Bulk.InMemory 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).InMemory 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/InMemory/InMemoryBatchExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Bulk; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.InMemory.Query.Internal; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Microsoft.EntityFrameworkCore.Query.Internal; 6 | using System.Collections.Generic; 7 | using System.Linq.Expressions; 8 | using System.Reflection; 9 | 10 | namespace Microsoft.EntityFrameworkCore 11 | { 12 | public static class InMemoryBatchExtensions 13 | { 14 | public static InMemoryDbContextOptionsBuilder UseBulk(this InMemoryDbContextOptionsBuilder builder) 15 | { 16 | var builder1 = typeof(InMemoryDbContextOptionsBuilder) 17 | .GetProperty("OptionsBuilder", BindingFlags.Instance | BindingFlags.NonPublic) 18 | .GetValue(builder) as IDbContextOptionsBuilderInfrastructure; 19 | builder1.AddOrUpdateExtension(new InMemoryBatchOptionsExtension()); 20 | return builder; 21 | } 22 | 23 | #if EFCORE60 24 | internal static void ReplaceProjectionMapping( 25 | this InMemoryQueryExpression expression, 26 | IReadOnlyDictionary projectionMapping) 27 | { 28 | expression.ReplaceProjection(projectionMapping); 29 | } 30 | #endif 31 | } 32 | 33 | internal class InMemoryBatchOptionsExtension : BatchOptionsExtension 34 | { 35 | public override string Name => "InMemoryBatchExtension"; 36 | 37 | protected override void ApplyServices(ExtensionServicesBuilder services) 38 | { 39 | services.TryAdd(); 40 | services.TryAdd(); 41 | services.TryAdd(); 42 | services.TryAdd(); 43 | services.TryAdd(); 44 | 45 | services.TryAdd(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/InMemory/Query/QueryContextParameterVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal 9 | { 10 | public class QueryContextParameterVisitor : ExpressionVisitor 11 | { 12 | private const string CompiledQueryParameterPrefix = "__"; 13 | 14 | private static readonly MethodInfo translatingGetParameterValue 15 | = typeof(InMemoryExpressionTranslatingExpressionVisitor) 16 | .GetTypeInfo().GetDeclaredMethod("GetParameterValue"); 17 | 18 | private readonly ParameterExpression[] _excluded; 19 | 20 | protected override Expression VisitParameter(ParameterExpression node) 21 | { 22 | if (_excluded.Contains(node)) return node; 23 | 24 | if (node.Name?.StartsWith(CompiledQueryParameterPrefix, StringComparison.Ordinal) == true) 25 | { 26 | return Expression.Call( 27 | translatingGetParameterValue.MakeGenericMethod(node.Type), 28 | QueryCompilationContext.QueryContextParameter, 29 | Expression.Constant(node.Name)); 30 | } 31 | 32 | return node; 33 | } 34 | 35 | private QueryContextParameterVisitor(ParameterExpression[] excluded) 36 | { 37 | _excluded = excluded; 38 | } 39 | 40 | public static Expression Process(Expression origin, IEnumerable excluded) 41 | { 42 | return new QueryContextParameterVisitor(excluded?.ToArray() ?? Array.Empty()).Visit(origin); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/InMemory/Storage/MergeRemapper.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Storage.Internal 2 | { 3 | public class MergeRemapper 4 | { 5 | public MergeRemapper(TOuter outer, TInner inner) 6 | { 7 | Outer = outer; 8 | Inner = inner; 9 | } 10 | 11 | public TOuter Outer { get; } 12 | 13 | public TInner Inner { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/InMemory/Storage/UpdateRemapper.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Storage.Internal 2 | { 3 | public class UpdateRemapper 4 | { 5 | public UpdateRemapper(TEntity origin, TEntity update) 6 | { 7 | Origin = origin; 8 | Update = update; 9 | } 10 | 11 | public TEntity Origin { get; } 12 | 13 | public TEntity Update { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/MySql/EFCore.Bulk.MySql-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31;RELATIONAL;MYSQL 6 | Microsoft.EntityFrameworkCore.Bulk.MySql 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).MySql 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/MySql/EFCore.Bulk.MySql-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50;RELATIONAL;MYSQL 6 | Microsoft.EntityFrameworkCore.Bulk.MySql 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).MySql 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/MySql/EFCore.Bulk.MySql-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60;RELATIONAL;MYSQL 6 | Microsoft.EntityFrameworkCore.Bulk.MySql 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).MySql 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/MySql/MySqlBatchExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Bulk; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.Query; 4 | using Microsoft.EntityFrameworkCore.Query.Internal; 5 | using Microsoft.EntityFrameworkCore.Storage.Internal; 6 | using Pomelo.EntityFrameworkCore.MySql.Query; 7 | 8 | namespace Microsoft.EntityFrameworkCore 9 | { 10 | public static class MySqlBatchExtensions 11 | { 12 | public static MySqlDbContextOptionsBuilder UseBulk(this MySqlDbContextOptionsBuilder builder) 13 | { 14 | var builder1 = ((IRelationalDbContextOptionsBuilderInfrastructure)builder).OptionsBuilder; 15 | var ext = new MySqlBatchOptionsExtension(); 16 | ((IDbContextOptionsBuilderInfrastructure)builder1).AddOrUpdateExtension(ext); 17 | return builder; 18 | } 19 | } 20 | 21 | public class MySqlBatchOptionsExtension : RelationalBatchOptionsExtension 22 | { 23 | public override string Name => "MySqlBatchExtension"; 24 | 25 | protected override void ApplyServices(ExtensionServicesBuilder services) 26 | { 27 | services.TryAdd(); 28 | 29 | services.TryAdd(); 30 | services.TryAdd(); 31 | services.TryAdd(); 32 | services.TryAdd(); 33 | 34 | services.TryAdd(); 35 | services.TryAdd(); 36 | #if EFCORE50 || EFCORE60 37 | services.TryAdd(); 38 | services.TryAdd(); 39 | #elif EFCORE31 40 | services.TryAdd(); 41 | SearchConditionBooleanGuard.AddTypeField(typeof(NullSemanticsRewritingExpressionVisitor), "_canOptimize"); 42 | #endif 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/MySql/QueryCompilationContextFactory.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE50 || EFCORE60 2 | 3 | using Microsoft.EntityFrameworkCore.Bulk; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Pomelo.EntityFrameworkCore.MySql.Query.Internal; 6 | using System; 7 | using System.Linq.Expressions; 8 | 9 | namespace Pomelo.EntityFrameworkCore.MySql.Query 10 | { 11 | public class MySqlBulkQueryCompilationContextFactory : 12 | MySqlQueryCompilationContextFactory, 13 | IBulkQueryCompilationContextFactory, 14 | IServiceAnnotation 15 | { 16 | public MySqlBulkQueryCompilationContextFactory( 17 | BulkQueryCompilationContextDependencies dependencies, 18 | RelationalQueryCompilationContextDependencies relationalDependencies) 19 | : base(dependencies.Dependencies, relationalDependencies) 20 | { 21 | } 22 | 23 | public Func CreateQueryExecutor(bool async, Expression query) 24 | { 25 | return Create(async).CreateQueryExecutor(query); 26 | } 27 | } 28 | } 29 | 30 | #endif -------------------------------------------------------------------------------- /src/MySql/QueryCompiler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Bulk; 3 | using Microsoft.EntityFrameworkCore.Diagnostics; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Query; 7 | using Microsoft.EntityFrameworkCore.Query.Internal; 8 | using Microsoft.EntityFrameworkCore.Storage; 9 | using System; 10 | using System.Linq.Expressions; 11 | 12 | namespace Pomelo.EntityFrameworkCore.MySql.Query 13 | { 14 | public class MySqlBulkQueryCompiler : BulkQueryCompiler 15 | { 16 | public MySqlBulkQueryCompiler( 17 | IQueryContextFactory queryContextFactory, 18 | ICompiledQueryCache compiledQueryCache, 19 | ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator, 20 | IDatabase database, 21 | IDiagnosticsLogger logger, 22 | ICurrentDbContext currentContext, 23 | IEvaluatableExpressionFilter evaluatableExpressionFilter, 24 | IBulkQueryCompilationContextFactory qccFactory, 25 | IModel model) 26 | : base(queryContextFactory, 27 | compiledQueryCache, 28 | compiledQueryCacheKeyGenerator, 29 | database, 30 | logger, 31 | currentContext, 32 | evaluatableExpressionFilter, 33 | qccFactory, 34 | model) 35 | { 36 | } 37 | 38 | protected override Func CompileBulkCore(IDatabase database, Expression query, IModel model, bool async) 39 | { 40 | if (query is MethodCallExpression methodCallExpression 41 | && methodCallExpression.Method.GetGenericMethodDefinition() == BatchOperationMethods.MergeCollapsed) 42 | { 43 | throw TranslationFailed(query, "MERGE INTO sentences are not supported in MySQL."); 44 | } 45 | 46 | return base.CompileBulkCore(database, query, model, async); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/PostgreSql/EFCore.Bulk.PostgreSql-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31;RELATIONAL;POSTGRE_SQL 6 | Microsoft.EntityFrameworkCore.Bulk.PostgreSql 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).PostgreSql 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/PostgreSql/EFCore.Bulk.PostgreSql-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50;RELATIONAL;POSTGRE_SQL 6 | Microsoft.EntityFrameworkCore.Bulk.PostgreSql 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).PostgreSql 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/PostgreSql/EFCore.Bulk.PostgreSql-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60;RELATIONAL;POSTGRE_SQL 6 | Microsoft.EntityFrameworkCore.Bulk.PostgreSql 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).PostgreSql 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/PostgreSql/QueryCompilationContextFactory.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE50 || EFCORE60 2 | 3 | using Microsoft.EntityFrameworkCore.Bulk; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; 6 | using System; 7 | using System.Linq.Expressions; 8 | 9 | namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query 10 | { 11 | public class NpgsqlBulkQueryCompilationContextFactory : 12 | NpgsqlQueryCompilationContextFactory, 13 | IBulkQueryCompilationContextFactory, 14 | IServiceAnnotation 15 | { 16 | public NpgsqlBulkQueryCompilationContextFactory( 17 | BulkQueryCompilationContextDependencies dependencies, 18 | RelationalQueryCompilationContextDependencies relationalDependencies) 19 | : base(dependencies.Dependencies, relationalDependencies) 20 | { 21 | } 22 | 23 | public Func CreateQueryExecutor(bool async, Expression query) 24 | { 25 | return Create(async).CreateQueryExecutor(query); 26 | } 27 | } 28 | } 29 | 30 | #endif -------------------------------------------------------------------------------- /src/PostgreSql/QueryCompiler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Bulk; 3 | using Microsoft.EntityFrameworkCore.Diagnostics; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Query; 7 | using Microsoft.EntityFrameworkCore.Query.Internal; 8 | using Microsoft.EntityFrameworkCore.Storage; 9 | using System; 10 | using System.Linq.Expressions; 11 | 12 | namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query 13 | { 14 | public class NpgsqlBulkQueryCompiler : BulkQueryCompiler 15 | { 16 | public NpgsqlBulkQueryCompiler( 17 | IQueryContextFactory queryContextFactory, 18 | ICompiledQueryCache compiledQueryCache, 19 | ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator, 20 | IDatabase database, 21 | IDiagnosticsLogger logger, 22 | ICurrentDbContext currentContext, 23 | IEvaluatableExpressionFilter evaluatableExpressionFilter, 24 | IBulkQueryCompilationContextFactory qccFactory, 25 | IModel model) 26 | : base(queryContextFactory, 27 | compiledQueryCache, 28 | compiledQueryCacheKeyGenerator, 29 | database, 30 | logger, 31 | currentContext, 32 | evaluatableExpressionFilter, 33 | qccFactory, 34 | model) 35 | { 36 | } 37 | 38 | protected override Func CompileBulkCore(IDatabase database, Expression query, IModel model, bool async) 39 | { 40 | if (query is MethodCallExpression methodCallExpression 41 | && methodCallExpression.Method.GetGenericMethodDefinition() == BatchOperationMethods.MergeCollapsed) 42 | { 43 | throw TranslationFailed(query, "MERGE INTO sentences are not supported in PostgreSQL."); 44 | } 45 | 46 | return base.CompileBulkCore(database, query, model, async); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Relational/EFCore.Bulk.Relational-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31;RELATIONAL 6 | Microsoft.EntityFrameworkCore.Bulk.Relational 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).Relational 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Relational/EFCore.Bulk.Relational-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50;RELATIONAL 6 | Microsoft.EntityFrameworkCore.Bulk.Relational 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).Relational 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Relational/EFCore.Bulk.Relational-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60;RELATIONAL 6 | Microsoft.EntityFrameworkCore.Bulk.Relational 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).Relational 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Relational/Metadata/EntityTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Metadata 6 | { 7 | public static class BulkEntityTypeExtensions 8 | { 9 | public static Dictionary GetColumns(this IEntityType entityType) 10 | { 11 | var store = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table).Value; 12 | var result = new Dictionary(); 13 | 14 | void DiscoverOwnedEntity(IEntityType currentNavigated, string baseName) 15 | { 16 | foreach (var property in currentNavigated.GetProperties()) 17 | { 18 | var name = baseName + "." + property.Name; 19 | result.Add(name.Trim('.'), property.GetColumnName(store)); 20 | } 21 | 22 | foreach (var navigation in currentNavigated.GetNavigations()) 23 | { 24 | if (!navigation.ForeignKey.IsOwnership) continue; 25 | var name = baseName + "." + navigation.Name; 26 | DiscoverOwnedEntity(navigation.ForeignKey.DeclaringEntityType, name); 27 | } 28 | } 29 | 30 | DiscoverOwnedEntity(entityType, ""); 31 | return result; 32 | } 33 | 34 | public static Dictionary GetValueConverters(this IEntityType entityType) 35 | { 36 | var store = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table).Value; 37 | var result = new Dictionary(); 38 | 39 | void DiscoverOwnedEntity(IEntityType currentNavigated, string baseName) 40 | { 41 | foreach (var property in currentNavigated.GetProperties()) 42 | { 43 | var name = baseName + "." + property.Name; 44 | var converter = property.GetValueConverter(); 45 | if (converter != null) 46 | { 47 | result.Add(name.Trim('.'), converter); 48 | } 49 | } 50 | 51 | foreach (var navigation in currentNavigated.GetNavigations()) 52 | { 53 | if (!navigation.ForeignKey.IsOwnership) continue; 54 | var name = baseName + "." + navigation.Name; 55 | DiscoverOwnedEntity(navigation.ForeignKey.DeclaringEntityType, name); 56 | } 57 | } 58 | 59 | DiscoverOwnedEntity(entityType, ""); 60 | return result; 61 | } 62 | 63 | public static IEntityType FindEntityTypeByTable(this IModel model, string tableName) 64 | { 65 | return model.GetEntityTypes() 66 | .Where(e => e.GetTableName() == tableName && !e.IsOwned()) 67 | .FirstOrDefault(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Relational/Metadata/StoreObjectIdentifier.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE31 2 | #pragma warning disable IDE0060 3 | 4 | namespace Microsoft.EntityFrameworkCore.Metadata 5 | { 6 | internal enum StoreObjectType 7 | { 8 | Table, 9 | } 10 | 11 | internal readonly struct StoreObjectIdentifier 12 | { 13 | public static StoreObjectIdentifier? Create(IEntityType _, StoreObjectType __) 14 | { 15 | return new StoreObjectIdentifier(); 16 | } 17 | } 18 | 19 | internal static class StoreObjectIdentifierCompatibility 20 | { 21 | public static string GetColumnName(this IProperty property, in StoreObjectIdentifier _) 22 | { 23 | return property.GetColumnName(); 24 | } 25 | } 26 | } 27 | 28 | #pragma warning restore IDE0060 29 | #endif -------------------------------------------------------------------------------- /src/Relational/Query/ExcludedTableColumnRewritingVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 2 | using System.Linq.Expressions; 3 | 4 | namespace Microsoft.EntityFrameworkCore.Query 5 | { 6 | public class ExcludedTableColumnRewritingVisitor : SqlExpressionVisitorV2 7 | { 8 | private readonly TableExpression _excludedTable; 9 | 10 | public ExcludedTableColumnRewritingVisitor(TableExpression excludedTable) 11 | { 12 | _excludedTable = excludedTable; 13 | } 14 | 15 | protected override Expression VisitColumn(ColumnExpression columnExpression) 16 | { 17 | if (columnExpression.Table == _excludedTable) 18 | { 19 | return new ExcludedTableColumnExpression( 20 | columnExpression.Name, 21 | columnExpression.Type, 22 | columnExpression.TypeMapping, 23 | columnExpression.IsNullable); 24 | } 25 | else 26 | { 27 | return base.VisitColumn(columnExpression); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Relational/Query/ParameterValueBasedSelectExpressionOptimizer.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE31 2 | using Microsoft.EntityFrameworkCore.Query.Internal; 3 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 4 | using Microsoft.EntityFrameworkCore.Storage; 5 | using System.Collections.Generic; 6 | 7 | namespace Microsoft.EntityFrameworkCore.Query 8 | { 9 | public class BulkParameterValueBasedSelectExpressionOptimizer : ParameterValueBasedSelectExpressionOptimizer 10 | { 11 | private readonly ISqlExpressionFactory _sqlExpressionFactory; 12 | 13 | public BulkParameterValueBasedSelectExpressionOptimizer( 14 | ISqlExpressionFactory sqlExpressionFactory, 15 | IParameterNameGeneratorFactory parameterNameGeneratorFactory, 16 | bool useRelationalNulls) 17 | : base(sqlExpressionFactory, parameterNameGeneratorFactory, useRelationalNulls) 18 | { 19 | _sqlExpressionFactory = sqlExpressionFactory; 20 | } 21 | 22 | public override (SelectExpression selectExpression, bool canCache) Optimize( 23 | SelectExpression selectExpression, 24 | IReadOnlyDictionary parametersValues) 25 | { 26 | var (optimizedSelectExpression, canCache) = base.Optimize(selectExpression, parametersValues); 27 | var valuesVisitor = new ValuesExpressionParameterExpandingVisitor(_sqlExpressionFactory, parametersValues); 28 | 29 | optimizedSelectExpression = (SelectExpression)valuesVisitor.Visit(optimizedSelectExpression); 30 | canCache = canCache && valuesVisitor.CanCache; 31 | 32 | return (optimizedSelectExpression, canCache); 33 | } 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/Relational/Query/QuerySqlGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Query 2 | { 3 | public interface IBulkQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Relational/Query/RelationalBulkQueryExecutor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.Internal; 3 | using Microsoft.EntityFrameworkCore.Query.Internal; 4 | using Microsoft.EntityFrameworkCore.Storage; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.EntityFrameworkCore.Query 8 | { 9 | public class RelationalBulkQueryExecutor : IBulkQueryExecutor 10 | { 11 | private readonly RelationalCommandCache _relationalCommandCache; 12 | private readonly RelationalQueryContext _queryContext; 13 | 14 | public RelationalBulkQueryExecutor( 15 | RelationalQueryContext relationalQueryContext, 16 | RelationalCommandCache relationalCommandCache) 17 | { 18 | _relationalCommandCache = relationalCommandCache; 19 | _queryContext = relationalQueryContext; 20 | } 21 | 22 | public int Execute() 23 | { 24 | using (_queryContext.ConcurrencyDetector.EnterCriticalSection()) 25 | { 26 | #if EFCORE50 || EFCORE60 27 | EntityFrameworkEventSource.Log.QueryExecuting(); 28 | #endif 29 | 30 | return _relationalCommandCache 31 | .RentAndPopulateRelationalCommand(_queryContext) 32 | .ExecuteNonQuery( 33 | new RelationalCommandParameterObject( 34 | _queryContext.Connection, 35 | _queryContext.ParameterValues, 36 | null, 37 | _queryContext.Context, 38 | _queryContext.CommandLogger)); 39 | } 40 | } 41 | 42 | public async Task ExecuteAsync() 43 | { 44 | using (_queryContext.ConcurrencyDetector.EnterCriticalSection()) 45 | { 46 | #if EFCORE50 || EFCORE60 47 | EntityFrameworkEventSource.Log.QueryExecuting(); 48 | #endif 49 | 50 | return await _relationalCommandCache 51 | .RentAndPopulateRelationalCommand(_queryContext) 52 | .ExecuteNonQueryAsync( 53 | new RelationalCommandParameterObject( 54 | _queryContext.Connection, 55 | _queryContext.ParameterValues, 56 | null, 57 | _queryContext.Context, 58 | _queryContext.CommandLogger), 59 | _queryContext.CancellationToken) 60 | .ConfigureAwait(false); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Relational/Query/RelationalParameterBasedSqlProcessor.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE50 || EFCORE60 2 | 3 | namespace Microsoft.EntityFrameworkCore.Query 4 | { 5 | public interface IRelationalBulkParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory 6 | { 7 | } 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /src/Relational/Query/SearchConditionBooleanGuard.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace Microsoft.EntityFrameworkCore.Query 7 | { 8 | internal class SearchConditionBooleanGuard : IDisposable 9 | { 10 | // bool value: True for predicate sections, false for value sections. 11 | public delegate void Setter(ExpressionVisitor expressionVisitor, bool isSearchCondition); 12 | public delegate bool Getter(ExpressionVisitor expressionVisitor); 13 | public static readonly Dictionary VisitorTypes = new Dictionary(); 14 | 15 | private bool _disposed; 16 | private bool _originalValue; 17 | private ExpressionVisitor _visitor; 18 | private Setter _setter; 19 | 20 | public static void AddTypeField(Type type, string memberName) 21 | { 22 | lock (VisitorTypes) 23 | { 24 | if (VisitorTypes.ContainsKey(type)) return; 25 | var param = Expression.Parameter(typeof(ExpressionVisitor)); 26 | var value = Expression.Parameter(typeof(bool)); 27 | var convert = Expression.Convert(param, type); 28 | var member = type.GetField(memberName, ReflectiveUtility.InstanceLevel); 29 | var field = Expression.Field(convert, member); 30 | var getter = Expression.Lambda(field, param); 31 | var setter = Expression.Lambda(Expression.Assign(field, value), param, value); 32 | VisitorTypes.Add(type, (getter.Compile(), setter.Compile())); 33 | } 34 | } 35 | 36 | private SearchConditionBooleanGuard(ExpressionVisitor visitor, Setter setter, bool origin) 37 | { 38 | _visitor = visitor; 39 | _setter = setter; 40 | _originalValue = origin; 41 | } 42 | 43 | public static IDisposable With(ExpressionVisitor expressionVisitor, bool isSearchCondition, bool? outside = default) 44 | { 45 | var type = expressionVisitor.GetType(); 46 | if (!VisitorTypes.ContainsKey(type)) return null; 47 | 48 | var (getter, setter) = VisitorTypes[type]; 49 | bool current = getter.Invoke(expressionVisitor); 50 | if (outside.HasValue && outside.Value != current) 51 | { 52 | throw new InvalidOperationException("State corrupt."); 53 | } 54 | 55 | setter.Invoke(expressionVisitor, isSearchCondition); 56 | return new SearchConditionBooleanGuard(expressionVisitor, setter, current); 57 | } 58 | 59 | protected void Dispose(bool disposing) 60 | { 61 | if (!_disposed) 62 | { 63 | if (disposing) 64 | { 65 | _setter?.Invoke(_visitor, _originalValue); 66 | } 67 | 68 | _visitor = null; 69 | _setter = null; 70 | _disposed = true; 71 | } 72 | } 73 | 74 | public void Dispose() 75 | { 76 | Dispose(disposing: true); 77 | GC.SuppressFinalize(this); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Relational/Query/SqlExpressions/AffectedRowsExpression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Storage; 2 | using System.Linq.Expressions; 3 | 4 | namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions 5 | { 6 | public class AffectedRowsExpression : SqlExpression 7 | { 8 | public AffectedRowsExpression() 9 | : base(typeof(int), RelationalTypeMapping.NullMapping) 10 | { 11 | } 12 | 13 | /// 14 | #if EFCORE50 || EFCORE60 15 | protected override void Print(ExpressionPrinter expressionPrinter) 16 | #elif EFCORE31 17 | public override void Print(ExpressionPrinter expressionPrinter) 18 | #endif 19 | { 20 | expressionPrinter.Append("affected rows"); 21 | } 22 | 23 | protected override Expression VisitChildren(ExpressionVisitor visitor) 24 | { 25 | return this; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Relational/Query/SqlExpressions/DeleteExpression.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using Microsoft.EntityFrameworkCore.Storage; 3 | using Microsoft.EntityFrameworkCore.Utilities; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | 7 | namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions 8 | { 9 | /// 10 | /// An expression that represents a DELETE in a SQL tree. 11 | /// 12 | public sealed class DeleteExpression : WrappedExpression 13 | { 14 | public DeleteExpression( 15 | TableExpression table, 16 | SqlExpression? predicate, 17 | IReadOnlyList joinedTables) 18 | { 19 | Check.NotNull(table, nameof(table)); 20 | Check.HasNoNulls(joinedTables, nameof(joinedTables)); 21 | 22 | Table = table; 23 | JoinedTables = joinedTables; 24 | Predicate = predicate; 25 | } 26 | 27 | /// 28 | /// The primary table to operate on. 29 | /// 30 | public TableExpression Table { get; } 31 | 32 | /// 33 | /// The WHERE predicate for the DELETE. 34 | /// 35 | public SqlExpression? Predicate { get; } 36 | 37 | /// 38 | /// The list of tables sources used to generate the result set. 39 | /// 40 | public IReadOnlyList JoinedTables { get; } 41 | 42 | /// 43 | protected override Expression VisitChildren(ExpressionVisitor visitor) 44 | { 45 | const string CallerName = "VisitDelete"; 46 | 47 | using (SearchConditionBooleanGuard.With(visitor, false)) 48 | { 49 | var table = visitor.VisitAndConvert(Table, CallerName); 50 | bool changed = table != Table; 51 | 52 | SqlExpression? predicate; 53 | using (SearchConditionBooleanGuard.With(visitor, true, false)) 54 | { 55 | predicate = visitor.VisitAndConvert(Predicate, CallerName); 56 | changed = changed || predicate != Predicate; 57 | } 58 | 59 | var joinedTables = visitor.VisitCollection(JoinedTables, CallerName); 60 | changed = changed || joinedTables != JoinedTables; 61 | 62 | return changed 63 | ? new DeleteExpression(table, predicate, joinedTables) 64 | : this; 65 | } 66 | } 67 | 68 | /// 69 | protected override void Prints(ExpressionPrinter expressionPrinter) 70 | { 71 | Check.NotNull(expressionPrinter, nameof(expressionPrinter)); 72 | 73 | expressionPrinter.Append("DELETE "); 74 | expressionPrinter.Visit(Table); 75 | 76 | if (JoinedTables.Count > 0) 77 | { 78 | expressionPrinter.AppendLine().Append("FROM "); 79 | 80 | for (int i = 0; i < JoinedTables.Count; i++) 81 | { 82 | expressionPrinter.Visit(JoinedTables[i]); 83 | if (i > 0) expressionPrinter.AppendLine(); 84 | } 85 | } 86 | 87 | if (Predicate != null) 88 | { 89 | expressionPrinter.AppendLine().Append("WHERE "); 90 | expressionPrinter.Visit(Predicate); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Relational/Query/SqlExpressions/ExcludedTableColumnExpression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Storage; 2 | using Microsoft.EntityFrameworkCore.Utilities; 3 | using System; 4 | using System.Linq.Expressions; 5 | 6 | namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions 7 | { 8 | /// 9 | /// An expression that represents an excluded table column in a SQL tree. 10 | /// 11 | public class ExcludedTableColumnExpression : SqlExpression 12 | { 13 | public ExcludedTableColumnExpression(string name, Type type, RelationalTypeMapping typeMapping, bool nullable) 14 | : base(type, typeMapping) 15 | { 16 | Check.NotEmpty(name, nameof(name)); 17 | 18 | Name = name; 19 | IsNullable = nullable; 20 | } 21 | 22 | /// 23 | /// The name of corresponding column. 24 | /// 25 | public string Name { get; } 26 | 27 | /// 28 | /// Whether this property is nullable. 29 | /// 30 | public bool IsNullable { get; } 31 | 32 | /// 33 | /// Creates another expression representing this property nullable. 34 | /// 35 | public ExcludedTableColumnExpression MakeNullable() 36 | => new ExcludedTableColumnExpression(Name, Type.MakeNullable(), TypeMapping, true); 37 | 38 | /// 39 | protected override Expression VisitChildren(ExpressionVisitor visitor) => this; 40 | 41 | /// 42 | public override string ToString() => "excluded." + Name; 43 | 44 | /// 45 | #if EFCORE50 || EFCORE60 46 | protected override void Print(ExpressionPrinter expressionPrinter) 47 | #elif EFCORE31 48 | public override void Print(ExpressionPrinter expressionPrinter) 49 | #endif 50 | { 51 | expressionPrinter.Append("excluded.").Append(Name); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Relational/Query/SqlExpressions/SelectIntoExpression.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using Microsoft.EntityFrameworkCore.Utilities; 3 | using System.Linq.Expressions; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions 6 | { 7 | /// 8 | /// An expression that represents a SELECT INTO in a SQL tree. 9 | /// 10 | public sealed class SelectIntoExpression : WrappedExpression 11 | { 12 | public SelectIntoExpression( 13 | string tableName, 14 | string schema, 15 | SelectExpression selectExpression) 16 | { 17 | Check.NotNull(tableName, nameof(tableName)); 18 | Check.NotNull(selectExpression, nameof(selectExpression)); 19 | 20 | TableName = tableName; 21 | Schema = schema; 22 | Expression = selectExpression; 23 | } 24 | 25 | /// 26 | /// The table to INSERT INTO. 27 | /// 28 | public string TableName { get; } 29 | 30 | /// 31 | /// The table to INSERT INTO. 32 | /// 33 | public string Schema { get; } 34 | 35 | /// 36 | /// The SELECT expression to insert. 37 | /// 38 | public SelectExpression Expression { get; } 39 | 40 | /// 41 | protected override Expression VisitChildren(ExpressionVisitor visitor) 42 | { 43 | var expression = visitor.VisitAndConvert(Expression, "VisitSelectInto"); 44 | if (expression == Expression) return this; 45 | return new SelectIntoExpression(TableName, Schema, expression); 46 | } 47 | 48 | /// 49 | protected override void Prints(ExpressionPrinter expressionPrinter) 50 | { 51 | expressionPrinter.Append("INSERT INTO ").AppendLine(TableName); 52 | expressionPrinter.Visit(Expression); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Relational/Query/SqlExpressions/UpsertExpression.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Storage; 4 | using Microsoft.EntityFrameworkCore.Utilities; 5 | using System.Collections.Generic; 6 | using System.Linq.Expressions; 7 | 8 | namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions 9 | { 10 | /// 11 | /// An expression that represents an UPSERT in a SQL tree. 12 | /// 13 | public class UpsertExpression : WrappedExpression 14 | { 15 | public UpsertExpression( 16 | TableExpression targetTable, 17 | TableExpressionBase sourceTable, 18 | IReadOnlyList columns, 19 | IReadOnlyList? onConflict, 20 | IKey conflictConstraint) 21 | { 22 | Check.NotNull(targetTable, nameof(targetTable)); 23 | Check.HasNoNulls(columns, nameof(columns)); 24 | Check.NullOrHasNoNulls(onConflict, nameof(onConflict)); 25 | Check.NotNull(conflictConstraint, nameof(conflictConstraint)); 26 | 27 | TargetTable = targetTable; 28 | SourceTable = sourceTable; 29 | Columns = columns; 30 | OnConflictUpdate = onConflict; 31 | ConflictConstraint = conflictConstraint; 32 | } 33 | 34 | /// 35 | /// The target table to INSERT INTO. 36 | /// 37 | public TableExpression TargetTable { get; } 38 | 39 | /// 40 | /// The source table to SELECT FROM. 41 | /// 42 | public TableExpressionBase SourceTable { get; } 43 | 44 | /// 45 | /// The columns being inserted, whose alias is the corresponding column name. 46 | /// 47 | public IReadOnlyList Columns { get; } 48 | 49 | /// 50 | /// The expressions being updated when conflict. 51 | /// 52 | public IReadOnlyList? OnConflictUpdate { get; } 53 | 54 | /// 55 | /// The conflict constraint name. 56 | /// 57 | public IKey ConflictConstraint { get; } 58 | 59 | /// 60 | protected override void Prints(ExpressionPrinter expressionPrinter) 61 | { 62 | expressionPrinter.Append("Upsert Entity"); 63 | } 64 | 65 | /// 66 | protected override Expression VisitChildren(ExpressionVisitor visitor) 67 | { 68 | const string CallerName = "VisitUpsert"; 69 | 70 | var targetTable = visitor.VisitAndConvert(TargetTable, CallerName); 71 | bool changed = targetTable != TargetTable; 72 | 73 | var sourceTable = visitor.VisitAndConvert(SourceTable, CallerName); 74 | changed = changed || sourceTable != SourceTable; 75 | 76 | var onConflictUpdate = visitor.VisitCollection(OnConflictUpdate, CallerName); 77 | changed = changed || onConflictUpdate != OnConflictUpdate; 78 | 79 | var columns = visitor.VisitCollection(Columns, CallerName); 80 | changed = changed || columns != Columns; 81 | 82 | return changed 83 | ? new UpsertExpression(targetTable, sourceTable, columns, onConflictUpdate, ConflictConstraint) 84 | : this; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Relational/Query/SqlExpressions/WrappedExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions 2 | { 3 | public abstract class WrappedExpression : TableExpressionBase 4 | { 5 | protected WrappedExpression() : base("wrapped") { } 6 | 7 | protected abstract void Prints(ExpressionPrinter expressionPrinter); 8 | 9 | #if EFCORE50 || EFCORE60 10 | protected override void Print(ExpressionPrinter expressionPrinter) 11 | #elif EFCORE31 12 | public override void Print(ExpressionPrinter expressionPrinter) 13 | #endif 14 | { 15 | Prints(expressionPrinter); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Relational/Query/ValuesExpressionParameterExpandingVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | 7 | namespace Microsoft.EntityFrameworkCore.Query 8 | { 9 | public class ValuesExpressionParameterExpandingVisitor : SqlExpressionVisitorV2 10 | { 11 | private readonly IReadOnlyDictionary _parameterValues; 12 | private readonly List<(ValuesExpression, ValuesExpression)> _replacement; 13 | private readonly ISqlExpressionFactory _sqlExpressionFactory; 14 | 15 | public bool CanCache => _replacement.Count == 0; 16 | 17 | public ValuesExpressionParameterExpandingVisitor( 18 | ISqlExpressionFactory sqlExpressionFactory, 19 | IReadOnlyDictionary parameterValues) 20 | { 21 | _sqlExpressionFactory = sqlExpressionFactory; 22 | _parameterValues = parameterValues; 23 | _replacement = new List<(ValuesExpression, ValuesExpression)>(); 24 | } 25 | 26 | protected override Expression VisitColumn(ColumnExpression columnExpression) 27 | { 28 | #if EFCORE60 29 | if (columnExpression.Table is not JoinExpressionBase joinExpression 30 | || joinExpression.Table is not ValuesExpression valuesExpression) 31 | #else 32 | if (columnExpression.Table is not ValuesExpression valuesExpression) 33 | #endif 34 | { 35 | return columnExpression; 36 | } 37 | 38 | // Only when this is a ValuesExpression should we update the column 39 | var result = (TableExpressionBase)Visit(valuesExpression); 40 | return result != columnExpression.Table 41 | ? _sqlExpressionFactory.Column(columnExpression, result) 42 | : columnExpression; 43 | } 44 | 45 | protected override Expression VisitValues(ValuesExpression valuesExpression) 46 | { 47 | if (valuesExpression.ImmediateValues == null 48 | && valuesExpression.RuntimeParameter != null 49 | && valuesExpression.TupleCount == null) 50 | { 51 | if (_parameterValues.TryGetValue(valuesExpression.RuntimeParameter, out var _lists) 52 | && _lists is IList lists) 53 | { 54 | for (int i = 0; i < _replacement.Count; i++) 55 | { 56 | if (_replacement[i].Item1 == valuesExpression) 57 | { 58 | return _replacement[i].Item2; 59 | } 60 | } 61 | 62 | var newExpr = new ValuesExpression(valuesExpression, lists.Count); 63 | _replacement.Add((valuesExpression, newExpr)); 64 | return newExpr; 65 | } 66 | else 67 | { 68 | throw new InvalidOperationException( 69 | "Parameter value corrupted."); 70 | } 71 | } 72 | 73 | return valuesExpression; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Relational/Reduce/OptionsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Bulk; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | 4 | namespace Microsoft.EntityFrameworkCore 5 | { 6 | public static class TableSplittingJoinsRemovalExtensions 7 | { 8 | public static DbContextOptionsBuilder UseTableSplittingJoinsRemoval(this DbContextOptionsBuilder builder) 9 | { 10 | ((IDbContextOptionsBuilderInfrastructure)builder).AddOrUpdateExtension(new TableSplittingJoinsRemovalDbContextOptionsExtension()); 11 | return builder; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Relational/Reduce/OptionsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.Query; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace Microsoft.EntityFrameworkCore.Bulk 10 | { 11 | public class TableSplittingJoinsRemovalDbContextOptionsExtension : IDbContextOptionsExtension 12 | { 13 | private DbContextOptionsExtensionInfo _info; 14 | 15 | public DbContextOptionsExtensionInfo Info => 16 | _info ??= new TableSplittingJoinsRemovalDbContextOptionsExtensionInfo(this); 17 | 18 | public void ApplyServices(IServiceCollection services) 19 | { 20 | var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IQueryTranslationPostprocessorFactory)); 21 | if (descriptor == null) throw new InvalidOperationException("No IQueryTranslationPostprocessorFactory registered."); 22 | 23 | services.Replace( 24 | ServiceDescriptor.Describe( 25 | typeof(IQueryTranslationPostprocessorFactory), 26 | typeof(TableSplittingJoinsWrappingQueryTranslationPostprocessorFactory<>).MakeGenericType(descriptor.ImplementationType), 27 | descriptor.Lifetime)); 28 | } 29 | 30 | public void Validate(IDbContextOptions options) 31 | { 32 | } 33 | 34 | private class TableSplittingJoinsRemovalDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo 35 | { 36 | public TableSplittingJoinsRemovalDbContextOptionsExtensionInfo( 37 | TableSplittingJoinsRemovalDbContextOptionsExtension extension) : base(extension) 38 | { 39 | } 40 | 41 | public override bool IsDatabaseProvider => false; 42 | 43 | public override string LogFragment => "using TableSplittingJoinsRemoval "; 44 | 45 | #if EFCORE31 || EFCORE50 46 | public override long GetServiceProviderHashCode() => 0; 47 | #elif EFCORE60 48 | public override int GetServiceProviderHashCode() => 0; 49 | 50 | public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => true; 51 | #endif 52 | 53 | public override void PopulateDebugInfo(IDictionary debugInfo) 54 | => debugInfo["TableSplittingJoinsRemoval"] = "1"; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Relational/Reduce/QueryTranslationPostprocessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Query 6 | { 7 | public class TableSplittingJoinsRemovalWrappingQueryTranslationPostprocessor : QueryTranslationPostprocessor 8 | { 9 | private readonly ISqlExpressionFactory _sqlExpressionFactory; 10 | private readonly QueryCompilationContext _queryCompilationContext; 11 | private readonly QueryTranslationPostprocessor _queryTranslationPostprocessor; 12 | 13 | public TableSplittingJoinsRemovalWrappingQueryTranslationPostprocessor( 14 | QueryTranslationPostprocessorDependencies dependencies, 15 | QueryTranslationPostprocessor queryTranslationPostprocessor, 16 | ISqlExpressionFactory sqlExpressionFactory, 17 | QueryCompilationContext queryCompilationContext) 18 | #if EFCORE50 || EFCORE60 19 | : base(dependencies, queryCompilationContext) 20 | #elif EFCORE31 21 | : base(dependencies) 22 | #endif 23 | { 24 | _queryTranslationPostprocessor = queryTranslationPostprocessor; 25 | _queryCompilationContext = queryCompilationContext; 26 | _sqlExpressionFactory = sqlExpressionFactory; 27 | } 28 | 29 | public override Expression Process(Expression query) 30 | { 31 | query = new SelfJoinsPruningExpressionVisitor(_queryCompilationContext, _sqlExpressionFactory).Reduce(query); 32 | query = _queryTranslationPostprocessor.Process(query); 33 | return query; 34 | } 35 | } 36 | 37 | public class TableSplittingJoinsWrappingQueryTranslationPostprocessorFactory : 38 | IQueryTranslationPostprocessorFactory 39 | where TPostprocessorFactory : IQueryTranslationPostprocessorFactory 40 | { 41 | private readonly QueryTranslationPostprocessorDependencies _dependencies; 42 | private readonly RelationalQueryTranslationPostprocessorDependencies _relationalDependencies; 43 | private readonly IQueryTranslationPostprocessorFactory _factory; 44 | 45 | public TableSplittingJoinsWrappingQueryTranslationPostprocessorFactory( 46 | QueryTranslationPostprocessorDependencies dependencies, 47 | RelationalQueryTranslationPostprocessorDependencies relationalDependencies, 48 | IServiceProvider serviceProvider) 49 | { 50 | _dependencies = dependencies; 51 | _relationalDependencies = relationalDependencies; 52 | _factory = ActivatorUtilities.CreateInstance(serviceProvider); 53 | } 54 | 55 | public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext) 56 | => new TableSplittingJoinsRemovalWrappingQueryTranslationPostprocessor( 57 | _dependencies, 58 | _factory.Create(queryCompilationContext), 59 | _relationalDependencies.SqlExpressionFactory, 60 | queryCompilationContext); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Relational/Reduce/ShaperQueryExpressionReplacingVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 2 | using System.Linq.Expressions; 3 | 4 | namespace Microsoft.EntityFrameworkCore.Query 5 | { 6 | public class ShaperQueryExpressionReplacingVisitor : ExpressionVisitor 7 | { 8 | private readonly SelectExpression _origin, _new; 9 | 10 | public ShaperQueryExpressionReplacingVisitor(SelectExpression origin, SelectExpression @new) 11 | { 12 | _origin = origin; 13 | _new = @new; 14 | } 15 | 16 | protected override Expression VisitExtension(Expression node) 17 | { 18 | if (node is ProjectionBindingExpression proj && proj.QueryExpression == _origin) 19 | { 20 | if (proj.Index.HasValue) 21 | { 22 | return new ProjectionBindingExpression(_new, proj.Index.Value, proj.Type); 23 | } 24 | else if (proj.ProjectionMember != null) 25 | { 26 | return new ProjectionBindingExpression(_new, proj.ProjectionMember, proj.Type); 27 | } 28 | #if EFCORE31 || EFCORE50 29 | else if (proj.IndexMap != null) 30 | { 31 | return new ProjectionBindingExpression(_new, proj.IndexMap); 32 | } 33 | #endif 34 | } 35 | 36 | return node == _origin ? _new : base.VisitExtension(node); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Relational/RelationalExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using Microsoft.EntityFrameworkCore.Storage.Internal; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Microsoft.EntityFrameworkCore.Bulk 8 | { 9 | public abstract class RelationalBatchOptionsExtension : BatchOptionsExtension 10 | { 11 | internal override HashSet GetRequiredServices() 12 | { 13 | var set = base.GetRequiredServices(); 14 | set.Add(typeof(IBulkQuerySqlGeneratorFactory)); 15 | #if EFCORE50 || EFCORE60 16 | set.Add(typeof(IRelationalBulkParameterBasedSqlProcessorFactory)); 17 | #endif 18 | return set; 19 | } 20 | 21 | internal override Dictionary GetServiceLifetimes() 22 | { 23 | var dict = base.GetServiceLifetimes(); 24 | #if EFCORE31 || EFCORE50 25 | dict.Add(typeof(IMethodCallTranslatorPlugin), ServiceLifetime.Singleton); 26 | dict.Add(typeof(IMemberTranslatorPlugin), ServiceLifetime.Singleton); 27 | #elif EFCORE60 28 | dict.Add(typeof(IMethodCallTranslatorPlugin), ServiceLifetime.Scoped); 29 | dict.Add(typeof(IMemberTranslatorPlugin), ServiceLifetime.Scoped); 30 | #endif 31 | dict.Add(typeof(IBulkQuerySqlGeneratorFactory), ServiceLifetime.Singleton); 32 | dict.Add(typeof(IAnonymousExpressionFactory), ServiceLifetime.Singleton); 33 | #if EFCORE50 34 | dict.Add(typeof(IRelationalBulkParameterBasedSqlProcessorFactory), ServiceLifetime.Singleton); 35 | #elif EFCORE60 36 | dict.Add(typeof(IRelationalBulkParameterBasedSqlProcessorFactory), ServiceLifetime.Scoped); 37 | #endif 38 | return dict; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Relational/Storage/BulkRelationalCommandBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Microsoft.EntityFrameworkCore.Storage 10 | { 11 | public static class BulkRelationalCommandBuilderExtensions 12 | { 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | [DebuggerStepThrough] 15 | public static IRelationalCommandBuilder GenerateList( 16 | this IRelationalCommandBuilder sql, 17 | IReadOnlyList items, 18 | Action generationAction, 19 | Action joinAction = null) 20 | { 21 | joinAction ??= (isb => isb.Append(", ")); 22 | 23 | for (var i = 0; i < items.Count; i++) 24 | { 25 | if (i > 0) joinAction(sql); 26 | generationAction(items[i]); 27 | } 28 | 29 | return sql; 30 | } 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | [DebuggerStepThrough] 34 | public static IRelationalCommandBuilder AppendIf( 35 | this IRelationalCommandBuilder sql, 36 | bool condition, 37 | string value) 38 | { 39 | if (condition) sql.Append(value); 40 | return sql; 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | [DebuggerStepThrough] 45 | public static ExpressionPrinter VisitCollection( 46 | this ExpressionPrinter expressionPrinter, 47 | IReadOnlyList items, 48 | Action generateExpression, 49 | Action joinAction = null) 50 | { 51 | joinAction ??= (isb => isb.Append(", ")); 52 | 53 | for (var i = 0; i < items.Count; i++) 54 | { 55 | if (i > 0) joinAction(expressionPrinter); 56 | generateExpression(expressionPrinter, items[i]); 57 | } 58 | 59 | return expressionPrinter; 60 | } 61 | 62 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 63 | [DebuggerStepThrough] 64 | public static IRelationalCommandBuilder Then(this IRelationalCommandBuilder sql, Action then) 65 | { 66 | then.Invoke(); 67 | return sql; 68 | } 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | [DebuggerStepThrough] 72 | public static IReadOnlyList VisitCollection( 73 | this ExpressionVisitor visitor, 74 | IReadOnlyList items, 75 | string callerName) 76 | where TExpression : Expression 77 | { 78 | if (items == null) return null; 79 | 80 | bool changed = false; 81 | var newItems = items.ToList(); 82 | for (int i = 0; i < newItems.Count; i++) 83 | { 84 | newItems[i] = visitor.VisitAndConvert(newItems[i], callerName); 85 | changed = changed || newItems[i] != items[i]; 86 | } 87 | 88 | return changed ? newItems : items; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Relational/Storage/ValuesRelationalParameter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Utilities; 2 | using System; 3 | using System.Data.Common; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Storage.Internal 6 | { 7 | public class ValuesRelationalParameter : RelationalParameterBase 8 | { 9 | public virtual AnonymousExpressionType Type { get; } 10 | 11 | public virtual string NamePrefix { get; } 12 | 13 | public override string InvariantName { get; } 14 | 15 | public ValuesRelationalParameter(AnonymousExpressionType type, string namePrefix, string invariantName) 16 | #if EFCORE60 17 | : base(invariantName) 18 | #endif 19 | { 20 | Check.NotNull(type, nameof(type)); 21 | Check.NotNull(namePrefix, nameof(namePrefix)); 22 | Check.NotNull(invariantName, nameof(invariantName)); 23 | 24 | Type = type; 25 | NamePrefix = namePrefix; 26 | InvariantName = invariantName; 27 | } 28 | 29 | public override void AddDbParameter(DbCommand command, object value) 30 | { 31 | Check.NotNull(command, nameof(command)); 32 | Check.NotNull(value, nameof(value)); 33 | 34 | if (value is not System.Collections.IList list) 35 | { 36 | throw new InvalidOperationException("Parameter corrupt."); 37 | } 38 | 39 | for (int i = 0; i < list.Count; i++) 40 | { 41 | Type.AddDbParameter(command, $"{NamePrefix}_{i}", list[i]); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/SqlServer/EFCore.Bulk.SqlServer-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31;RELATIONAL;SQL_SERVER 6 | Microsoft.EntityFrameworkCore.Bulk.SqlServer 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).SqlServer 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/SqlServer/EFCore.Bulk.SqlServer-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50;RELATIONAL;SQL_SERVER 6 | Microsoft.EntityFrameworkCore.Bulk.SqlServer 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).SqlServer 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/SqlServer/EFCore.Bulk.SqlServer-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60;RELATIONAL;SQL_SERVER 6 | Microsoft.EntityFrameworkCore.Bulk.SqlServer 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).SqlServer 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/SqlServer/MathTranslationPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Diagnostics; 2 | using Microsoft.EntityFrameworkCore.Query; 3 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | 8 | namespace Microsoft.EntityFrameworkCore.SqlServer.Query 9 | { 10 | public class MathTranslationPlugin : IMethodCallTranslator, IMethodCallTranslatorPlugin 11 | { 12 | private MathTranslationPlugin[] Translators { get; } 13 | 14 | IEnumerable IMethodCallTranslatorPlugin.Translators => Translators; 15 | 16 | ISqlExpressionFactory Sql { get; } 17 | 18 | public MathTranslationPlugin(ISqlExpressionFactory sqlExpressionFactory) 19 | { 20 | Sql = sqlExpressionFactory; 21 | Translators = new[] { this }; 22 | } 23 | 24 | public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments) 25 | { 26 | if (arguments.Count != 2 || method.DeclaringType != typeof(Math)) return null; 27 | 28 | return method.Name switch 29 | { 30 | nameof(Math.Min) => IIF(Sql.LessThan(arguments[0], arguments[1]), arguments[0], arguments[1]), 31 | nameof(Math.Max) => IIF(Sql.GreaterThan(arguments[0], arguments[1]), arguments[0], arguments[1]), 32 | _ => null, 33 | }; 34 | 35 | CaseExpression IIF(SqlExpression test, SqlExpression whenTrue, SqlExpression whenFalse) => 36 | Sql.Case(new[] { new CaseWhenClause(test, whenTrue) }, whenFalse); 37 | } 38 | 39 | public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments, IDiagnosticsLogger logger) 40 | => Translate(instance, method, arguments); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SqlServer/QueryCompilationContextFactory.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE50 || EFCORE60 2 | 3 | using Microsoft.EntityFrameworkCore.Bulk; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; 6 | using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; 7 | using System; 8 | using System.Linq.Expressions; 9 | 10 | namespace Microsoft.EntityFrameworkCore.SqlServer.Query 11 | { 12 | public class SqlServerBulkQueryCompilationContextFactory : 13 | SqlServerQueryCompilationContextFactory, 14 | IBulkQueryCompilationContextFactory, 15 | IServiceAnnotation 16 | { 17 | public SqlServerBulkQueryCompilationContextFactory( 18 | BulkQueryCompilationContextDependencies dependencies, 19 | RelationalQueryCompilationContextDependencies relationalDependencies, 20 | ISqlServerConnection sqlServerConnection) 21 | : base(dependencies.Dependencies, relationalDependencies, sqlServerConnection) 22 | { 23 | } 24 | 25 | public Func CreateQueryExecutor(bool async, Expression query) 26 | { 27 | return Create(async).CreateQueryExecutor(query); 28 | } 29 | } 30 | } 31 | 32 | #endif -------------------------------------------------------------------------------- /src/Sqlite/EFCore.Bulk.Sqlite-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(DefineConstants);EFCORE31;RELATIONAL;SQLITE 6 | Microsoft.EntityFrameworkCore.Bulk.Sqlite 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).Sqlite 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Sqlite/EFCore.Bulk.Sqlite-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | $(DefineConstants);EFCORE50;RELATIONAL;SQLITE 6 | Microsoft.EntityFrameworkCore.Bulk.Sqlite 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).Sqlite 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Sqlite/EFCore.Bulk.Sqlite-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60;RELATIONAL;SQLITE 6 | Microsoft.EntityFrameworkCore.Bulk.Sqlite 7 | Microsoft.EntityFrameworkCore 8 | $(ExtensionPackagePrefix).Sqlite 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Sqlite/QueryCompilationContextFactory.cs: -------------------------------------------------------------------------------- 1 | #if EFCORE50 || EFCORE60 2 | 3 | using Microsoft.EntityFrameworkCore.Bulk; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Microsoft.EntityFrameworkCore.Query.Internal; 6 | using System; 7 | using System.Linq.Expressions; 8 | 9 | namespace Microsoft.EntityFrameworkCore.Sqlite.Query 10 | { 11 | public class SqliteBulkQueryCompilationContextFactory : 12 | RelationalQueryCompilationContextFactory, 13 | IBulkQueryCompilationContextFactory, 14 | IServiceAnnotation 15 | { 16 | public SqliteBulkQueryCompilationContextFactory( 17 | BulkQueryCompilationContextDependencies dependencies, 18 | RelationalQueryCompilationContextDependencies relationalDependencies) 19 | : base(dependencies.Dependencies, relationalDependencies) 20 | { 21 | } 22 | 23 | public Func CreateQueryExecutor(bool async, Expression query) 24 | { 25 | return Create(async).CreateQueryExecutor(query); 26 | } 27 | } 28 | } 29 | 30 | #endif -------------------------------------------------------------------------------- /src/Sqlite/QueryCompiler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Bulk; 2 | using Microsoft.EntityFrameworkCore.Diagnostics; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Query; 6 | using Microsoft.EntityFrameworkCore.Query.Internal; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using System; 9 | using System.Linq.Expressions; 10 | 11 | namespace Microsoft.EntityFrameworkCore.Sqlite.Query 12 | { 13 | public class SqliteBulkQueryCompiler : BulkQueryCompiler 14 | { 15 | public SqliteBulkQueryCompiler( 16 | IQueryContextFactory queryContextFactory, 17 | ICompiledQueryCache compiledQueryCache, 18 | ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator, 19 | IDatabase database, 20 | IDiagnosticsLogger logger, 21 | ICurrentDbContext currentContext, 22 | IEvaluatableExpressionFilter evaluatableExpressionFilter, 23 | IBulkQueryCompilationContextFactory qccFactory, 24 | IModel model) 25 | : base(queryContextFactory, 26 | compiledQueryCache, 27 | compiledQueryCacheKeyGenerator, 28 | database, 29 | logger, 30 | currentContext, 31 | evaluatableExpressionFilter, 32 | qccFactory, 33 | model) 34 | { 35 | } 36 | 37 | protected override Func CompileBulkCore(IDatabase database, Expression query, IModel model, bool async) 38 | { 39 | if (query is MethodCallExpression methodCallExpression 40 | && methodCallExpression.Method.GetGenericMethodDefinition() == BatchOperationMethods.MergeCollapsed) 41 | { 42 | throw TranslationFailed(query, "MERGE INTO sentences are not supported in SQLite."); 43 | } 44 | 45 | return base.CompileBulkCore(database, query, model, async); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Sqlite/SqliteBatchExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Bulk; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.Query; 4 | using Microsoft.EntityFrameworkCore.Query.Internal; 5 | using Microsoft.EntityFrameworkCore.Sqlite.Query; 6 | using Microsoft.EntityFrameworkCore.Storage.Internal; 7 | 8 | namespace Microsoft.EntityFrameworkCore 9 | { 10 | public static class SqliteBatchExtensions 11 | { 12 | public static SqliteDbContextOptionsBuilder UseBulk(this SqliteDbContextOptionsBuilder builder) 13 | { 14 | var builder1 = ((IRelationalDbContextOptionsBuilderInfrastructure)builder).OptionsBuilder; 15 | var ext = new SqliteBatchOptionsExtension(); 16 | ((IDbContextOptionsBuilderInfrastructure)builder1).AddOrUpdateExtension(ext); 17 | return builder; 18 | } 19 | } 20 | 21 | public class SqliteBatchOptionsExtension : RelationalBatchOptionsExtension 22 | { 23 | public override string Name => "SqliteBatchExtension"; 24 | 25 | protected override void ApplyServices(ExtensionServicesBuilder services) 26 | { 27 | services.TryAdd(); 28 | 29 | services.TryAdd(); 30 | services.TryAdd(); 31 | services.TryAdd(); 32 | services.TryAdd(); 33 | 34 | services.TryAdd(); 35 | services.TryAdd(); 36 | #if EFCORE50 || EFCORE60 37 | services.TryAdd(); 38 | services.TryAdd(); 39 | #elif EFCORE31 40 | services.TryAdd(); 41 | SearchConditionBooleanGuard.AddTypeField(typeof(NullSemanticsRewritingExpressionVisitor), "_canOptimize"); 42 | #endif 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/InMemory/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Tests 2 | { 3 | public class InMemoryContextFactory : ContextFactoryBase 4 | where TContext : DbContext 5 | { 6 | protected override void Configure(DbContextOptionsBuilder optionsBuilder) 7 | { 8 | optionsBuilder.UseInMemoryDatabase( 9 | "Nothing", 10 | s => s.UseBulk()); 11 | } 12 | 13 | protected override void EnsureCreated(TContext context) 14 | { 15 | } 16 | 17 | protected override void EnsureDeleted(TContext context) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/InMemory/Context.PrepareDatabase.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | [Collection("DatabaseCollection")] 6 | public class DatabaseCollection : 7 | DatabaseCollection>, 8 | ICollectionFixture 9 | { 10 | public DatabaseCollection() : base(new InMemoryContextFactory()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/InMemory/EFCore.Bulk.InMemory.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.InMemory.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/InMemory/EFCore.Bulk.InMemory.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.InMemory.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/InMemory/EFCore.Bulk.InMemory.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.InMemory.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/InMemory/Functional.Delete.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchDelete; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class InMemoryDeleteTest : DeleteTestBase> 6 | { 7 | public InMemoryDeleteTest( 8 | InMemoryContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/InMemory/Functional.InsertInto.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchInsertInto; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class InMemoryInsertIntoTest : InsertIntoTestBase> 6 | { 7 | public InMemoryInsertIntoTest( 8 | InMemoryContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/InMemory/Functional.MergeInto.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.MergeInto; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class InMemoryMergeIntoTest : MergeIntoTestBase> 6 | { 7 | public InMemoryMergeIntoTest( 8 | InMemoryContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/InMemory/Functional.Update.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchUpdate; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class InMemoryUpdateTest : UpdateTestBase> 6 | { 7 | public InMemoryUpdateTest( 8 | InMemoryContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/InMemory/Functional.UpdateJoin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchUpdateJoin; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class InMemoryUpdateJoinTest : UpdateJoinTestBase> 6 | { 7 | public InMemoryUpdateJoinTest( 8 | InMemoryContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/InMemory/Functional.Upsert.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.Upsert; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class InMemoryUpsertTest : UpsertTestBase> 6 | { 7 | public InMemoryUpsertTest( 8 | InMemoryContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/MySql/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | using Pomelo.EntityFrameworkCore.MySql.Storage; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class MySqlContextFactory : RelationalContextFactoryBase 6 | where TContext : DbContext 7 | { 8 | public ServerVersion ServerVersion { get; private set; } 9 | 10 | protected override string ScriptSplit => ";\r\n\r\n"; 11 | 12 | protected override string DropTableCommand => "DROP TABLE IF EXISTS `{0}`"; 13 | 14 | protected override void Configure(DbContextOptionsBuilder optionsBuilder) 15 | { 16 | var connectionString = 17 | $"Server=localhost;" + 18 | $"Port=3306;" + 19 | $"Database=efcorebulktest{Suffix};" + 20 | $"User=root;" + 21 | $"Password=Password12!;" + 22 | $"Character Set=utf8;" + 23 | $"TreatTinyAsBoolean=true;"; 24 | 25 | ServerVersion = ServerVersion.AutoDetect(connectionString); 26 | 27 | #if EFCORE50 || EFCORE60 28 | optionsBuilder.UseMySql( 29 | connectionString, 30 | ServerVersion, 31 | s => s.UseBulk()); 32 | #elif EFCORE31 33 | optionsBuilder.UseMySql( 34 | connectionString, 35 | s => s.UseBulk() 36 | .ServerVersion(ServerVersion)); 37 | #endif 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/MySql/Context.PrepareDatabase.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | [Collection("DatabaseCollection")] 6 | public class DatabaseCollection : 7 | DatabaseCollection>, 8 | ICollectionFixture 9 | { 10 | public DatabaseCollection() : base(new MySqlContextFactory()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/MySql/EFCore.Bulk.MySql.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.MySQL.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/MySql/EFCore.Bulk.MySql.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.MySQL.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/MySql/EFCore.Bulk.MySql.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.MySQL.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/MySql/Functional.Delete.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchDelete; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class MySqlDeleteTest : DeleteTestBase> 6 | { 7 | public MySqlDeleteTest( 8 | MySqlContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_ConstantCondition() 14 | { 15 | base.CompiledQuery_ConstantCondition(); 16 | 17 | AssertSql(@" 18 | DELETE `i` 19 | FROM `Item_{{schema}}` AS `i` 20 | WHERE (`i`.`ItemId` > 500) AND (`i`.`Price` = 3.0) 21 | "); 22 | } 23 | 24 | public override void CompiledQuery_ContainsSomething() 25 | { 26 | base.CompiledQuery_ContainsSomething(); 27 | 28 | AssertSql(@" 29 | DELETE `i` 30 | FROM `Item_{{schema}}` AS `i` 31 | WHERE `i`.`Name` IN ('jyntnytjyntjntnytnt', 'aaa') 32 | "); 33 | } 34 | 35 | public override void CompiledQuery_ParameteredCondition() 36 | { 37 | base.CompiledQuery_ParameteredCondition(); 38 | 39 | AssertSql(@" 40 | DELETE `i` 41 | FROM `Item_{{schema}}` AS `i` 42 | WHERE `i`.`Name` = @__nameToDelete 43 | "); 44 | } 45 | 46 | public override void ConstantCondition() 47 | { 48 | base.ConstantCondition(); 49 | 50 | AssertSql(@" 51 | DELETE `i` 52 | FROM `Item_{{schema}}` AS `i` 53 | WHERE (`i`.`ItemId` > 500) AND (`i`.`Price` = 124.0) 54 | "); 55 | } 56 | 57 | public override void ContainsAndAlsoEqual() 58 | { 59 | base.ContainsAndAlsoEqual(); 60 | 61 | AssertSql(V31, @" 62 | DELETE `i` 63 | FROM `Item_{{schema}}` AS `i` 64 | WHERE `i`.`Description` IN ('info') OR (`i`.`Name` = @__nameToDelete_1) 65 | "); 66 | 67 | AssertSql(V50 | V60, @" 68 | DELETE `i` 69 | FROM `Item_{{schema}}` AS `i` 70 | WHERE (`i`.`Description` = 'info') OR (`i`.`Name` = @__nameToDelete_1) 71 | "); 72 | } 73 | 74 | public override void ContainsSomething() 75 | { 76 | base.ContainsSomething(); 77 | 78 | AssertSql(@" 79 | DELETE `i` 80 | FROM `Item_{{schema}}` AS `i` 81 | WHERE `i`.`Description` IN ('info', 'aaa') 82 | "); 83 | } 84 | 85 | public override void EmptyContains() 86 | { 87 | base.EmptyContains(); 88 | 89 | AssertSql(V31, @" 90 | DELETE `i` 91 | FROM `Item_{{schema}}` AS `i` 92 | WHERE TRUE = FALSE 93 | "); 94 | 95 | AssertSql(V50 | V60, @" 96 | DELETE `i` 97 | FROM `Item_{{schema}}` AS `i` 98 | WHERE FALSE 99 | "); 100 | } 101 | 102 | public override void ListAny() 103 | { 104 | base.ListAny(); 105 | 106 | AssertSql(V31, @" 107 | DELETE `i` 108 | FROM `Item_{{schema}}` AS `i` 109 | WHERE `i`.`Description` IN ('info') 110 | "); 111 | 112 | AssertSql(V50 | V60, @" 113 | DELETE `i` 114 | FROM `Item_{{schema}}` AS `i` 115 | WHERE `i`.`Description` = 'info' 116 | "); 117 | } 118 | 119 | public override void ParameteredCondition() 120 | { 121 | base.ParameteredCondition(); 122 | 123 | AssertSql(@" 124 | DELETE `i` 125 | FROM `Item_{{schema}}` AS `i` 126 | WHERE `i`.`Name` = @__nameToDelete_0 127 | "); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/MySql/Functional.InsertInto.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchInsertInto; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class MySqlInsertIntoTest : InsertIntoTestBase> 6 | { 7 | public MySqlInsertIntoTest( 8 | MySqlContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalSelectInto() 14 | { 15 | base.CompiledQuery_NormalSelectInto(); 16 | 17 | AssertSql(@" 18 | INSERT INTO `ChangeLog_{{schema}}` (`Description`, `ChangedBy`, `Audit_IsDeleted`) 19 | SELECT CONCAT(COALESCE(`j`.`Server`, @__hh), '666') AS `Description`, `j`.`CompileError` AS `ChangedBy`, TRUE AS `Audit_IsDeleted` 20 | FROM `Judging_{{schema}}` AS `j` 21 | "); 22 | } 23 | 24 | public override void CompiledQuery_WithAbstractType() 25 | { 26 | base.CompiledQuery_WithAbstractType(); 27 | 28 | AssertSql(@" 29 | INSERT INTO `Person_{{schema}}` (`Name`, `Class`) 30 | SELECT `p`.`Name`, `p`.`Subject` AS `Class` 31 | FROM `Person_{{schema}}` AS `p` 32 | WHERE `p`.`Discriminator` = 'Student' 33 | "); 34 | } 35 | 36 | public override void CompiledQuery_WithComputedColumn() 37 | { 38 | base.CompiledQuery_WithComputedColumn(); 39 | 40 | AssertSql(V31, @" 41 | INSERT INTO `Document_{{schema}}` (`Content`) 42 | SELECT CONCAT(`d`.`Content`, CONVERT(`d`.`ContentLength`, CHAR(11))) AS `Content` 43 | FROM `Document_{{schema}}` AS `d` 44 | "); 45 | 46 | AssertSql(V50, @" 47 | INSERT INTO `Document_{{schema}}` (`Content`) 48 | SELECT CONCAT(`d`.`Content`, CAST(`d`.`ContentLength` AS char) COLLATE utf8mb4_bin) AS `Content` 49 | FROM `Document_{{schema}}` AS `d` 50 | "); 51 | 52 | AssertSql(V60, @" 53 | INSERT INTO `Document_{{schema}}` (`Content`) 54 | SELECT CONCAT(`d`.`Content`, CAST(`d`.`ContentLength` AS char)) AS `Content` 55 | FROM `Document_{{schema}}` AS `d` 56 | "); 57 | } 58 | 59 | public override void NormalSelectInto() 60 | { 61 | base.NormalSelectInto(); 62 | 63 | AssertSql(@" 64 | INSERT INTO `ChangeLog_{{schema}}` (`Description`, `ChangedBy`, `Audit_IsDeleted`) 65 | SELECT CONCAT(COALESCE(`j`.`Server`, @__hh_0), '666') AS `Description`, `j`.`CompileError` AS `ChangedBy`, TRUE AS `Audit_IsDeleted` 66 | FROM `Judging_{{schema}}` AS `j` 67 | "); 68 | } 69 | 70 | public override void WithAbstractType() 71 | { 72 | base.WithAbstractType(); 73 | 74 | AssertSql(@" 75 | INSERT INTO `Person_{{schema}}` (`Name`, `Class`) 76 | SELECT `p`.`Name`, `p`.`Subject` AS `Class` 77 | FROM `Person_{{schema}}` AS `p` 78 | WHERE `p`.`Discriminator` = 'Student' 79 | "); 80 | } 81 | 82 | public override void WithComputedColumn() 83 | { 84 | base.WithComputedColumn(); 85 | 86 | AssertSql(V31, @" 87 | INSERT INTO `Document_{{schema}}` (`Content`) 88 | SELECT CONCAT(`d`.`Content`, CONVERT(`d`.`ContentLength`, CHAR(11))) AS `Content` 89 | FROM `Document_{{schema}}` AS `d` 90 | "); 91 | 92 | AssertSql(V50, @" 93 | INSERT INTO `Document_{{schema}}` (`Content`) 94 | SELECT CONCAT(`d`.`Content`, CAST(`d`.`ContentLength` AS char) COLLATE utf8mb4_bin) AS `Content` 95 | FROM `Document_{{schema}}` AS `d` 96 | "); 97 | 98 | AssertSql(V60, @" 99 | INSERT INTO `Document_{{schema}}` (`Content`) 100 | SELECT CONCAT(`d`.`Content`, CAST(`d`.`ContentLength` AS char)) AS `Content` 101 | FROM `Document_{{schema}}` AS `d` 102 | "); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/MySql/Functional.UpdateJoin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchUpdateJoin; 2 | using Pomelo.EntityFrameworkCore.MySql.Infrastructure; 3 | using Pomelo.EntityFrameworkCore.MySql.Storage; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Tests 6 | { 7 | public class MySqlUpdateJoinTest : UpdateJoinTestBase> 8 | { 9 | public ServerVersion ServerVersion { get; } 10 | 11 | public MySqlUpdateJoinTest( 12 | MySqlContextFactory factory) 13 | : base(factory) 14 | { 15 | ServerVersion = factory.ServerVersion; 16 | } 17 | 18 | public override void CompiledQuery_NormalUpdate() 19 | { 20 | base.CompiledQuery_NormalUpdate(); 21 | 22 | AssertSql(@" 23 | UPDATE `ItemA_{{schema}}` AS `i` 24 | INNER JOIN ( 25 | SELECT `i0`.`Id`, `i0`.`Value` 26 | FROM `ItemB_{{schema}}` AS `i0` 27 | WHERE `i0`.`Value` = @__aa 28 | ) AS `t` ON `i`.`Id` = `t`.`Id` 29 | SET `i`.`Value` = (`i`.`Value` + `t`.`Value`) - @__cc 30 | WHERE `i`.`Id` = @__bb 31 | "); 32 | } 33 | 34 | public override void LocalTableJoin() 35 | { 36 | base.LocalTableJoin(); 37 | 38 | if (ServerVersion.Type == ServerType.MySql && ServerVersion.Version >= new System.Version(8, 0, 19)) 39 | { 40 | AssertSql(@" 41 | UPDATE `ItemB_{{schema}}` AS `i` 42 | INNER JOIN ( 43 | VALUES 44 | ROW(@__p_0_0_0, @__p_0_0_1), 45 | ROW(@__p_0_1_0, @__p_0_1_1), 46 | ROW(@__p_0_2_0, @__p_0_2_1) 47 | ) AS `cte` (`Id`, `Value`) ON `i`.`Id` = `cte`.`Id` 48 | SET `i`.`Value` = `i`.`Value` + `cte`.`Value` 49 | WHERE `i`.`Id` <> 2 50 | "); 51 | } 52 | else 53 | { 54 | AssertSql(@" 55 | UPDATE `ItemB_{{schema}}` AS `i` 56 | INNER JOIN ( 57 | SELECT @__p_0_0_0 AS `Id`, @__p_0_0_1 AS `Value` 58 | UNION SELECT @__p_0_1_0, @__p_0_1_1 59 | UNION SELECT @__p_0_2_0, @__p_0_2_1 60 | ) AS `cte` ON `i`.`Id` = `cte`.`Id` 61 | SET `i`.`Value` = `i`.`Value` + `cte`.`Value` 62 | WHERE `i`.`Id` <> 2 63 | "); 64 | } 65 | } 66 | 67 | public override void NormalUpdate() 68 | { 69 | base.NormalUpdate(); 70 | 71 | AssertSql(@" 72 | UPDATE `ItemA_{{schema}}` AS `i` 73 | INNER JOIN ( 74 | SELECT `i0`.`Id`, `i0`.`Value` 75 | FROM `ItemB_{{schema}}` AS `i0` 76 | WHERE `i0`.`Value` = 2 77 | ) AS `t` ON `i`.`Id` = `t`.`Id` 78 | SET `i`.`Value` = (`i`.`Value` + `t`.`Value`) - 3 79 | WHERE `i`.`Id` = 1 80 | "); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/PostgreSql/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class NpgsqlContextFactory : RelationalContextFactoryBase 6 | where TContext : DbContext 7 | { 8 | protected override string ScriptSplit => ";\r\n\r\n"; 9 | 10 | protected override string DropTableCommand => "DROP TABLE IF EXISTS \"{0}\""; 11 | 12 | protected override void Configure(DbContextOptionsBuilder optionsBuilder) 13 | { 14 | string username = Environment.GetEnvironmentVariable("PGUSER") ?? "postgres"; 15 | string password = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "Password12!"; 16 | 17 | optionsBuilder.UseNpgsql( 18 | $"User ID={username};" + 19 | $"Password={password};" + 20 | $"Host=localhost;" + 21 | $"Port=5432;" + 22 | $"Database=efcorebulktest{Suffix};" + 23 | $"Pooling=true;", 24 | s => s.UseBulk().UseLegacyDateTimeOffset()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/PostgreSql/Context.PrepareDatabase.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | [Collection("DatabaseCollection")] 6 | public class DatabaseCollection : 7 | DatabaseCollection>, 8 | ICollectionFixture 9 | { 10 | public DatabaseCollection() : base(new NpgsqlContextFactory()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/PostgreSql/EFCore.Bulk.PostgreSql.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.PostgreSQL.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/PostgreSql/EFCore.Bulk.PostgreSql.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.PostgreSQL.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/PostgreSql/EFCore.Bulk.PostgreSql.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.PostgreSQL.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/PostgreSql/Functional.InsertInto.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchInsertInto; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class NpgsqlInsertIntoTest : InsertIntoTestBase> 6 | { 7 | public NpgsqlInsertIntoTest( 8 | NpgsqlContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalSelectInto() 14 | { 15 | base.CompiledQuery_NormalSelectInto(); 16 | 17 | AssertSql(@" 18 | INSERT INTO ""ChangeLog_{{schema}}"" (""Description"", ""ChangedBy"", ""Audit_IsDeleted"") 19 | SELECT COALESCE(j.""Server"", @__hh) || '666' AS ""Description"", j.""CompileError"" AS ""ChangedBy"", TRUE AS ""Audit_IsDeleted"" 20 | FROM ""Judging_{{schema}}"" AS j 21 | "); 22 | } 23 | 24 | public override void CompiledQuery_WithAbstractType() 25 | { 26 | base.CompiledQuery_WithAbstractType(); 27 | 28 | AssertSql(@" 29 | INSERT INTO ""Person_{{schema}}"" (""Name"", ""Class"") 30 | SELECT p.""Name"", p.""Subject"" AS ""Class"" 31 | FROM ""Person_{{schema}}"" AS p 32 | WHERE p.""Discriminator"" = 'Student' 33 | "); 34 | } 35 | 36 | public override void CompiledQuery_WithComputedColumn() 37 | { 38 | base.CompiledQuery_WithComputedColumn(); 39 | 40 | AssertSql(V31 | V50, @" 41 | INSERT INTO ""Document_{{schema}}"" (""Content"") 42 | SELECT d.""Content"" || CAST(d.""ContentLength"" AS text) AS ""Content"" 43 | FROM ""Document_{{schema}}"" AS d 44 | "); 45 | 46 | AssertSql(V60, @" 47 | INSERT INTO ""Document_{{schema}}"" (""Content"") 48 | SELECT d.""Content"" || d.""ContentLength""::text AS ""Content"" 49 | FROM ""Document_{{schema}}"" AS d 50 | "); 51 | } 52 | 53 | public override void NormalSelectInto() 54 | { 55 | base.NormalSelectInto(); 56 | 57 | AssertSql(@" 58 | INSERT INTO ""ChangeLog_{{schema}}"" (""Description"", ""ChangedBy"", ""Audit_IsDeleted"") 59 | SELECT COALESCE(j.""Server"", @__hh_0) || '666' AS ""Description"", j.""CompileError"" AS ""ChangedBy"", TRUE AS ""Audit_IsDeleted"" 60 | FROM ""Judging_{{schema}}"" AS j 61 | "); 62 | } 63 | 64 | public override void WithAbstractType() 65 | { 66 | base.WithAbstractType(); 67 | 68 | AssertSql(@" 69 | INSERT INTO ""Person_{{schema}}"" (""Name"", ""Class"") 70 | SELECT p.""Name"", p.""Subject"" AS ""Class"" 71 | FROM ""Person_{{schema}}"" AS p 72 | WHERE p.""Discriminator"" = 'Student' 73 | "); 74 | } 75 | 76 | public override void WithComputedColumn() 77 | { 78 | base.WithComputedColumn(); 79 | 80 | AssertSql(V31 | V50, @" 81 | INSERT INTO ""Document_{{schema}}"" (""Content"") 82 | SELECT d.""Content"" || CAST(d.""ContentLength"" AS text) AS ""Content"" 83 | FROM ""Document_{{schema}}"" AS d 84 | "); 85 | 86 | AssertSql(V60, @" 87 | INSERT INTO ""Document_{{schema}}"" (""Content"") 88 | SELECT d.""Content"" || d.""ContentLength""::text AS ""Content"" 89 | FROM ""Document_{{schema}}"" AS d 90 | "); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/PostgreSql/Functional.UpdateJoin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchUpdateJoin; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class NpgsqlUpdateJoinTest : UpdateJoinTestBase> 6 | { 7 | public NpgsqlUpdateJoinTest( 8 | NpgsqlContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalUpdate() 14 | { 15 | base.CompiledQuery_NormalUpdate(); 16 | 17 | AssertSql(@" 18 | UPDATE ""ItemA_{{schema}}"" AS i 19 | SET ""Value"" = (i.""Value"" + t.""Value"") - @__cc 20 | FROM ( 21 | SELECT i0.""Id"", i0.""Value"" 22 | FROM ""ItemB_{{schema}}"" AS i0 23 | WHERE i0.""Value"" = @__aa 24 | ) AS t 25 | WHERE (i.""Id"" = @__bb) AND (i.""Id"" = t.""Id"") 26 | "); 27 | } 28 | 29 | public override void LocalTableJoin() 30 | { 31 | base.LocalTableJoin(); 32 | 33 | AssertSql(@" 34 | UPDATE ""ItemB_{{schema}}"" AS i 35 | SET ""Value"" = i.""Value"" + cte.""Value"" 36 | FROM ( 37 | VALUES 38 | (@__p_0_0_0, @__p_0_0_1), 39 | (@__p_0_1_0, @__p_0_1_1), 40 | (@__p_0_2_0, @__p_0_2_1) 41 | ) AS cte (""Id"", ""Value"") 42 | WHERE (i.""Id"" <> 2) AND (i.""Id"" = cte.""Id"") 43 | "); 44 | } 45 | 46 | public override void NormalUpdate() 47 | { 48 | base.NormalUpdate(); 49 | 50 | AssertSql(@" 51 | UPDATE ""ItemA_{{schema}}"" AS i 52 | SET ""Value"" = (i.""Value"" + t.""Value"") - 3 53 | FROM ( 54 | SELECT i0.""Id"", i0.""Value"" 55 | FROM ""ItemB_{{schema}}"" AS i0 56 | WHERE i0.""Value"" = 2 57 | ) AS t 58 | WHERE (i.""Id"" = 1) AND (i.""Id"" = t.""Id"") 59 | "); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/Relational/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Tests 6 | { 7 | public abstract class RelationalContextFactoryBase : 8 | ContextFactoryBase 9 | where TContext : DbContext 10 | { 11 | protected string Suffix { get; } 12 | 13 | protected abstract string ScriptSplit { get; } 14 | 15 | protected abstract string DropTableCommand { get; } 16 | 17 | protected RelationalContextFactoryBase() 18 | { 19 | #if EFCORE31 20 | Suffix = "v31"; 21 | #elif EFCORE50 22 | Suffix = "v50"; 23 | #elif EFCORE60 24 | Suffix = "v60"; 25 | #endif 26 | } 27 | 28 | protected override void PostConfigure(DbContextOptionsBuilder optionsBuilder) 29 | { 30 | base.PostConfigure(optionsBuilder); 31 | optionsBuilder.AddInterceptors(new CommandInterceptor(CommandTracer)); 32 | } 33 | 34 | protected override void EnsureCreated(TContext context) 35 | { 36 | if (!context.Database.EnsureCreated()) 37 | { 38 | var script = context.Database.GenerateCreateScript(); 39 | foreach (var line in script.Trim().Split(ScriptSplit, StringSplitOptions.RemoveEmptyEntries)) 40 | { 41 | context.Database.ExecuteSqlRaw(line.Trim()); 42 | } 43 | } 44 | } 45 | 46 | protected override void EnsureDeleted(TContext context) 47 | { 48 | List entityTypes = new(); 49 | 50 | void RecursiveSearch(IEntityType entityType) 51 | { 52 | foreach (IForeignKey foreignKey in entityType.GetForeignKeys()) 53 | { 54 | if (foreignKey.PrincipalEntityType != entityType) 55 | { 56 | if (!entityTypes.Contains(foreignKey.PrincipalEntityType)) 57 | { 58 | RecursiveSearch(foreignKey.PrincipalEntityType); 59 | } 60 | } 61 | } 62 | 63 | if (!entityTypes.Contains(entityType)) 64 | { 65 | entityTypes.Add(entityType); 66 | } 67 | } 68 | 69 | foreach (IEntityType entityType in context.Model.GetEntityTypes()) 70 | { 71 | RecursiveSearch(entityType); 72 | } 73 | 74 | entityTypes.Reverse(); 75 | foreach (var item in entityTypes) 76 | { 77 | var tableName = item.GetTableName(); 78 | if (tableName != null) 79 | { 80 | context.Database.ExecuteSqlRaw(string.Format(DropTableCommand, tableName)); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /test/Relational/EFCore.Bulk.Relational.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Relational.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 19 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Relational/EFCore.Bulk.Relational.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Relational.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 19 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Relational/EFCore.Bulk.Relational.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Relational.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 19 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Specifications/Context.CommandNotifier.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Tests 2 | { 3 | public interface ICommandNotifier 4 | { 5 | string LastCommand { get; set; } 6 | 7 | bool Receiving { get; } 8 | 9 | public void SetLastCommand(string command) 10 | { 11 | if (Receiving) 12 | { 13 | LastCommand = command; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/Specifications/Context.CommandTracer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Tests 6 | { 7 | public class CommandTracer : ICommandNotifier 8 | { 9 | private string _lastCommand; 10 | private bool _receivingCommand; 11 | 12 | string ICommandNotifier.LastCommand 13 | { 14 | get 15 | { 16 | return _lastCommand; 17 | } 18 | 19 | set 20 | { 21 | _lastCommand = value; 22 | Output.WriteLine( 23 | "------------------------------------------\n" + 24 | $"-- {DateTimeOffset.UtcNow:s} Received Command\n" + 25 | "------------------------------------------\n" + 26 | (value ?? "") + 27 | "\n"); 28 | } 29 | } 30 | 31 | bool ICommandNotifier.Receiving => _receivingCommand; 32 | 33 | public void AssertSql(string sql) 34 | { 35 | sql = sql.Trim().Replace("\r", string.Empty).Replace("\n", Environment.NewLine); 36 | Assert.Equal(sql, _lastCommand); 37 | } 38 | 39 | public CommandReceivingDisposable BeginScope() 40 | { 41 | if (_receivingCommand) 42 | { 43 | throw new InvalidOperationException("Cannot create a nested scope."); 44 | } 45 | 46 | return new CommandReceivingDisposable(this); 47 | } 48 | 49 | public sealed class CommandReceivingDisposable : IDisposable 50 | { 51 | private CommandTracer _query; 52 | 53 | public CommandReceivingDisposable(CommandTracer query) 54 | { 55 | query._receivingCommand = true; 56 | _query = query; 57 | } 58 | 59 | public void Dispose() 60 | { 61 | if (_query != null) 62 | { 63 | _query._receivingCommand = false; 64 | } 65 | 66 | _query = null; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Specifications/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace Microsoft.EntityFrameworkCore.Tests 6 | { 7 | public interface IDbContextFactory : IDisposable 8 | where TContext : DbContext 9 | { 10 | ILoggerFactory LoggerFactory { get; } 11 | 12 | CommandTracer CommandTracer { get; } 13 | 14 | string UniqueId { get; } 15 | 16 | DbContextOptions Options { get; } 17 | 18 | object Seed { get; } 19 | 20 | TContext Create(); 21 | } 22 | 23 | public interface IDbContextWithSeeds 24 | { 25 | object Seed(); 26 | } 27 | 28 | public abstract class ContextFactoryBase : 29 | IDbContextFactory 30 | where TContext : DbContext 31 | { 32 | private Func _factory; 33 | private DbContextOptions _options; 34 | 35 | public ILoggerFactory LoggerFactory { get; } 36 | 37 | public CommandTracer CommandTracer { get; } 38 | 39 | public object Seed { get; } 40 | 41 | public string UniqueId { get; } 42 | 43 | public DbContextOptions Options => _options; 44 | 45 | public virtual TContext Create() 46 | { 47 | return _factory(); 48 | } 49 | 50 | protected ContextFactoryBase() 51 | { 52 | var contextType = typeof(TContext); 53 | var constructor = contextType.GetConstructor(new[] { typeof(string), typeof(DbContextOptions) }); 54 | if (constructor == null) 55 | { 56 | throw new InvalidOperationException("The DbContext type must have a constructor of .ctor(string schema, DbContextOptions options)."); 57 | } 58 | 59 | LoggerFactory = ContextLoggerFactory.Singleton; 60 | CommandTracer = new CommandTracer(); 61 | UniqueId = Guid.NewGuid().ToString()[0..6]; 62 | 63 | var optionsBuilder = new DbContextOptionsBuilder(); 64 | Configure(optionsBuilder); 65 | PostConfigure(optionsBuilder); 66 | _options = optionsBuilder.Options; 67 | 68 | _factory = Expression.Lambda>( 69 | Expression.New( 70 | constructor, 71 | Expression.Constant(UniqueId), 72 | Expression.Constant(_options))) 73 | .Compile(); 74 | 75 | using (var context = _factory()) 76 | { 77 | EnsureCreated(context); 78 | } 79 | 80 | using (var context = _factory()) 81 | { 82 | Seed = (context as IDbContextWithSeeds)?.Seed(); 83 | } 84 | } 85 | 86 | protected virtual void PostConfigure(DbContextOptionsBuilder optionsBuilder) 87 | { 88 | optionsBuilder.UseLoggerFactory(LoggerFactory); 89 | } 90 | 91 | protected abstract void Configure(DbContextOptionsBuilder optionsBuilder); 92 | 93 | protected abstract void EnsureCreated(TContext context); 94 | 95 | protected abstract void EnsureDeleted(TContext context); 96 | 97 | public void Dispose() 98 | { 99 | using (var context = _factory()) 100 | { 101 | EnsureDeleted(context); 102 | } 103 | 104 | _factory = null; 105 | _options = null; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test/Specifications/Context.LoggerFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public static class ContextLoggerFactory 6 | { 7 | public static ILoggerFactory Singleton { get; } = LoggerFactory.Create(l => l.AddDebug()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/Specifications/Context.PrepareDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Microsoft.EntityFrameworkCore.Tests 5 | { 6 | public class PrepareContext : DbContext 7 | { 8 | public class HelloWorld 9 | { 10 | public int Id { get; set; } 11 | public string Description { get; set; } 12 | } 13 | 14 | public string DefaultSchema { get; } 15 | 16 | public PrepareContext(string schema, DbContextOptions options) 17 | : base(options) 18 | { 19 | DefaultSchema = schema; 20 | } 21 | 22 | protected override void OnModelCreating(ModelBuilder modelBuilder) 23 | { 24 | modelBuilder.Entity(entity => 25 | { 26 | entity.ToTable(nameof(HelloWorld) + "_" + DefaultSchema); 27 | }); 28 | } 29 | } 30 | 31 | [CollectionDefinition("DatabaseCollection")] 32 | public abstract class DatabaseCollection : IDisposable 33 | where TFactory : class, IDbContextFactory 34 | { 35 | private readonly TFactory _factory; 36 | 37 | protected DatabaseCollection(TFactory factory) 38 | { 39 | _factory = factory; 40 | using var context = _factory.Create(); 41 | context.Database.EnsureCreated(); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | using var context = _factory.Create(); 47 | context.Database.EnsureDeleted(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Specifications/Context.QueryTestBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; 2 | using System; 3 | using System.Diagnostics; 4 | using Xunit; 5 | 6 | namespace Microsoft.EntityFrameworkCore.Tests 7 | { 8 | [Collection("DatabaseCollection")] 9 | public abstract class QueryTestBase : IClassFixture 10 | where TContext : DbContext 11 | where TFactory : class, IDbContextFactory 12 | { 13 | private readonly TFactory _factory; 14 | 15 | protected const DatabaseProvider V31 = DatabaseProvider.Version_31; 16 | protected const DatabaseProvider V50 = DatabaseProvider.Version_50; 17 | protected const DatabaseProvider V60 = DatabaseProvider.Version_60; 18 | 19 | protected TContext CreateContext() 20 | { 21 | return _factory.Create(); 22 | } 23 | 24 | protected void AssertSql(string sql) 25 | { 26 | _factory.CommandTracer.AssertSql(sql.Replace("{{schema}}", _factory.UniqueId)); 27 | } 28 | 29 | protected void AssertSql(DatabaseProvider version, string sql) 30 | { 31 | #if EFCORE31 32 | if ((version & V31) == V31) AssertSql(sql); 33 | #elif EFCORE50 34 | if ((version & V50) == V50) AssertSql(sql); 35 | #elif EFCORE60 36 | if ((version & V60) == V60) AssertSql(sql); 37 | #endif 38 | } 39 | 40 | protected IDisposable CatchCommand() 41 | { 42 | return _factory.CommandTracer.BeginScope(); 43 | } 44 | 45 | protected QueryTestBase(TFactory factory) 46 | { 47 | _factory = factory; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Specifications/EFCore.Bulk.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 24 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/Specifications/EFCore.Bulk.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 24 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/Specifications/EFCore.Bulk.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 24 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/SqlServer/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.Tests 2 | { 3 | public class SqlServerContextFactory : RelationalContextFactoryBase 4 | where TContext : DbContext 5 | { 6 | protected override string ScriptSplit => "\r\nGO"; 7 | 8 | protected override string DropTableCommand => "DROP TABLE IF EXISTS [{0}]"; 9 | 10 | protected override void Configure(DbContextOptionsBuilder optionsBuilder) 11 | { 12 | optionsBuilder.UseSqlServer( 13 | $"Server=(localdb)\\mssqllocaldb;" + 14 | $"Database=efcorebulktest{Suffix};" + 15 | $"Trusted_Connection=True;" + 16 | $"MultipleActiveResultSets=true", 17 | s => s.UseBulk().UseMathExtensions()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/SqlServer/Context.PrepareDatabase.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | [Collection("DatabaseCollection")] 6 | public class DatabaseCollection : 7 | DatabaseCollection>, 8 | ICollectionFixture 9 | { 10 | public DatabaseCollection() : base(new SqlServerContextFactory()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/SqlServer/EFCore.Bulk.SqlServer.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.SqlServer.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/SqlServer/EFCore.Bulk.SqlServer.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.SqlServer.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/SqlServer/EFCore.Bulk.SqlServer.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.SqlServer.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 26 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/SqlServer/Functional.Delete.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchDelete; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class SqlServerDeleteTest : DeleteTestBase> 6 | { 7 | public SqlServerDeleteTest( 8 | SqlServerContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_ConstantCondition() 14 | { 15 | base.CompiledQuery_ConstantCondition(); 16 | 17 | AssertSql(@" 18 | DELETE [i] 19 | FROM [Item_{{schema}}] AS [i] 20 | WHERE ([i].[ItemId] > 500) AND ([i].[Price] = 3.0) 21 | "); 22 | } 23 | 24 | public override void CompiledQuery_ContainsSomething() 25 | { 26 | base.CompiledQuery_ContainsSomething(); 27 | 28 | AssertSql(@" 29 | DELETE [i] 30 | FROM [Item_{{schema}}] AS [i] 31 | WHERE [i].[Name] IN (N'jyntnytjyntjntnytnt', N'aaa') 32 | "); 33 | } 34 | 35 | public override void CompiledQuery_ParameteredCondition() 36 | { 37 | base.CompiledQuery_ParameteredCondition(); 38 | 39 | AssertSql(@" 40 | DELETE [i] 41 | FROM [Item_{{schema}}] AS [i] 42 | WHERE [i].[Name] = @__nameToDelete 43 | "); 44 | } 45 | 46 | public override void ConstantCondition() 47 | { 48 | base.ConstantCondition(); 49 | 50 | AssertSql(@" 51 | DELETE [i] 52 | FROM [Item_{{schema}}] AS [i] 53 | WHERE ([i].[ItemId] > 500) AND ([i].[Price] = 124.0) 54 | "); 55 | } 56 | 57 | public override void ContainsAndAlsoEqual() 58 | { 59 | base.ContainsAndAlsoEqual(); 60 | 61 | AssertSql(V31, @" 62 | DELETE [i] 63 | FROM [Item_{{schema}}] AS [i] 64 | WHERE [i].[Description] IN (N'info') OR ([i].[Name] = @__nameToDelete_1) 65 | "); 66 | 67 | AssertSql(V50 | V60, @" 68 | DELETE [i] 69 | FROM [Item_{{schema}}] AS [i] 70 | WHERE ([i].[Description] = N'info') OR ([i].[Name] = @__nameToDelete_1) 71 | "); 72 | } 73 | 74 | public override void ContainsSomething() 75 | { 76 | base.ContainsSomething(); 77 | 78 | AssertSql(@" 79 | DELETE [i] 80 | FROM [Item_{{schema}}] AS [i] 81 | WHERE [i].[Description] IN (N'info', N'aaa') 82 | "); 83 | } 84 | 85 | public override void EmptyContains() 86 | { 87 | base.EmptyContains(); 88 | 89 | AssertSql(V31, @" 90 | DELETE [i] 91 | FROM [Item_{{schema}}] AS [i] 92 | WHERE CAST(1 AS bit) = CAST(0 AS bit) 93 | "); 94 | 95 | AssertSql(V50 | V60, @" 96 | DELETE [i] 97 | FROM [Item_{{schema}}] AS [i] 98 | WHERE 0 = 1 99 | "); 100 | } 101 | 102 | public override void ListAny() 103 | { 104 | base.ListAny(); 105 | 106 | AssertSql(V31, @" 107 | DELETE [i] 108 | FROM [Item_{{schema}}] AS [i] 109 | WHERE [i].[Description] IN (N'info') 110 | "); 111 | 112 | AssertSql(V50 | V60, @" 113 | DELETE [i] 114 | FROM [Item_{{schema}}] AS [i] 115 | WHERE [i].[Description] = N'info' 116 | "); 117 | } 118 | 119 | public override void ParameteredCondition() 120 | { 121 | base.ParameteredCondition(); 122 | 123 | AssertSql(@" 124 | DELETE [i] 125 | FROM [Item_{{schema}}] AS [i] 126 | WHERE [i].[Name] = @__nameToDelete_0 127 | "); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/SqlServer/Functional.InsertInto.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchInsertInto; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class SqlServerInsertIntoTest : InsertIntoTestBase> 6 | { 7 | public SqlServerInsertIntoTest( 8 | SqlServerContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalSelectInto() 14 | { 15 | base.CompiledQuery_NormalSelectInto(); 16 | 17 | AssertSql(@" 18 | INSERT INTO [ChangeLog_{{schema}}] ([Description], [ChangedBy], [Audit_IsDeleted]) 19 | SELECT COALESCE([j].[Server], @__hh) + N'666' AS [Description], [j].[CompileError] AS [ChangedBy], CAST(1 AS bit) AS [Audit_IsDeleted] 20 | FROM [Judging_{{schema}}] AS [j] 21 | "); 22 | } 23 | 24 | public override void CompiledQuery_WithAbstractType() 25 | { 26 | base.CompiledQuery_WithAbstractType(); 27 | 28 | AssertSql(@" 29 | INSERT INTO [Person_{{schema}}] ([Name], [Class]) 30 | SELECT [p].[Name], [p].[Subject] AS [Class] 31 | FROM [Person_{{schema}}] AS [p] 32 | WHERE [p].[Discriminator] = N'Student' 33 | "); 34 | } 35 | 36 | public override void CompiledQuery_WithComputedColumn() 37 | { 38 | base.CompiledQuery_WithComputedColumn(); 39 | 40 | AssertSql(V31, @" 41 | INSERT INTO [Document_{{schema}}] ([Content]) 42 | SELECT [d].[Content] + CONVERT(VARCHAR(11), [d].[ContentLength]) AS [Content] 43 | FROM [Document_{{schema}}] AS [d] 44 | "); 45 | 46 | AssertSql(V50, @" 47 | INSERT INTO [Document_{{schema}}] ([Content]) 48 | SELECT [d].[Content] + COALESCE(CONVERT(VARCHAR(11), [d].[ContentLength]), N'') AS [Content] 49 | FROM [Document_{{schema}}] AS [d] 50 | "); 51 | 52 | AssertSql(V60, @" 53 | INSERT INTO [Document_{{schema}}] ([Content]) 54 | SELECT [d].[Content] + COALESCE(CONVERT(varchar(11), [d].[ContentLength]), N'') AS [Content] 55 | FROM [Document_{{schema}}] AS [d] 56 | "); 57 | } 58 | 59 | public override void NormalSelectInto() 60 | { 61 | base.NormalSelectInto(); 62 | 63 | AssertSql(@" 64 | INSERT INTO [ChangeLog_{{schema}}] ([Description], [ChangedBy], [Audit_IsDeleted]) 65 | SELECT COALESCE([j].[Server], @__hh_0) + N'666' AS [Description], [j].[CompileError] AS [ChangedBy], CAST(1 AS bit) AS [Audit_IsDeleted] 66 | FROM [Judging_{{schema}}] AS [j] 67 | "); 68 | } 69 | 70 | public override void WithAbstractType() 71 | { 72 | base.WithAbstractType(); 73 | 74 | AssertSql(@" 75 | INSERT INTO [Person_{{schema}}] ([Name], [Class]) 76 | SELECT [p].[Name], [p].[Subject] AS [Class] 77 | FROM [Person_{{schema}}] AS [p] 78 | WHERE [p].[Discriminator] = N'Student' 79 | "); 80 | } 81 | 82 | public override void WithComputedColumn() 83 | { 84 | base.WithComputedColumn(); 85 | 86 | AssertSql(V31, @" 87 | INSERT INTO [Document_{{schema}}] ([Content]) 88 | SELECT [d].[Content] + CONVERT(VARCHAR(11), [d].[ContentLength]) AS [Content] 89 | FROM [Document_{{schema}}] AS [d] 90 | "); 91 | 92 | AssertSql(V50, @" 93 | INSERT INTO [Document_{{schema}}] ([Content]) 94 | SELECT [d].[Content] + COALESCE(CONVERT(VARCHAR(11), [d].[ContentLength]), N'') AS [Content] 95 | FROM [Document_{{schema}}] AS [d] 96 | "); 97 | 98 | AssertSql(V60, @" 99 | INSERT INTO [Document_{{schema}}] ([Content]) 100 | SELECT [d].[Content] + COALESCE(CONVERT(varchar(11), [d].[ContentLength]), N'') AS [Content] 101 | FROM [Document_{{schema}}] AS [d] 102 | "); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/SqlServer/Functional.UpdateJoin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchUpdateJoin; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class SqlServerUpdateJoinTest : UpdateJoinTestBase> 6 | { 7 | public SqlServerUpdateJoinTest( 8 | SqlServerContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalUpdate() 14 | { 15 | base.CompiledQuery_NormalUpdate(); 16 | 17 | AssertSql(@" 18 | UPDATE [i] 19 | SET [i].[Value] = ([i].[Value] + [t].[Value]) - @__cc 20 | FROM [ItemA_{{schema}}] AS [i] 21 | INNER JOIN ( 22 | SELECT [i0].[Id], [i0].[Value] 23 | FROM [ItemB_{{schema}}] AS [i0] 24 | WHERE [i0].[Value] = @__aa 25 | ) AS [t] ON [i].[Id] = [t].[Id] 26 | WHERE [i].[Id] = @__bb 27 | "); 28 | } 29 | 30 | public override void LocalTableJoin() 31 | { 32 | base.LocalTableJoin(); 33 | 34 | AssertSql(@" 35 | UPDATE [i] 36 | SET [i].[Value] = [i].[Value] + [cte].[Value] 37 | FROM [ItemB_{{schema}}] AS [i] 38 | INNER JOIN ( 39 | VALUES 40 | (@__p_0_0_0, @__p_0_0_1), 41 | (@__p_0_1_0, @__p_0_1_1), 42 | (@__p_0_2_0, @__p_0_2_1) 43 | ) AS [cte] ([Id], [Value]) ON [i].[Id] = [cte].[Id] 44 | WHERE [i].[Id] <> 2 45 | "); 46 | } 47 | 48 | public override void NormalUpdate() 49 | { 50 | base.NormalUpdate(); 51 | 52 | AssertSql(@" 53 | UPDATE [i] 54 | SET [i].[Value] = ([i].[Value] + [t].[Value]) - 3 55 | FROM [ItemA_{{schema}}] AS [i] 56 | INNER JOIN ( 57 | SELECT [i0].[Id], [i0].[Value] 58 | FROM [ItemB_{{schema}}] AS [i0] 59 | WHERE [i0].[Value] = 2 60 | ) AS [t] ON [i].[Id] = [t].[Id] 61 | WHERE [i].[Id] = 1 62 | "); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/Sqlite/Context.Factory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.Sqlite; 2 | using System; 3 | 4 | namespace Microsoft.EntityFrameworkCore.Tests 5 | { 6 | public static class SqliteConnectionHolder 7 | { 8 | public static SqliteConnection Singleton { get; } 9 | = new Func(delegate 10 | { 11 | var connection = new SqliteConnection("Filename=:memory:"); 12 | connection.Open(); 13 | return connection; 14 | }) 15 | .Invoke(); 16 | } 17 | 18 | public class SqliteContextFactory : RelationalContextFactoryBase 19 | where TContext : DbContext 20 | { 21 | protected override string ScriptSplit => ";\r\n\r\n"; 22 | 23 | protected override string DropTableCommand => "DROP TABLE IF EXISTS \"{0}\""; 24 | 25 | protected override void Configure(DbContextOptionsBuilder optionsBuilder) 26 | { 27 | optionsBuilder.UseSqlite( 28 | SqliteConnectionHolder.Singleton, 29 | s => s.UseBulk()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Sqlite/Context.PrepareDatabase.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | [Collection("DatabaseCollection")] 6 | public class DatabaseCollection : 7 | DatabaseCollection>, 8 | ICollectionFixture 9 | { 10 | public DatabaseCollection() : base(new SqliteContextFactory()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/Sqlite/EFCore.Bulk.Sqlite.Tests-3.1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | $(DefineConstants);EFCORE31 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Sqlite.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 27 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/Sqlite/EFCore.Bulk.Sqlite.Tests-5.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE50 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Sqlite.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 27 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/Sqlite/EFCore.Bulk.Sqlite.Tests-6.0.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | $(DefineConstants);EFCORE60 6 | false 7 | Microsoft.EntityFrameworkCore.Bulk.Sqlite.Tests 8 | Microsoft.EntityFrameworkCore.Tests 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <_Parameter1>Microsoft.EntityFrameworkCore.TestUtilities.Xunit.TestPriorityOrderer 27 | <_Parameter2>Microsoft.EntityFrameworkCore.TestUtilities 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/Sqlite/Functional.Delete.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchDelete; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class SqliteDeleteTest : DeleteTestBase> 6 | { 7 | public SqliteDeleteTest( 8 | SqliteContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_ConstantCondition() 14 | { 15 | base.CompiledQuery_ConstantCondition(); 16 | 17 | AssertSql(@" 18 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 19 | WHERE (""i"".""ItemId"" > 500) AND (""i"".""Price"" = '3.0') 20 | "); 21 | } 22 | 23 | public override void CompiledQuery_ContainsSomething() 24 | { 25 | base.CompiledQuery_ContainsSomething(); 26 | 27 | AssertSql(@" 28 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 29 | WHERE ""i"".""Name"" IN ('jyntnytjyntjntnytnt', 'aaa') 30 | "); 31 | } 32 | 33 | public override void CompiledQuery_ParameteredCondition() 34 | { 35 | base.CompiledQuery_ParameteredCondition(); 36 | 37 | AssertSql(@" 38 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 39 | WHERE ""i"".""Name"" = @__nameToDelete 40 | "); 41 | } 42 | 43 | public override void ConstantCondition() 44 | { 45 | base.ConstantCondition(); 46 | 47 | AssertSql(@" 48 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 49 | WHERE (""i"".""ItemId"" > 500) AND (""i"".""Price"" = '124.0') 50 | "); 51 | } 52 | 53 | public override void ContainsAndAlsoEqual() 54 | { 55 | base.ContainsAndAlsoEqual(); 56 | 57 | AssertSql(V31, @" 58 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 59 | WHERE ""i"".""Description"" IN ('info') OR (""i"".""Name"" = @__nameToDelete_1) 60 | "); 61 | 62 | AssertSql(V50 | V60, @" 63 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 64 | WHERE (""i"".""Description"" = 'info') OR (""i"".""Name"" = @__nameToDelete_1) 65 | "); 66 | } 67 | 68 | public override void ContainsSomething() 69 | { 70 | base.ContainsSomething(); 71 | 72 | AssertSql(@" 73 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 74 | WHERE ""i"".""Description"" IN ('info', 'aaa') 75 | "); 76 | } 77 | 78 | public override void EmptyContains() 79 | { 80 | base.EmptyContains(); 81 | 82 | AssertSql(V31, @" 83 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 84 | WHERE 1 = 0 85 | "); 86 | 87 | AssertSql(V50 | V60, @" 88 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 89 | WHERE 0 90 | "); 91 | } 92 | 93 | public override void ListAny() 94 | { 95 | base.ListAny(); 96 | 97 | AssertSql(V31, @" 98 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 99 | WHERE ""i"".""Description"" IN ('info') 100 | "); 101 | 102 | AssertSql(V50 | V60, @" 103 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 104 | WHERE ""i"".""Description"" = 'info' 105 | "); 106 | } 107 | 108 | public override void ParameteredCondition() 109 | { 110 | base.ParameteredCondition(); 111 | 112 | AssertSql(@" 113 | DELETE FROM ""Item_{{schema}}"" AS ""i"" 114 | WHERE ""i"".""Name"" = @__nameToDelete_0 115 | "); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/Sqlite/Functional.InsertInto.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchInsertInto; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class SqliteInsertIntoTest : InsertIntoTestBase> 6 | { 7 | public SqliteInsertIntoTest( 8 | SqliteContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalSelectInto() 14 | { 15 | base.CompiledQuery_NormalSelectInto(); 16 | 17 | AssertSql(@" 18 | INSERT INTO ""ChangeLog_{{schema}}"" (""Description"", ""ChangedBy"", ""Audit_IsDeleted"") 19 | SELECT COALESCE(""j"".""Server"", @__hh) || '666' AS ""Description"", ""j"".""CompileError"" AS ""ChangedBy"", 1 AS ""Audit_IsDeleted"" 20 | FROM ""Judging_{{schema}}"" AS ""j"" 21 | "); 22 | } 23 | 24 | public override void CompiledQuery_WithAbstractType() 25 | { 26 | base.CompiledQuery_WithAbstractType(); 27 | 28 | AssertSql(@" 29 | INSERT INTO ""Person_{{schema}}"" (""Name"", ""Class"") 30 | SELECT ""p"".""Name"", ""p"".""Subject"" AS ""Class"" 31 | FROM ""Person_{{schema}}"" AS ""p"" 32 | WHERE ""p"".""Discriminator"" = 'Student' 33 | "); 34 | } 35 | 36 | public override void NormalSelectInto() 37 | { 38 | base.NormalSelectInto(); 39 | 40 | AssertSql(@" 41 | INSERT INTO ""ChangeLog_{{schema}}"" (""Description"", ""ChangedBy"", ""Audit_IsDeleted"") 42 | SELECT COALESCE(""j"".""Server"", @__hh_0) || '666' AS ""Description"", ""j"".""CompileError"" AS ""ChangedBy"", 1 AS ""Audit_IsDeleted"" 43 | FROM ""Judging_{{schema}}"" AS ""j"" 44 | "); 45 | } 46 | 47 | public override void WithAbstractType() 48 | { 49 | base.WithAbstractType(); 50 | 51 | AssertSql(@" 52 | INSERT INTO ""Person_{{schema}}"" (""Name"", ""Class"") 53 | SELECT ""p"".""Name"", ""p"".""Subject"" AS ""Class"" 54 | FROM ""Person_{{schema}}"" AS ""p"" 55 | WHERE ""p"".""Discriminator"" = 'Student' 56 | "); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/Sqlite/Functional.UpdateJoin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Tests.BatchUpdateJoin; 2 | 3 | namespace Microsoft.EntityFrameworkCore.Tests 4 | { 5 | public class SqliteUpdateJoinTest : UpdateJoinTestBase> 6 | { 7 | public SqliteUpdateJoinTest( 8 | SqliteContextFactory factory) 9 | : base(factory) 10 | { 11 | } 12 | 13 | public override void CompiledQuery_NormalUpdate() 14 | { 15 | base.CompiledQuery_NormalUpdate(); 16 | 17 | AssertSql(@" 18 | UPDATE ""ItemA_{{schema}}"" AS ""i"" 19 | SET ""Value"" = (""i"".""Value"" + ""t"".""Value"") - @__cc 20 | FROM ( 21 | SELECT ""i0"".""Id"", ""i0"".""Value"" 22 | FROM ""ItemB_{{schema}}"" AS ""i0"" 23 | WHERE ""i0"".""Value"" = @__aa 24 | ) AS ""t"" 25 | WHERE (""i"".""Id"" = @__bb) AND (""i"".""Id"" = ""t"".""Id"") 26 | "); 27 | } 28 | 29 | public override void LocalTableJoin() 30 | { 31 | base.LocalTableJoin(); 32 | 33 | AssertSql(@" 34 | WITH ""cte"" (""Id"", ""Value"") AS ( 35 | VALUES 36 | (@__p_0_0_0, @__p_0_0_1), 37 | (@__p_0_1_0, @__p_0_1_1), 38 | (@__p_0_2_0, @__p_0_2_1) 39 | ) 40 | UPDATE ""ItemB_{{schema}}"" AS ""i"" 41 | SET ""Value"" = ""i"".""Value"" + ""cte"".""Value"" 42 | FROM ""cte"" 43 | WHERE (""i"".""Id"" <> 2) AND (""i"".""Id"" = ""cte"".""Id"") 44 | "); 45 | } 46 | 47 | public override void NormalUpdate() 48 | { 49 | base.NormalUpdate(); 50 | 51 | AssertSql(@" 52 | UPDATE ""ItemA_{{schema}}"" AS ""i"" 53 | SET ""Value"" = (""i"".""Value"" + ""t"".""Value"") - 3 54 | FROM ( 55 | SELECT ""i0"".""Id"", ""i0"".""Value"" 56 | FROM ""ItemB_{{schema}}"" AS ""i0"" 57 | WHERE ""i0"".""Value"" = 2 58 | ) AS ""t"" 59 | WHERE (""i"".""Id"" = 1) AND (""i"".""Id"" = ""t"".""Id"") 60 | "); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/TestUtilities/DatabaseProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 4 | { 5 | [Flags] 6 | public enum DatabaseProvider 7 | { 8 | None = 0, 9 | 10 | InMemory_31 = 1 << 0, 11 | InMemory_50 = 1 << 1, 12 | InMemory_60 = 1 << 2, 13 | 14 | SqlServer_31 = 1 << 3, 15 | SqlServer_50 = 1 << 4, 16 | SqlServer_60 = 1 << 5, 17 | 18 | PostgreSQL_31 = 1 << 6, 19 | PostgreSQL_50 = 1 << 7, 20 | PostgreSQL_60 = 1 << 8, 21 | 22 | Sqlite_31 = 1 << 9, 23 | Sqlite_50 = 1 << 10, 24 | Sqlite_60 = 1 << 11, 25 | 26 | MySql_31 = 1 << 12, 27 | MySql_50 = 1 << 13, 28 | MySql_60 = 1 << 14, 29 | 30 | InMemory = InMemory_31 | InMemory_50 | InMemory_60, 31 | SqlServer = SqlServer_31 | SqlServer_50 | SqlServer_60, 32 | PostgreSQL = PostgreSQL_31 | PostgreSQL_50 | PostgreSQL_60, 33 | Sqlite = Sqlite_31 | Sqlite_50 | Sqlite_60, 34 | MySql = MySql_31 | MySql_50 | MySql_60, 35 | 36 | Relational_31 = SqlServer_31 | PostgreSQL_31 | Sqlite_31 | MySql_31, 37 | Relational_50 = SqlServer_50 | PostgreSQL_50 | Sqlite_50 | MySql_50, 38 | Relational_60 = SqlServer_60 | PostgreSQL_60 | Sqlite_60 | MySql_60, 39 | Relational = SqlServer | PostgreSQL | Sqlite | MySql, 40 | 41 | Version_31 = InMemory_31 | Relational_31, 42 | Version_50 = InMemory_50 | Relational_50, 43 | Version_60 = InMemory_60 | Relational_60, 44 | All = InMemory | Relational 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/TestUtilities/DatabaseProviderSkipConditionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xunit.Abstractions; 4 | using Xunit.Sdk; 5 | 6 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 7 | { 8 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] 9 | public sealed class DatabaseProviderSkipConditionAttribute : Attribute, ITestCondition 10 | { 11 | public DatabaseProvider ExcludedProviders { get; } 12 | 13 | public DatabaseProviderSkipConditionAttribute(DatabaseProvider excludedProviders) 14 | { 15 | ExcludedProviders = excludedProviders; 16 | } 17 | 18 | private static DatabaseProvider GetCurrentProvider(ITestMethod method) 19 | { 20 | var dir = method.Method.Type.Assembly.Name; 21 | 22 | var dp = dir.Contains("InMemory", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.InMemory 23 | : dir.Contains("PostgreSql", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.PostgreSQL 24 | : dir.Contains("SqlServer", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.SqlServer 25 | : dir.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.Sqlite 26 | : dir.Contains("MySql", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.MySql 27 | : DatabaseProvider.None; 28 | 29 | var ver = dir.Contains("3.1", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.Version_31 30 | : dir.Contains("5.0", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.Version_50 31 | : dir.Contains("6.0", StringComparison.OrdinalIgnoreCase) ? DatabaseProvider.Version_60 32 | : DatabaseProvider.None; 33 | 34 | return dp & ver; 35 | } 36 | 37 | public string SkipReason { get; set; } = "Test cannot run on this provider."; 38 | 39 | public ValueTask IsMetAsync(XunitTestCase testcase) 40 | { 41 | return new ValueTask( 42 | (GetCurrentProvider(testcase.TestMethod) & ExcludedProviders) == DatabaseProvider.None); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/TestUtilities/EFCore.TestUtilities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | false 6 | Microsoft.EntityFrameworkCore.TestUtilities 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | $(AssemblyName) 17 | $(AssemblyName) 18 | 1.0.0 19 | 1.0.0 20 | 1.0.0.0 21 | 1.0.0.0 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/TestUtilities/TestPriorityAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Xunit 4 | { 5 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 6 | public class TestPriorityAttribute : Attribute 7 | { 8 | public TestPriorityAttribute(int priority) 9 | { 10 | Priority = priority; 11 | } 12 | 13 | public int Priority { get; private set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/TestUtilities/TestPriorityOrderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | using Xunit.Sdk; 7 | 8 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 9 | { 10 | public class TestPriorityOrderer : ITestCaseOrderer 11 | { 12 | public IEnumerable OrderTestCases(IEnumerable testCases) where TTestCase : ITestCase 13 | { 14 | var sortedMethods = new SortedDictionary>(); 15 | 16 | foreach (TTestCase testCase in testCases) 17 | { 18 | int priority = 0; 19 | 20 | foreach (IAttributeInfo attr in testCase.TestMethod.Method.GetCustomAttributes((typeof(TestPriorityAttribute).AssemblyQualifiedName))) 21 | priority = attr.GetNamedArgument("Priority"); 22 | 23 | GetOrCreate(sortedMethods, priority).Add(testCase); 24 | } 25 | 26 | foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority])) 27 | { 28 | list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name)); 29 | foreach (TTestCase testCase in list) 30 | yield return testCase; 31 | } 32 | } 33 | 34 | static TValue GetOrCreate(IDictionary dictionary, TKey key) where TValue : new() 35 | { 36 | TValue result; 37 | 38 | if (dictionary.TryGetValue(key, out result)) return result; 39 | 40 | result = new TValue(); 41 | dictionary[key] = result; 42 | 43 | return result; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ConditionalFactAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Xunit.Sdk; 6 | 7 | // ReSharper disable once CheckNamespace 8 | namespace Xunit 9 | { 10 | [AttributeUsage(AttributeTargets.Method)] 11 | [XunitTestCaseDiscoverer( 12 | "Microsoft.EntityFrameworkCore.TestUtilities.Xunit.ConditionalFactDiscoverer", 13 | "Microsoft.EntityFrameworkCore.TestUtilities")] 14 | public sealed class ConditionalFactAttribute : FactAttribute 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ConditionalFactDiscoverer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | using Xunit.Sdk; 7 | 8 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 9 | { 10 | /// 11 | /// Used dynamically from . 12 | /// Make sure to update that class if you move this type. 13 | /// 14 | public class ConditionalFactDiscoverer : FactDiscoverer 15 | { 16 | public ConditionalFactDiscoverer(IMessageSink messageSink) 17 | : base(messageSink) 18 | { 19 | } 20 | 21 | protected override IXunitTestCase CreateTestCase( 22 | ITestFrameworkDiscoveryOptions discoveryOptions, 23 | ITestMethod testMethod, 24 | IAttributeInfo factAttribute) 25 | => new ConditionalFactTestCase( 26 | DiagnosticMessageSink, 27 | discoveryOptions.MethodDisplayOrDefault(), 28 | discoveryOptions.MethodDisplayOptionsOrDefault(), 29 | testMethod); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ConditionalFactTestCase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit.Abstractions; 8 | using Xunit.Sdk; 9 | 10 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 11 | { 12 | public sealed class ConditionalFactTestCase : XunitTestCase 13 | { 14 | [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] 15 | public ConditionalFactTestCase() 16 | { 17 | } 18 | 19 | public ConditionalFactTestCase( 20 | IMessageSink diagnosticMessageSink, 21 | TestMethodDisplay defaultMethodDisplay, 22 | TestMethodDisplayOptions defaultMethodDisplayOptions, 23 | ITestMethod testMethod, 24 | object[] testMethodArguments = null) 25 | : base( 26 | diagnosticMessageSink, 27 | defaultMethodDisplay, 28 | defaultMethodDisplayOptions, 29 | testMethod, 30 | testMethodArguments) 31 | { 32 | } 33 | 34 | public override async Task RunAsync( 35 | IMessageSink diagnosticMessageSink, 36 | IMessageBus messageBus, 37 | object[] constructorArguments, 38 | ExceptionAggregator aggregator, 39 | CancellationTokenSource cancellationTokenSource) 40 | { 41 | if (await XunitTestCaseExtensions.TrySkipAsync(this, messageBus)) 42 | { 43 | return new RunSummary { Total = 1, Skipped = 1 }; 44 | } 45 | else 46 | { 47 | var runner = new ContextualTestCaseRunner( 48 | this, 49 | DisplayName, 50 | SkipReason, 51 | constructorArguments, 52 | TestMethodArguments, 53 | messageBus, 54 | aggregator, 55 | cancellationTokenSource); 56 | 57 | return await runner.RunAsync(); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ConditionalTheoryAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Xunit.Sdk; 6 | 7 | // ReSharper disable once CheckNamespace 8 | namespace Xunit 9 | { 10 | [AttributeUsage(AttributeTargets.Method)] 11 | [XunitTestCaseDiscoverer( 12 | "Microsoft.EntityFrameworkCore.TestUtilities.Xunit.ConditionalTheoryDiscoverer", 13 | "Microsoft.EntityFrameworkCore.TestUtilities")] 14 | public sealed class ConditionalTheoryAttribute : TheoryAttribute 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ConditionalTheoryDiscoverer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | using Xunit.Sdk; 8 | 9 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 10 | { 11 | /// 12 | /// Used dynamically from . 13 | /// Make sure to update that class if you move this type. 14 | /// 15 | public class ConditionalTheoryDiscoverer : TheoryDiscoverer 16 | { 17 | public ConditionalTheoryDiscoverer(IMessageSink messageSink) 18 | : base(messageSink) 19 | { 20 | } 21 | 22 | protected override IEnumerable CreateTestCasesForTheory( 23 | ITestFrameworkDiscoveryOptions discoveryOptions, 24 | ITestMethod testMethod, 25 | IAttributeInfo theoryAttribute) 26 | { 27 | yield return new ConditionalTheoryTestCase( 28 | DiagnosticMessageSink, 29 | discoveryOptions.MethodDisplayOrDefault(), 30 | discoveryOptions.MethodDisplayOptionsOrDefault(), 31 | testMethod); 32 | } 33 | 34 | protected override IEnumerable CreateTestCasesForDataRow( 35 | ITestFrameworkDiscoveryOptions discoveryOptions, 36 | ITestMethod testMethod, 37 | IAttributeInfo theoryAttribute, 38 | object[] dataRow) 39 | { 40 | yield return new ConditionalFactTestCase( 41 | DiagnosticMessageSink, 42 | discoveryOptions.MethodDisplayOrDefault(), 43 | discoveryOptions.MethodDisplayOptionsOrDefault(), 44 | testMethod, 45 | dataRow); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ConditionalTheoryTestCase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit.Abstractions; 8 | using Xunit.Sdk; 9 | 10 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 11 | { 12 | public sealed class ConditionalTheoryTestCase : XunitTheoryTestCase 13 | { 14 | [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] 15 | public ConditionalTheoryTestCase() 16 | { 17 | } 18 | 19 | public ConditionalTheoryTestCase( 20 | IMessageSink diagnosticMessageSink, 21 | TestMethodDisplay defaultMethodDisplay, 22 | TestMethodDisplayOptions defaultMethodDisplayOptions, 23 | ITestMethod testMethod) 24 | : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) 25 | { 26 | } 27 | 28 | public override async Task RunAsync( 29 | IMessageSink diagnosticMessageSink, 30 | IMessageBus messageBus, 31 | object[] constructorArguments, 32 | ExceptionAggregator aggregator, 33 | CancellationTokenSource cancellationTokenSource) 34 | => await XunitTestCaseExtensions.TrySkipAsync(this, messageBus) 35 | ? new RunSummary { Total = 1, Skipped = 1 } 36 | : await base.RunAsync( 37 | diagnosticMessageSink, 38 | messageBus, 39 | constructorArguments, 40 | aggregator, 41 | cancellationTokenSource); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/ITestCondition.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using Xunit.Sdk; 6 | 7 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 8 | { 9 | public interface ITestCondition 10 | { 11 | ValueTask IsMetAsync(XunitTestCase testcase); 12 | 13 | string SkipReason { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/Output.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Xunit.Abstractions; 3 | 4 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 5 | { 6 | public static class Output 7 | { 8 | private static readonly AsyncLocal asyncLocal; 9 | 10 | static Output() 11 | { 12 | asyncLocal = new AsyncLocal(); 13 | } 14 | 15 | internal static void SetInstance(ITestOutputHelper testOutputHelper) 16 | { 17 | asyncLocal.Value = testOutputHelper; 18 | } 19 | 20 | public static void WriteLine(string message) 21 | { 22 | asyncLocal.Value?.WriteLine(message); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/PlatformSkipConditionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using System.Threading.Tasks; 7 | using Xunit.Sdk; 8 | 9 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 10 | { 11 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] 12 | public sealed class PlatformSkipConditionAttribute : Attribute, ITestCondition 13 | { 14 | private readonly TestPlatform _excludedPlatforms; 15 | 16 | public PlatformSkipConditionAttribute(TestPlatform excludedPlatforms) 17 | { 18 | _excludedPlatforms = excludedPlatforms; 19 | } 20 | 21 | public ValueTask IsMetAsync(XunitTestCase testcase) 22 | => new ValueTask(CanRunOnThisPlatform(_excludedPlatforms)); 23 | 24 | public string SkipReason { get; set; } = "Test cannot run on this platform."; 25 | 26 | private static bool CanRunOnThisPlatform(TestPlatform excludedFrameworks) 27 | { 28 | if (excludedFrameworks == TestPlatform.None) 29 | { 30 | return true; 31 | } 32 | 33 | if (excludedFrameworks.HasFlag(TestPlatform.Windows) 34 | && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 35 | { 36 | return false; 37 | } 38 | 39 | if (excludedFrameworks.HasFlag(TestPlatform.Linux) 40 | && RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 41 | { 42 | return false; 43 | } 44 | 45 | return !excludedFrameworks.HasFlag(TestPlatform.Mac) 46 | || !RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/TestPlatform.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 7 | { 8 | [Flags] 9 | public enum TestPlatform 10 | { 11 | None = 0, 12 | Windows = 1 << 0, 13 | Linux = 1 << 1, 14 | Mac = 1 << 2 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/UseCultureAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.Reflection; 7 | using Xunit.Sdk; 8 | 9 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 10 | { 11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 12 | public sealed class UseCultureAttribute : BeforeAfterTestAttribute 13 | { 14 | private CultureInfo _originalCulture; 15 | private CultureInfo _originalUiCulture; 16 | 17 | public UseCultureAttribute(string culture) 18 | : this(culture, culture) 19 | { 20 | } 21 | 22 | public UseCultureAttribute(string culture, string uiCulture) 23 | { 24 | Culture = new CultureInfo(culture); 25 | UiCulture = new CultureInfo(uiCulture); 26 | } 27 | 28 | public CultureInfo Culture { get; } 29 | 30 | public CultureInfo UiCulture { get; } 31 | 32 | public override void Before(MethodInfo methodUnderTest) 33 | { 34 | _originalCulture = CultureInfo.CurrentCulture; 35 | _originalUiCulture = CultureInfo.CurrentUICulture; 36 | CultureInfo.CurrentCulture = Culture; 37 | CultureInfo.CurrentUICulture = UiCulture; 38 | } 39 | 40 | public override void After(MethodInfo methodUnderTest) 41 | { 42 | CultureInfo.CurrentCulture = _originalCulture; 43 | CultureInfo.CurrentUICulture = _originalUiCulture; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/TestUtilities/Xunit/XunitTestCaseExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Xunit.Abstractions; 10 | using Xunit.Sdk; 11 | 12 | namespace Microsoft.EntityFrameworkCore.TestUtilities.Xunit 13 | { 14 | public static class XunitTestCaseExtensions 15 | { 16 | private static readonly ConcurrentDictionary> _typeAttributes 17 | = new ConcurrentDictionary>(); 18 | 19 | private static readonly ConcurrentDictionary> _assemblyAttributes 20 | = new ConcurrentDictionary>(); 21 | 22 | public static async ValueTask TrySkipAsync(XunitTestCase testCase, IMessageBus messageBus) 23 | { 24 | var method = testCase.Method; 25 | var type = testCase.TestMethod.TestClass.Class; 26 | var assembly = type.Assembly; 27 | 28 | var skipReasons = new List(); 29 | var attributes = 30 | _assemblyAttributes.GetOrAdd( 31 | assembly.Name, 32 | a => assembly.GetCustomAttributes(typeof(ITestCondition)).ToList()) 33 | .Concat( 34 | _typeAttributes.GetOrAdd( 35 | type.Name, 36 | t => type.GetCustomAttributes(typeof(ITestCondition)).ToList())) 37 | .Concat(method.GetCustomAttributes(typeof(ITestCondition))) 38 | .OfType() 39 | .Select(attributeInfo => (ITestCondition)attributeInfo.Attribute); 40 | 41 | foreach (var attribute in attributes) 42 | { 43 | if (!await attribute.IsMetAsync(testCase)) 44 | { 45 | skipReasons.Add(attribute.SkipReason); 46 | } 47 | } 48 | 49 | if (skipReasons.Count > 0) 50 | { 51 | messageBus.QueueMessage( 52 | new TestSkipped(new XunitTest(testCase, testCase.DisplayName), string.Join(Environment.NewLine, skipReasons))); 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | } 59 | } 60 | --------------------------------------------------------------------------------