From 36d6a66ac02b7b04746450b5fc7e00d5e86a9554 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 20 Jul 2024 11:46:56 +0100 Subject: [PATCH 1/2] Move ExecuteUpdate/ExecuteDelete to core so non-relational providers can implement Fixes #31052 --- .../Internal/PrecompiledQueryCodeGenerator.cs | 8 +- .../Query/Internal/QueryLocator.cs | 15 +- .../RelationalQueryableExtensions.cs | 111 ------- .../Properties/RelationalStrings.Designer.cs | 6 - .../Properties/RelationalStrings.resx | 57 ++-- .../Properties/TypeForwards.cs | 1 + .../Query/QuerySqlGenerator.cs | 6 +- ...nslatingExpressionVisitor.ExecuteDelete.cs | 12 +- ...nslatingExpressionVisitor.ExecuteUpdate.cs | 6 +- ...yableMethodTranslatingExpressionVisitor.cs | 18 +- .../Internal/SqlServerQuerySqlGenerator.cs | 6 +- .../EntityFrameworkQueryableExtensions.cs | 115 +++++++- src/EFCore/Properties/CoreStrings.Designer.cs | 6 + src/EFCore/Properties/CoreStrings.resx | 3 + .../NavigationExpandingExpressionVisitor.cs | 7 + .../Query/SetPropertyCalls.cs | 4 +- ...rsInheritanceBulkUpdatesInMemoryFixture.cs | 10 + ...ltersInheritanceBulkUpdatesInMemoryTest.cs | 66 +++++ .../InheritanceBulkUpdatesInMemoryFixture.cs | 19 ++ .../InheritanceBulkUpdatesInMemoryTest.cs | 72 +++++ .../NonSharedModelBulkUpdatesInMemoryTest.cs | 62 ++++ .../NorthwindBulkUpdatesInMemoryFixture.cs | 23 ++ .../NorthwindBulkUpdatesInMemoryTest.cs | 273 ++++++++++++++++++ .../InMemoryComplianceTest.cs | 3 + ...lexTypeBulkUpdatesRelationalFixtureBase.cs | 26 ++ ...omplexTypeBulkUpdatesRelationalTestBase.cs | 35 +++ ...nheritanceBulkUpdatesRelationalTestBase.cs | 48 +++ ...ritanceBulkUpdatesRelationalFixtureBase.cs | 26 ++ ...nheritanceBulkUpdatesRelationalTestBase.cs | 49 ++++ ...haredModelBulkUpdatesRelationalTestBase.cs | 104 +++++++ .../NorthwindBulkUpdatesRelationalFixture.cs | 27 ++ .../NorthwindBulkUpdatesRelationalTestBase.cs | 117 ++++++++ ...PCFiltersInheritanceBulkUpdatesTestBase.cs | 10 +- .../TPCInheritanceBulkUpdatesFixture.cs | 5 +- .../TPCInheritanceBulkUpdatesTestBase.cs | 4 +- .../TPHInheritanceBulkUpdatesFixture.cs | 5 +- .../TPHInheritanceBulkUpdatesTestBase.cs | 11 +- ...PTFiltersInheritanceBulkUpdatesTestBase.cs | 10 +- .../TPTInheritanceBulkUpdatesFixture.cs | 5 +- .../TPTInheritanceBulkUpdatesTestBase.cs | 4 +- .../BulkUpdates/BulkUpdatesTestBase.cs | 5 - .../ComplexTypeBulkUpdatesFixtureBase.cs | 5 +- .../ComplexTypeBulkUpdatesTestBase.cs | 50 ++-- .../FiltersInheritanceBulkUpdatesTestBase.cs | 24 -- .../BulkUpdates/IBulkUpdatesFixtureBase.cs | 0 .../InheritanceBulkUpdatesFixtureBase.cs | 3 +- .../InheritanceBulkUpdatesTestBase.cs | 34 +-- .../NonSharedModelBulkUpdatesTestBase.cs | 89 +----- .../NorthwindBulkUpdatesFixture.cs | 5 +- .../NorthwindBulkUpdatesTestBase.cs | 238 +++++---------- .../TestUtilities/BulkUpdatesAsserter.cs | 65 +++++ .../ComplexTypeBulkUpdatesSqlServerTest.cs | 12 +- .../NonSharedModelBulkUpdatesSqlServerTest.cs | 2 +- .../NorthwindBulkUpdatesSqlServerFixture.cs | 2 +- .../NorthwindBulkUpdatesSqlServerTest.cs | 2 +- ...tersInheritanceBulkUpdatesSqlServerTest.cs | 16 +- .../TPHInheritanceBulkUpdatesSqlServerTest.cs | 14 +- .../ComplexTypeBulkUpdatesSqliteTest.cs | 4 +- .../NonSharedModelBulkUpdatesSqliteTest.cs | 2 +- .../NorthwindBulkUpdatesSqliteFixture.cs | 2 +- .../NorthwindBulkUpdatesSqliteTest.cs | 2 +- ...FiltersInheritanceBulkUpdatesSqliteTest.cs | 16 +- .../TPHInheritanceBulkUpdatesSqliteTest.cs | 14 +- 63 files changed, 1376 insertions(+), 625 deletions(-) rename src/{EFCore.Relational => EFCore}/Query/SetPropertyCalls.cs (95%) create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs create mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalFixtureBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalFixtureBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalFixture.cs create mode 100644 test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/BulkUpdatesTestBase.cs (84%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs (63%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs (85%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs (85%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/IBulkUpdatesFixtureBase.cs (100%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs (83%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/InheritanceBulkUpdatesTestBase.cs (84%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs (77%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/NorthwindBulkUpdatesFixture.cs (67%) rename test/{EFCore.Relational.Specification.Tests => EFCore.Specification.Tests}/BulkUpdates/NorthwindBulkUpdatesTestBase.cs (87%) create mode 100644 test/EFCore.Specification.Tests/TestUtilities/BulkUpdatesAsserter.cs diff --git a/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs b/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs index 52a3752053c..cde91a000ab 100644 --- a/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs +++ b/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs @@ -1066,10 +1066,10 @@ or nameof(EntityFrameworkQueryableExtensions.ToListAsync) method.GetParameters()[1].ParameterType.GenericTypeArguments[0].GenericTypeArguments[1])), // ExecuteDelete/Update behave just like other scalar-returning operators - nameof(RelationalQueryableExtensions.ExecuteDeleteAsync) when method.DeclaringType == typeof(RelationalQueryableExtensions) - => RewriteToSync(typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteDelete))), - nameof(RelationalQueryableExtensions.ExecuteUpdateAsync) when method.DeclaringType == typeof(RelationalQueryableExtensions) - => RewriteToSync(typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdate))), + nameof(EntityFrameworkQueryableExtensions.ExecuteDeleteAsync) when method.DeclaringType == typeof(EntityFrameworkQueryableExtensions) + => RewriteToSync(typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))), + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync) when method.DeclaringType == typeof(EntityFrameworkQueryableExtensions) + => RewriteToSync(typeof(EntityFrameworkQueryableExtensions).GetMethod(nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))), // In the regular case (sync terminating operator which needs to stay in the query tree), simply compose the terminating // operator over the penultimate and return that. diff --git a/src/EFCore.Design/Query/Internal/QueryLocator.cs b/src/EFCore.Design/Query/Internal/QueryLocator.cs index e8a0c13a339..aa2ed595246 100644 --- a/src/EFCore.Design/Query/Internal/QueryLocator.cs +++ b/src/EFCore.Design/Query/Internal/QueryLocator.cs @@ -174,11 +174,11 @@ or nameof(EntityFrameworkQueryableExtensions.SumAsync) or nameof(EntityFrameworkQueryableExtensions.ForEachAsync) when IsOnEfQueryableExtensions(): - case nameof(RelationalQueryableExtensions.ExecuteDelete) - or nameof(RelationalQueryableExtensions.ExecuteUpdate) - or nameof(RelationalQueryableExtensions.ExecuteDeleteAsync) - or nameof(RelationalQueryableExtensions.ExecuteUpdateAsync) - when IsOnEfRelationalQueryableExtensions(): + case nameof(EntityFrameworkQueryableExtensions.ExecuteDelete) + or nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate) + or nameof(EntityFrameworkQueryableExtensions.ExecuteDeleteAsync) + or nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync) + when IsOnEfQueryableExtensions(): if (ProcessQueryCandidate(invocation)) { return; @@ -202,9 +202,6 @@ bool IsOnQueryable() bool IsOnEfQueryableExtensions() => IsOnTypeSymbol(_symbols.EfQueryableExtensions); - bool IsOnEfRelationalQueryableExtensions() - => IsOnTypeSymbol(_symbols.EfRelationalQueryableExtensions); - bool IsOnTypeSymbol(ITypeSymbol typeSymbol) => _semanticModel.GetSymbolInfo(invocation, _cancellationToken).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.OriginalDefinition.Equals(typeSymbol, SymbolEqualityComparer.Default); @@ -342,7 +339,6 @@ private readonly struct Symbols public readonly INamedTypeSymbol IEnumerableOfT; public readonly INamedTypeSymbol Queryable; public readonly INamedTypeSymbol EfQueryableExtensions; - public readonly INamedTypeSymbol EfRelationalQueryableExtensions; // ReSharper restore InconsistentNaming private Symbols(Compilation compilation) @@ -358,7 +354,6 @@ private Symbols(Compilation compilation) IEnumerableOfT = GetTypeSymbolOrThrow("System.Collections.Generic.IEnumerable`1"); Queryable = GetTypeSymbolOrThrow("System.Linq.Queryable"); EfQueryableExtensions = GetTypeSymbolOrThrow("Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions"); - EfRelationalQueryableExtensions = GetTypeSymbolOrThrow("Microsoft.EntityFrameworkCore.RelationalQueryableExtensions"); } public static Symbols Load(Compilation compilation) diff --git a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs index 502c8c8ca93..f9b69bcf54e 100644 --- a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs @@ -272,115 +272,4 @@ internal static readonly MethodInfo AsSplitQueryMethodInfo = typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(AsSplitQuery))!; #endregion - - #region ExecuteDelete - - /// - /// Deletes all database rows for the entity instances which match the LINQ query from the database. - /// - /// - /// - /// This operation executes immediately against the database, rather than being deferred until - /// is called. It also does not interact with the EF change tracker in any way: - /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated - /// to reflect the changes. - /// - /// - /// See Executing bulk operations with EF Core - /// for more information and examples. - /// - /// - /// The source query. - /// The total number of rows deleted in the database. - public static int ExecuteDelete(this IQueryable source) - => source.Provider.Execute(Expression.Call(ExecuteDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression)); - - /// - /// Asynchronously deletes database rows for the entity instances which match the LINQ query from the database. - /// - /// - /// - /// This operation executes immediately against the database, rather than being deferred until - /// is called. It also does not interact with the EF change tracker in any way: - /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated - /// to reflect the changes. - /// - /// - /// See Executing bulk operations with EF Core - /// for more information and examples. - /// - /// - /// The source query. - /// A to observe while waiting for the task to complete. - /// The total number of rows deleted in the database. - public static Task ExecuteDeleteAsync(this IQueryable source, CancellationToken cancellationToken = default) - => source.Provider is IAsyncQueryProvider provider - ? provider.ExecuteAsync>( - Expression.Call(ExecuteDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression), cancellationToken) - : throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync); - - internal static readonly MethodInfo ExecuteDeleteMethodInfo - = typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteDelete))!; - - #endregion - - #region ExecuteUpdate - - /// - /// Updates all database rows for the entity instances which match the LINQ query from the database. - /// - /// - /// - /// This operation executes immediately against the database, rather than being deferred until - /// is called. It also does not interact with the EF change tracker in any way: - /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated - /// to reflect the changes. - /// - /// - /// See Executing bulk operations with EF Core - /// for more information and examples. - /// - /// - /// The source query. - /// A collection of set property statements specifying properties to update. - /// The total number of rows updated in the database. - public static int ExecuteUpdate( - this IQueryable source, - Expression, SetPropertyCalls>> setPropertyCalls) - => source.Provider.Execute( - Expression.Call(ExecuteUpdateMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression, setPropertyCalls)); - - /// - /// Asynchronously updates database rows for the entity instances which match the LINQ query from the database. - /// - /// - /// - /// This operation executes immediately against the database, rather than being deferred until - /// is called. It also does not interact with the EF change tracker in any way: - /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated - /// to reflect the changes. - /// - /// - /// See Executing bulk operations with EF Core - /// for more information and examples. - /// - /// - /// The source query. - /// A collection of set property statements specifying properties to update. - /// A to observe while waiting for the task to complete. - /// The total number of rows updated in the database. - public static Task ExecuteUpdateAsync( - this IQueryable source, - Expression, SetPropertyCalls>> setPropertyCalls, - CancellationToken cancellationToken = default) - => source.Provider is IAsyncQueryProvider provider - ? provider.ExecuteAsync>( - Expression.Call( - ExecuteUpdateMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression, setPropertyCalls), cancellationToken) - : throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync); - - internal static readonly MethodInfo ExecuteUpdateMethodInfo - = typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteUpdate))!; - - #endregion } diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index b174634a215..8fa50e47227 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1619,12 +1619,6 @@ public static string SetOperationsRequireAtLeastOneSideWithValidTypeMapping(obje GetString("SetOperationsRequireAtLeastOneSideWithValidTypeMapping", nameof(setOperationType)), setOperationType); - /// - /// The SetProperty<TProperty> method can only be used within 'ExecuteUpdate' method. - /// - public static string SetPropertyMethodInvoked - => GetString("SetPropertyMethodInvoked"); - /// /// This LINQ query is being executed in split-query mode, and the SQL shown is for the first query to be executed. Additional queries may also be executed depending on the results of the first query. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 1e7a548235a..edccbd9e5aa 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -1,17 +1,17 @@  - @@ -1041,9 +1041,6 @@ A set operation '{setOperationType}' requires valid type mapping for at least one of its sides. - - The SetProperty<TProperty> method can only be used within 'ExecuteUpdate' method. - This LINQ query is being executed in split-query mode, and the SQL shown is for the first query to be executed. Additional queries may also be executed depending on the results of the first query. diff --git a/src/EFCore.Relational/Properties/TypeForwards.cs b/src/EFCore.Relational/Properties/TypeForwards.cs index 7312e1c35c4..8121602abd9 100644 --- a/src/EFCore.Relational/Properties/TypeForwards.cs +++ b/src/EFCore.Relational/Properties/TypeForwards.cs @@ -6,3 +6,4 @@ [assembly: TypeForwardedTo(typeof(AttributeCodeFragment))] [assembly: TypeForwardedTo(typeof(MethodCallCodeFragment))] [assembly: TypeForwardedTo(typeof(NestedClosureCodeFragment))] +[assembly: TypeForwardedTo(typeof(SetPropertyCalls<>))] diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 36a605bdab2..af7ed7eebe7 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -203,7 +203,8 @@ protected override Expression VisitDelete(DeleteExpression deleteExpression) } throw new InvalidOperationException( - RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteDelete))); + RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))); } /// @@ -1540,7 +1541,8 @@ void LiftPredicate(TableExpressionBase joinTable) } throw new InvalidOperationException( - RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate))); + RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))); } /// diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs index 0d7ddc80370..50ff19d5002 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Query; public partial class RelationalQueryableMethodTranslatingExpressionVisitor { /// - /// Translates method + /// Translates method /// over the given source. /// /// The shaped query on which the operator is applied. @@ -29,7 +29,8 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor if (mappingStrategy == RelationalAnnotationNames.TptMappingStrategy) { AddTranslationErrorDetails( - RelationalStrings.ExecuteOperationOnTPT(nameof(RelationalQueryableExtensions.ExecuteDelete), entityType.DisplayName())); + RelationalStrings.ExecuteOperationOnTPT( + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete), entityType.DisplayName())); return null; } @@ -38,7 +39,8 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor { // We allow TPC is it is leaf type AddTranslationErrorDetails( - RelationalStrings.ExecuteOperationOnTPC(nameof(RelationalQueryableExtensions.ExecuteDelete), entityType.DisplayName())); + RelationalStrings.ExecuteOperationOnTPC( + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete), entityType.DisplayName())); return null; } @@ -46,7 +48,7 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor { AddTranslationErrorDetails( RelationalStrings.ExecuteOperationOnEntitySplitting( - nameof(RelationalQueryableExtensions.ExecuteDelete), entityType.DisplayName())); + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete), entityType.DisplayName())); return null; } @@ -97,7 +99,7 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa { AddTranslationErrorDetails( RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator( - nameof(RelationalQueryableExtensions.ExecuteDelete), + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete), entityType.DisplayName())); return null; } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs index 0caec315c1d..2fabecd5762 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs @@ -17,7 +17,7 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor /// /// Translates /// + /// cref="EntityFrameworkQueryableExtensions.ExecuteUpdate{TSource}(IQueryable{TSource}, Expression{Func{SetPropertyCalls{TSource}, SetPropertyCalls{TSource}}})" /> /// method /// over the given source. /// @@ -62,7 +62,7 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor { AddTranslationErrorDetails( RelationalStrings.ExecuteOperationOnTPC( - nameof(RelationalQueryableExtensions.ExecuteUpdate), tpcTablesExpression.EntityType.DisplayName())); + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate), tpcTablesExpression.EntityType.DisplayName())); return null; } @@ -423,7 +423,7 @@ MethodCallExpression mce when mce.TryGetIndexerArguments(RelationalDependencies. { AddTranslationErrorDetails( RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator( - nameof(RelationalQueryableExtensions.ExecuteUpdate), + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate), entityType.DisplayName())); return null; } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 20115136354..44fc1da2904 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -226,11 +226,19 @@ [new FromSqlExpression(alias, sqlQueryRootExpression.Sql, sqlQueryRootExpression } } + private static readonly MethodInfo ExecuteDeleteMethodInfo + = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod( + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))!; + + private static readonly MethodInfo ExecuteUpdateMethodInfo + = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod( + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))!; + /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { var method = methodCallExpression.Method; - if (method.DeclaringType == typeof(RelationalQueryableExtensions)) + if (method.DeclaringType == typeof(EntityFrameworkQueryableExtensions)) { var source = Visit(methodCallExpression.Arguments[0]); if (source is ShapedQueryExpression shapedQueryExpression) @@ -238,15 +246,15 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp var genericMethod = method.IsGenericMethod ? method.GetGenericMethodDefinition() : null; switch (method.Name) { - case nameof(RelationalQueryableExtensions.ExecuteDelete) - when genericMethod == RelationalQueryableExtensions.ExecuteDeleteMethodInfo: + case nameof(EntityFrameworkQueryableExtensions.ExecuteDelete) + when genericMethod == ExecuteDeleteMethodInfo: return TranslateExecuteDelete(shapedQueryExpression) ?? throw new InvalidOperationException( RelationalStrings.NonQueryTranslationFailedWithDetails( methodCallExpression.Print(), TranslationErrorDetails)); - case nameof(RelationalQueryableExtensions.ExecuteUpdate) - when genericMethod == RelationalQueryableExtensions.ExecuteUpdateMethodInfo: + case nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate) + when genericMethod == ExecuteUpdateMethodInfo: return TranslateExecuteUpdate(shapedQueryExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()) ?? throw new InvalidOperationException( RelationalStrings.NonQueryTranslationFailedWithDetails( diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index 717d31dad4f..b33dac3340e 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -95,7 +95,8 @@ protected override Expression VisitDelete(DeleteExpression deleteExpression) } throw new InvalidOperationException( - RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteDelete))); + RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))); } /// @@ -169,7 +170,8 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) } throw new InvalidOperationException( - RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate))); + RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))); } /// diff --git a/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs b/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs index 3dbcaed6b29..665b828fbe4 100644 --- a/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs +++ b/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs @@ -2912,8 +2912,8 @@ source.Provider is EntityQueryProvider /// public static IQueryable TagWithCallSite( this IQueryable source, - [NotParameterized][CallerFilePath] string? filePath = null, - [NotParameterized][CallerLineNumber] int lineNumber = 0) + [NotParameterized] [CallerFilePath] string? filePath = null, + [NotParameterized] [CallerLineNumber] int lineNumber = 0) => source.Provider is EntityQueryProvider ? source.Provider.CreateQuery( Expression.Call( @@ -3266,4 +3266,115 @@ private static TResult ExecuteAsync( operatorMethodInfo, source, (Expression?)null, cancellationToken); #endregion + + #region ExecuteDelete + + /// + /// Deletes all database rows for the entity instances which match the LINQ query from the database. + /// + /// + /// + /// This operation executes immediately against the database, rather than being deferred until + /// is called. It also does not interact with the EF change tracker in any way: + /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated + /// to reflect the changes. + /// + /// + /// See Executing bulk operations with EF Core + /// for more information and examples. + /// + /// + /// The source query. + /// The total number of rows deleted in the database. + public static int ExecuteDelete(this IQueryable source) + => source.Provider.Execute(Expression.Call(ExecuteDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression)); + + /// + /// Asynchronously deletes database rows for the entity instances which match the LINQ query from the database. + /// + /// + /// + /// This operation executes immediately against the database, rather than being deferred until + /// is called. It also does not interact with the EF change tracker in any way: + /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated + /// to reflect the changes. + /// + /// + /// See Executing bulk operations with EF Core + /// for more information and examples. + /// + /// + /// The source query. + /// A to observe while waiting for the task to complete. + /// The total number of rows deleted in the database. + public static Task ExecuteDeleteAsync(this IQueryable source, CancellationToken cancellationToken = default) + => source.Provider is IAsyncQueryProvider provider + ? provider.ExecuteAsync>( + Expression.Call(ExecuteDeleteMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression), cancellationToken) + : throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync); + + internal static readonly MethodInfo ExecuteDeleteMethodInfo + = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteDelete))!; + + #endregion + + #region ExecuteUpdate + + /// + /// Updates all database rows for the entity instances which match the LINQ query from the database. + /// + /// + /// + /// This operation executes immediately against the database, rather than being deferred until + /// is called. It also does not interact with the EF change tracker in any way: + /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated + /// to reflect the changes. + /// + /// + /// See Executing bulk operations with EF Core + /// for more information and examples. + /// + /// + /// The source query. + /// A collection of set property statements specifying properties to update. + /// The total number of rows updated in the database. + public static int ExecuteUpdate( + this IQueryable source, + Expression, SetPropertyCalls>> setPropertyCalls) + => source.Provider.Execute( + Expression.Call(ExecuteUpdateMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression, setPropertyCalls)); + + /// + /// Asynchronously updates database rows for the entity instances which match the LINQ query from the database. + /// + /// + /// + /// This operation executes immediately against the database, rather than being deferred until + /// is called. It also does not interact with the EF change tracker in any way: + /// entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated + /// to reflect the changes. + /// + /// + /// See Executing bulk operations with EF Core + /// for more information and examples. + /// + /// + /// The source query. + /// A collection of set property statements specifying properties to update. + /// A to observe while waiting for the task to complete. + /// The total number of rows updated in the database. + public static Task ExecuteUpdateAsync( + this IQueryable source, + Expression, SetPropertyCalls>> setPropertyCalls, + CancellationToken cancellationToken = default) + => source.Provider is IAsyncQueryProvider provider + ? provider.ExecuteAsync>( + Expression.Call( + ExecuteUpdateMethodInfo.MakeGenericMethod(typeof(TSource)), source.Expression, setPropertyCalls), cancellationToken) + : throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync); + + internal static readonly MethodInfo ExecuteUpdateMethodInfo + = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ExecuteUpdate))!; + + #endregion } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 317fe454ffa..ba1e675ecc3 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2907,6 +2907,12 @@ public static string ServiceProviderConfigRemoved(object? key) public static string SetOperationWithDifferentIncludesInOperands => GetString("SetOperationWithDifferentIncludesInOperands"); + /// + /// The SetProperty<TProperty> method can only be used within 'ExecuteUpdate' method. + /// + public static string SetPropertyMethodInvoked + => GetString("SetPropertyMethodInvoked"); + /// /// The shared-type entity type '{entityType}' cannot have a base type. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index eb2d6b4c5ac..f688153f94b 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1558,6 +1558,9 @@ Unable to translate set operation since both operands have different 'Include' operations. Consider having same 'Include' applied on both sides. + + The SetProperty<TProperty> method can only be used within 'ExecuteUpdate' method. + The shared-type entity type '{entityType}' cannot have a base type. diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index 45b85cf0962..f7e8de8764c 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -555,6 +555,13 @@ when QueryableMethods.IsSumWithSelector(method): thenInclude: false, setLoaded: false); + // Handled by relational/provider even though method is on `EntityFrameworkQueryableExtensions` + case nameof(EntityFrameworkQueryableExtensions.ExecuteDelete): + case nameof(EntityFrameworkQueryableExtensions.ExecuteDeleteAsync): + case nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate): + case nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync): + return ProcessUnknownMethod(methodCallExpression); + case nameof(Queryable.GroupBy) when genericMethod == QueryableMethods.GroupByWithKeySelector: return ProcessGroupBy( diff --git a/src/EFCore.Relational/Query/SetPropertyCalls.cs b/src/EFCore/Query/SetPropertyCalls.cs similarity index 95% rename from src/EFCore.Relational/Query/SetPropertyCalls.cs rename to src/EFCore/Query/SetPropertyCalls.cs index 6508452059c..d525aa8184f 100644 --- a/src/EFCore.Relational/Query/SetPropertyCalls.cs +++ b/src/EFCore/Query/SetPropertyCalls.cs @@ -40,7 +40,7 @@ private SetPropertyCalls() public SetPropertyCalls SetProperty( Func propertyExpression, Func valueExpression) - => throw new InvalidOperationException(RelationalStrings.SetPropertyMethodInvoked); + => throw new InvalidOperationException(CoreStrings.SetPropertyMethodInvoked); /// /// Specifies a property and corresponding value it should be updated to in ExecuteUpdate method. @@ -55,7 +55,7 @@ public SetPropertyCalls SetProperty( public SetPropertyCalls SetProperty( Func propertyExpression, TProperty valueExpression) - => throw new InvalidOperationException(RelationalStrings.SetPropertyMethodInvoked); + => throw new InvalidOperationException(CoreStrings.SetPropertyMethodInvoked); #region Hidden System.Object members diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs new file mode 100644 index 00000000000..4936a20c9ab --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class FiltersInheritanceBulkUpdatesInMemoryFixture : InheritanceBulkUpdatesInMemoryFixture +{ + public override bool EnableFilters + => true; +} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs new file mode 100644 index 00000000000..10749c47e34 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class FiltersInheritanceBulkUpdatesInMemoryTest(FiltersInheritanceBulkUpdatesInMemoryFixture fixture) + : FiltersInheritanceBulkUpdatesTestBase(fixture) +{ + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + public override Task Delete_where_hierarchy(bool async) + => AssertTranslationFailed(() => base.Delete_where_hierarchy(async)); + + public override Task Delete_where_hierarchy_subquery(bool async) + => AssertTranslationFailed(() => base.Delete_where_hierarchy_subquery(async)); + + public override Task Delete_where_hierarchy_derived(bool async) + => AssertTranslationFailed(() => base.Delete_where_hierarchy_derived(async)); + + public override Task Delete_where_using_hierarchy(bool async) + => AssertTranslationFailed(() => base.Delete_where_using_hierarchy(async)); + + public override Task Delete_where_using_hierarchy_derived(bool async) + => AssertTranslationFailed(() => base.Delete_where_using_hierarchy_derived(async)); + + public override Task Delete_GroupBy_Where_Select_First(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First(async)); + + public override Task Delete_GroupBy_Where_Select_First_2(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_2(async)); + + public override Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_3(async)); + + public override Task Update_base_type(bool async) + => AssertTranslationFailed(() => base.Update_base_type(async)); + + public override Task Update_base_type_with_OfType(bool async) + => AssertTranslationFailed(() => base.Update_base_type_with_OfType(async)); + + public override Task Update_where_hierarchy_subquery(bool async) + => AssertTranslationFailed(() => base.Update_where_hierarchy_subquery(async)); + + public override Task Update_base_property_on_derived_type(bool async) + => AssertTranslationFailed(() => base.Update_base_property_on_derived_type(async)); + + public override Task Update_derived_property_on_derived_type(bool async) + => AssertTranslationFailed(() => base.Update_derived_property_on_derived_type(async)); + + public override Task Update_base_and_derived_types(bool async) + => AssertTranslationFailed(() => base.Update_base_and_derived_types(async)); + + public override Task Update_where_using_hierarchy(bool async) + => AssertTranslationFailed(() => base.Update_where_using_hierarchy(async)); + + public override Task Update_where_using_hierarchy_derived(bool async) + => AssertTranslationFailed(() => base.Update_where_using_hierarchy_derived(async)); + + protected static async Task AssertTranslationFailed(Func query) + => Assert.Contains( + CoreStrings.TranslationFailed("")[48..], + (await Assert.ThrowsAsync(query)) + .Message); +} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs new file mode 100644 index 00000000000..64ab5c7d1d6 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class InheritanceBulkUpdatesInMemoryFixture : InheritanceBulkUpdatesFixtureBase +{ + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + // TODO: Fake transactions needed for real tests. + } + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder) + .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); +} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs new file mode 100644 index 00000000000..dc37c9a09b1 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class InheritanceBulkUpdatesInMemoryTest(InheritanceBulkUpdatesInMemoryFixture fixture) + : InheritanceBulkUpdatesTestBase(fixture) +{ + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + public override Task Delete_where_hierarchy(bool async) + => AssertTranslationFailed(() => base.Delete_where_hierarchy(async)); + + public override Task Delete_where_hierarchy_subquery(bool async) + => AssertTranslationFailed(() => base.Delete_where_hierarchy_subquery(async)); + + public override Task Delete_where_hierarchy_derived(bool async) + => AssertTranslationFailed(() => base.Delete_where_hierarchy_derived(async)); + + public override Task Delete_where_using_hierarchy(bool async) + => AssertTranslationFailed(() => base.Delete_where_using_hierarchy(async)); + + public override Task Delete_where_using_hierarchy_derived(bool async) + => AssertTranslationFailed(() => base.Delete_where_using_hierarchy_derived(async)); + + public override Task Delete_GroupBy_Where_Select_First(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First(async)); + + public override Task Delete_GroupBy_Where_Select_First_2(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_2(async)); + + public override Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_3(async)); + + public override Task Update_base_type(bool async) + => AssertTranslationFailed(() => base.Update_base_type(async)); + + public override Task Update_base_type_with_OfType(bool async) + => AssertTranslationFailed(() => base.Update_base_type_with_OfType(async)); + + public override Task Update_where_hierarchy_subquery(bool async) + => AssertTranslationFailed(() => base.Update_where_hierarchy_subquery(async)); + + public override Task Update_base_property_on_derived_type(bool async) + => AssertTranslationFailed(() => base.Update_base_property_on_derived_type(async)); + + public override Task Update_derived_property_on_derived_type(bool async) + => AssertTranslationFailed(() => base.Update_derived_property_on_derived_type(async)); + + public override Task Update_base_and_derived_types(bool async) + => AssertTranslationFailed(() => base.Update_base_and_derived_types(async)); + + public override Task Update_where_using_hierarchy(bool async) + => AssertTranslationFailed(() => base.Update_where_using_hierarchy(async)); + + public override Task Update_where_using_hierarchy_derived(bool async) + => AssertTranslationFailed(() => base.Update_where_using_hierarchy_derived(async)); + + public override Task Update_with_interface_in_property_expression(bool async) + => AssertTranslationFailed(() => base.Update_with_interface_in_property_expression(async)); + + public override Task Update_with_interface_in_EF_Property_in_property_expression(bool async) + => AssertTranslationFailed(() => base.Update_with_interface_in_EF_Property_in_property_expression(async)); + + protected static async Task AssertTranslationFailed(Func query) + => Assert.Contains( + CoreStrings.TranslationFailed("")[48..], + (await Assert.ThrowsAsync(query)) + .Message); +} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs new file mode 100644 index 00000000000..c8f541f6c58 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class NonSharedModelBulkUpdatesInMemoryTest : NonSharedModelBulkUpdatesTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + // TODO: Fake transactions needed for real tests. + } + + protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder) + .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + public override Task Delete_aggregate_root_when_eager_loaded_owned_collection(bool async) + => AssertTranslationFailed(() => base.Delete_aggregate_root_when_eager_loaded_owned_collection(async)); + + public override Task Delete_with_owned_collection_and_non_natively_translatable_query(bool async) + => AssertTranslationFailed(() => base.Delete_with_owned_collection_and_non_natively_translatable_query(async)); + + public override Task Delete_aggregate_root_when_table_sharing_with_owned(bool async) + => AssertTranslationFailed(() => base.Delete_aggregate_root_when_table_sharing_with_owned(async)); + + public override Task Replace_ColumnExpression_in_column_setter(bool async) + => AssertTranslationFailed(() => base.Replace_ColumnExpression_in_column_setter(async)); + + public override Task Update_non_owned_property_on_entity_with_owned(bool async) + => AssertTranslationFailed(() => base.Update_non_owned_property_on_entity_with_owned(async)); + + public override Task Update_non_owned_property_on_entity_with_owned2(bool async) + => AssertTranslationFailed(() => base.Update_non_owned_property_on_entity_with_owned2(async)); + + public override Task Update_non_owned_property_on_entity_with_owned_in_join(bool async) + => AssertTranslationFailed(() => base.Update_non_owned_property_on_entity_with_owned_in_join(async)); + + public override Task Update_owned_and_non_owned_properties_with_table_sharing(bool async) + => AssertTranslationFailed(() => base.Update_owned_and_non_owned_properties_with_table_sharing(async)); + + public override Task Delete_entity_with_auto_include(bool async) + => AssertTranslationFailed(() => base.Delete_entity_with_auto_include(async)); + + public override Task Delete_predicate_based_on_optional_navigation(bool async) + => AssertTranslationFailed(() => base.Delete_predicate_based_on_optional_navigation(async)); + + public override Task Update_with_alias_uniquification_in_setter_subquery(bool async) + => AssertTranslationFailed(() => base.Update_with_alias_uniquification_in_setter_subquery(async)); + + protected static async Task AssertTranslationFailed(Func query) + => Assert.Contains( + CoreStrings.TranslationFailed("")[48..], + (await Assert.ThrowsAsync(query)) + .Message); +} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs new file mode 100644 index 00000000000..56a0c01f595 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class NorthwindBulkUpdatesInMemoryFixture : NorthwindBulkUpdatesFixture + where TModelCustomizer : ITestModelCustomizer, new() +{ + protected override ITestStoreFactory TestStoreFactory + => InMemoryTestStoreFactory.Instance; + + protected override Type ContextType + => typeof(NorthwindInMemoryContext); + + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + { + // TODO: Fake transactions needed for real tests. + } + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder) + .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); +} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs new file mode 100644 index 00000000000..37c74c32f87 --- /dev/null +++ b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs @@ -0,0 +1,273 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +public class NorthwindBulkUpdatesInMemoryTest( + NorthwindBulkUpdatesInMemoryFixture fixture) + : NorthwindBulkUpdatesTestBase>(fixture) +{ + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + public override Task Delete_non_entity_projection(bool async) + => AssertTranslationFailed(() => base.Delete_non_entity_projection(async)); + + public override Task Delete_non_entity_projection_2(bool async) + => AssertTranslationFailed(() => base.Delete_non_entity_projection_2(async)); + + public override Task Delete_non_entity_projection_3(bool async) + => AssertTranslationFailed(() => base.Delete_non_entity_projection_3(async)); + + public override Task Update_without_property_to_set_throws(bool async) + => AssertTranslationFailed(() => base.Update_without_property_to_set_throws(async)); + + public override Task Update_with_invalid_lambda_throws(bool async) + => AssertTranslationFailed(() => base.Update_with_invalid_lambda_throws(async)); + + public override Task Update_with_invalid_lambda_in_set_property_throws(bool async) + => AssertTranslationFailed(() => base.Update_with_invalid_lambda_in_set_property_throws(async)); + + public override Task Update_multiple_tables_throws(bool async) + => AssertTranslationFailed(() => base.Update_multiple_tables_throws(async)); + + public override Task Update_unmapped_property_throws(bool async) + => AssertTranslationFailed(() => base.Update_unmapped_property_throws(async)); + + public override Task Delete_Where_TagWith(bool async) + => AssertTranslationFailed(() => base.Delete_Where_TagWith(async)); + + public override Task Delete_Where(bool async) + => AssertTranslationFailed(() => base.Delete_Where(async)); + + public override Task Delete_Where_parameter(bool async) + => AssertTranslationFailed(() => base.Delete_Where_parameter(async)); + + public override Task Delete_Where_OrderBy(bool async) + => AssertTranslationFailed(() => base.Delete_Where_OrderBy(async)); + + public override Task Delete_Where_OrderBy_Skip(bool async) + => AssertTranslationFailed(() => base.Delete_Where_OrderBy_Skip(async)); + + public override Task Delete_Where_OrderBy_Take(bool async) + => AssertTranslationFailed(() => base.Delete_Where_OrderBy_Take(async)); + + public override Task Delete_Where_OrderBy_Skip_Take(bool async) + => AssertTranslationFailed(() => base.Delete_Where_OrderBy_Skip_Take(async)); + + public override Task Delete_Where_Skip(bool async) + => AssertTranslationFailed(() => base.Delete_Where_Skip(async)); + + public override Task Delete_Where_Take(bool async) + => AssertTranslationFailed(() => base.Delete_Where_Take(async)); + + public override Task Delete_Where_Skip_Take(bool async) + => AssertTranslationFailed(() => base.Delete_Where_Skip_Take(async)); + + public override Task Delete_Where_predicate_with_GroupBy_aggregate(bool async) + => AssertTranslationFailed(() => base.Delete_Where_predicate_with_GroupBy_aggregate(async)); + + public override Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) + => AssertTranslationFailed(() => base.Delete_Where_predicate_with_GroupBy_aggregate_2(async)); + + public override Task Delete_GroupBy_Where_Select(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select(async)); + + public override Task Delete_GroupBy_Where_Select_2(bool async) + => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_2(async)); + + public override Task Delete_Where_Skip_Take_Skip_Take_causing_subquery(bool async) + => AssertTranslationFailed(() => base.Delete_Where_Skip_Take_Skip_Take_causing_subquery(async)); + + public override Task Delete_Where_Distinct(bool async) + => AssertTranslationFailed(() => base.Delete_Where_Distinct(async)); + + public override Task Delete_SelectMany(bool async) + => AssertTranslationFailed(() => base.Delete_SelectMany(async)); + + public override Task Delete_SelectMany_subquery(bool async) + => AssertTranslationFailed(() => base.Delete_SelectMany_subquery(async)); + + public override Task Delete_Where_using_navigation(bool async) + => AssertTranslationFailed(() => base.Delete_Where_using_navigation(async)); + + public override Task Delete_Where_using_navigation_2(bool async) + => AssertTranslationFailed(() => base.Delete_Where_using_navigation_2(async)); + + public override Task Delete_Union(bool async) + => AssertTranslationFailed(() => base.Delete_Union(async)); + + public override Task Delete_Concat(bool async) + => AssertTranslationFailed(() => base.Delete_Concat(async)); + + public override Task Delete_Intersect(bool async) + => AssertTranslationFailed(() => base.Delete_Intersect(async)); + + public override Task Delete_Except(bool async) + => AssertTranslationFailed(() => base.Delete_Except(async)); + + public override Task Delete_Where_optional_navigation_predicate(bool async) + => AssertTranslationFailed(() => base.Delete_Where_optional_navigation_predicate(async)); + + public override Task Delete_with_join(bool async) + => AssertTranslationFailed(() => base.Delete_with_join(async)); + + public override Task Delete_with_left_join(bool async) + => AssertTranslationFailed(() => base.Delete_with_left_join(async)); + + public override Task Delete_with_cross_join(bool async) + => AssertTranslationFailed(() => base.Delete_with_cross_join(async)); + + public override Task Delete_with_cross_apply(bool async) + => AssertTranslationFailed(() => base.Delete_with_cross_apply(async)); + + public override Task Delete_with_outer_apply(bool async) + => AssertTranslationFailed(() => base.Delete_with_outer_apply(async)); + + public override Task Update_Where_set_constant_TagWith(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_constant_TagWith(async)); + + public override Task Update_Where_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_constant(async)); + + public override Task Update_Where_parameter_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_parameter_set_constant(async)); + + public override Task Update_Where_set_parameter(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_parameter(async)); + + public override Task Update_Where_set_parameter_from_closure_array(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_parameter_from_closure_array(async)); + + public override Task Update_Where_set_parameter_from_inline_list(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_parameter_from_inline_list(async)); + + public override Task Update_Where_set_parameter_from_multilevel_property_access(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_parameter_from_multilevel_property_access(async)); + + public override Task Update_Where_Skip_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_Skip_set_constant(async)); + + public override Task Update_Where_Take_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_Take_set_constant(async)); + + public override Task Update_Where_Skip_Take_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_Skip_Take_set_constant(async)); + + public override Task Update_Where_OrderBy_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_OrderBy_set_constant(async)); + + public override Task Update_Where_OrderBy_Skip_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_OrderBy_Skip_set_constant(async)); + + public override Task Update_Where_OrderBy_Take_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_OrderBy_Take_set_constant(async)); + + public override Task Update_Where_OrderBy_Skip_Take_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_OrderBy_Skip_Take_set_constant(async)); + + public override Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(async)); + + public override Task Update_Where_GroupBy_aggregate_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_GroupBy_aggregate_set_constant(async)); + + public override Task Update_Where_GroupBy_First_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_GroupBy_First_set_constant(async)); + + public override Task Update_Where_GroupBy_First_set_constant_2(bool async) + => AssertTranslationFailed(() => base.Update_Where_GroupBy_First_set_constant_2(async)); + + public override Task Update_Where_GroupBy_First_set_constant_3(bool async) + // Translation of EF.Property fails before getting to ExecuteUpdate. + => Assert.ThrowsAsync( + () => base.Update_Where_GroupBy_First_set_constant_3(async)); + + public override Task Update_Where_Distinct_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_Distinct_set_constant(async)); + + public override Task Update_Where_using_navigation_set_null(bool async) + => AssertTranslationFailed(() => base.Update_Where_using_navigation_set_null(async)); + + public override Task Update_Where_using_navigation_2_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_using_navigation_2_set_constant(async)); + + public override Task Update_Where_SelectMany_set_null(bool async) + => AssertTranslationFailed(() => base.Update_Where_SelectMany_set_null(async)); + + public override Task Update_Where_set_property_plus_constant(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_property_plus_constant(async)); + + public override Task Update_Where_set_property_plus_parameter(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_property_plus_parameter(async)); + + public override Task Update_Where_set_property_plus_property(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_property_plus_property(async)); + + public override Task Update_Where_set_constant_using_ef_property(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_constant_using_ef_property(async)); + + public override Task Update_Where_set_null(bool async) + => AssertTranslationFailed(() => base.Update_Where_set_null(async)); + + public override Task Update_Where_multiple_set(bool async) + => AssertTranslationFailed(() => base.Update_Where_multiple_set(async)); + + public override Task Update_Union_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Union_set_constant(async)); + + public override Task Update_Concat_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Concat_set_constant(async)); + + public override Task Update_Except_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Except_set_constant(async)); + + public override Task Update_Intersect_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_Intersect_set_constant(async)); + + public override Task Update_with_join_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_join_set_constant(async)); + + public override Task Update_with_left_join_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_left_join_set_constant(async)); + + public override Task Update_with_cross_join_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_cross_join_set_constant(async)); + + public override Task Update_with_cross_apply_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_cross_apply_set_constant(async)); + + public override Task Update_with_outer_apply_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_outer_apply_set_constant(async)); + + public override Task Update_with_cross_join_left_join_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_cross_join_left_join_set_constant(async)); + + public override Task Update_with_cross_join_cross_apply_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_cross_join_cross_apply_set_constant(async)); + + public override Task Update_with_cross_join_outer_apply_set_constant(bool async) + => AssertTranslationFailed(() => base.Update_with_cross_join_outer_apply_set_constant(async)); + + public override Task Update_Where_SelectMany_subquery_set_null(bool async) + => AssertTranslationFailed(() => base.Update_Where_SelectMany_subquery_set_null(async)); + + public override Task Update_Where_Join_set_property_from_joined_single_result_table(bool async) + => AssertTranslationFailed(() => base.Update_Where_Join_set_property_from_joined_single_result_table(async)); + + public override Task Update_Where_Join_set_property_from_joined_table(bool async) + => AssertTranslationFailed(() => base.Update_Where_Join_set_property_from_joined_table(async)); + + public override Task Update_Where_Join_set_property_from_joined_single_result_scalar(bool async) + => AssertTranslationFailed(() => base.Update_Where_Join_set_property_from_joined_single_result_scalar(async)); + + public override Task Update_with_two_inner_joins(bool async) + => AssertTranslationFailed(() => base.Update_with_two_inner_joins(async)); + + protected static async Task AssertTranslationFailed(Func query) + => Assert.Contains( + CoreStrings.TranslationFailed("")[48..], + (await Assert.ThrowsAsync(query)) + .Message); +} diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index 666a22e8a8e..96fe0d54875 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.BulkUpdates; + namespace Microsoft.EntityFrameworkCore; public class InMemoryComplianceTest : ComplianceTestBase @@ -16,6 +18,7 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(StoreGeneratedTestBase<>), typeof(ConferencePlannerTestBase<>), typeof(ManyToManyQueryTestBase<>), + typeof(ComplexTypeBulkUpdatesTestBase<>), }; protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalFixtureBase.cs new file mode 100644 index 00000000000..8337d339a05 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalFixtureBase.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class ComplexTypeBulkUpdatesRelationalFixtureBase : ComplexTypeBulkUpdatesFixtureBase, ITestSqlLoggerFactory +{ + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public new RelationalTestStore TestStore + => (RelationalTestStore)base.TestStore; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings( + c => c.Log(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning)) + .EnableDetailedErrors(); + + protected override bool ShouldLogCategory(string logCategory) + => logCategory == DbLoggerCategory.Query.Name; +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs new file mode 100644 index 00000000000..5eff7e3ee4c --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class ComplexTypeBulkUpdatesRelationalTestBase : ComplexTypeBulkUpdatesTestBase + where TFixture : ComplexTypeBulkUpdatesRelationalFixtureBase, new() +{ + protected ComplexTypeBulkUpdatesRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + ClearLog(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override Task Delete_complex_type(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteDeleteOnNonEntityType, + () => base.Delete_complex_type(async)); + + public override Task Update_projected_complex_type_via_OrderBy_Skip(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteUpdateSubqueryNotSupportedOverComplexTypes("Customer.ShippingAddress#Address"), + () => base.Update_projected_complex_type_via_OrderBy_Skip(async)); + + protected static async Task AssertTranslationFailed(string details, Func query) + => Assert.Contains( + RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + (await Assert.ThrowsAsync(query)).Message); + + private void ClearLog() + => Fixture.TestSqlLoggerFactory.Clear(); +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs new file mode 100644 index 00000000000..9825d26ae50 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel; + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class FiltersInheritanceBulkUpdatesRelationalTestBase : FiltersInheritanceBulkUpdatesTestBase + where TFixture : InheritanceBulkUpdatesRelationalFixtureBase, new() +{ + protected FiltersInheritanceBulkUpdatesRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + ClearLog(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteDelete", "EagleQuery"), + () => AssertDelete( + async, + ss => ss.Set().Where(e => e.CountryId > 0), + rowsAffectedCount: 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteUpdate", "EagleQuery"), + () => AssertUpdate( + async, + ss => ss.Set().Where(e => e.CountryId > 0), + e => e, + s => s.SetProperty(e => e.Name, "Eagle"), + rowsAffectedCount: 1)); + + protected static async Task AssertTranslationFailed(string details, Func query) + => Assert.Contains( + RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + (await Assert.ThrowsAsync(query)).Message); + + protected abstract void ClearLog(); +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalFixtureBase.cs new file mode 100644 index 00000000000..8517405afb8 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalFixtureBase.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class InheritanceBulkUpdatesRelationalFixtureBase : InheritanceBulkUpdatesFixtureBase, ITestSqlLoggerFactory +{ + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public new RelationalTestStore TestStore + => (RelationalTestStore)base.TestStore; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings( + c => c.Log(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning)) + .EnableDetailedErrors(); + + protected override bool ShouldLogCategory(string logCategory) + => logCategory == DbLoggerCategory.Query.Name; +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs new file mode 100644 index 00000000000..44a60ff868f --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel; + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class InheritanceBulkUpdatesRelationalTestBase : InheritanceBulkUpdatesTestBase + where TFixture : InheritanceBulkUpdatesRelationalFixtureBase, new() +{ + protected InheritanceBulkUpdatesRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + ClearLog(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteDelete", "EagleQuery"), + () => AssertDelete( + async, + ss => ss.Set().Where(e => e.CountryId > 0), + rowsAffectedCount: 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteUpdate", "EagleQuery"), + () => AssertUpdate( + async, + ss => ss.Set().Where(e => e.CountryId > 0), + e => e, + s => s.SetProperty(e => e.Name, "Eagle"), + rowsAffectedCount: 1)); + + protected static async Task AssertTranslationFailed(string details, Func query) + => Assert.Contains( + RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + (await Assert.ThrowsAsync(query)).Message); + + protected virtual void ClearLog() + => Fixture.TestSqlLoggerFactory.Clear(); +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs new file mode 100644 index 00000000000..a8221b1b893 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class NonSharedModelBulkUpdatesRelationalTestBase : NonSharedModelBulkUpdatesTestBase +{ + protected override string StoreName + => "NonSharedModelBulkUpdatesTests"; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async) + { + var contextFactory = await InitializeAsync( + onModelCreating: mb => + { + mb.Entity().HasOne().WithOne().HasForeignKey(e => e.Id); + mb.Entity().ToTable(nameof(Owner)); + }); + + await AssertTranslationFailedWithDetails( + () => AssertDelete( + async, contextFactory.CreateContext, + context => context.Set(), rowsAffectedCount: 0), + RelationalStrings.ExecuteDeleteOnTableSplitting(nameof(Owner))); + } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Update_main_table_in_entity_with_entity_splitting(bool async) + { + var contextFactory = await InitializeAsync( + onModelCreating: mb => mb.Entity() + .ToTable("Blogs") + .SplitToTable( + "BlogsPart1", tb => + { + tb.Property(b => b.Title); + tb.Property(b => b.Rating); + }), + seed: async context => + { + context.Set().Add(new Blog { Title = "SomeBlog" }); + await context.SaveChangesAsync(); + }); + + await AssertUpdate( + async, + contextFactory.CreateContext, + ss => ss.Set(), + s => s.SetProperty(b => b.CreationTimestamp, b => new DateTime(2020, 1, 1)), + rowsAffectedCount: 1); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Update_non_main_table_in_entity_with_entity_splitting(bool async) + { + var contextFactory = await InitializeAsync( + onModelCreating: mb => mb.Entity() + .ToTable("Blogs") + .SplitToTable( + "BlogsPart1", tb => + { + tb.Property(b => b.Title); + tb.Property(b => b.Rating); + }), + seed: async context => + { + context.Set().Add(new Blog { Title = "SomeBlog" }); + await context.SaveChangesAsync(); + }); + + await AssertUpdate( + async, + contextFactory.CreateContext, + ss => ss.Set(), + s => s + .SetProperty(b => b.Title, b => b.Rating.ToString()) + .SetProperty(b => b.Rating, b => b.Title!.Length), + rowsAffectedCount: 1); + } + + #region HelperMethods + + protected static async Task AssertTranslationFailedWithDetails(Func query, string details) + => Assert.Contains( + RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + (await Assert.ThrowsAsync(query)) + .Message); + + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected void ClearLog() + => TestSqlLoggerFactory.Clear(); + + #endregion +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalFixture.cs new file mode 100644 index 00000000000..3ea28305a1f --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalFixture.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class NorthwindBulkUpdatesRelationalFixture : NorthwindBulkUpdatesFixture, ITestSqlLoggerFactory + where TModelCustomizer : ITestModelCustomizer, new() +{ + public new RelationalTestStore TestStore + => (RelationalTestStore)base.TestStore; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings( + c => c.Log(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning)) + .EnableDetailedErrors(); + + protected override bool ShouldLogCategory(string logCategory) + => logCategory == DbLoggerCategory.Query.Name; + + public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs new file mode 100644 index 00000000000..4e269f706df --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestModels.Northwind; + +namespace Microsoft.EntityFrameworkCore.BulkUpdates; + +#nullable disable + +public abstract class NorthwindBulkUpdatesRelationalTestBase : NorthwindBulkUpdatesTestBase + where TFixture : NorthwindBulkUpdatesRelationalFixture, new() +{ + protected NorthwindBulkUpdatesRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + ClearLog(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override Task Delete_non_entity_projection(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteDeleteOnNonEntityType, + () => base.Delete_non_entity_projection(async)); + + public override Task Delete_non_entity_projection_2(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteDeleteOnNonEntityType, + () => base.Delete_non_entity_projection_2(async)); + + public override Task Delete_non_entity_projection_3(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteDeleteOnNonEntityType, + () => base.Delete_non_entity_projection_3(async)); + + public virtual Task Delete_FromSql_converted_to_subquery(bool async) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + () => Fixture.CreateContext(), + (facade, transaction) => Fixture.UseTransaction(facade, transaction), + async context => + { + var queryable = context.Set().FromSqlRaw( + NormalizeDelimitersInRawString( + @"SELECT [OrderID], [ProductID], [UnitPrice], [Quantity], [Discount] +FROM [Order Details] +WHERE [OrderID] < 10300")); + + if (async) + { + await queryable.ExecuteDeleteAsync(); + } + else + { + queryable.ExecuteDelete(); + } + }); + + public override Task Update_without_property_to_set_throws(bool async) + => AssertTranslationFailed( + RelationalStrings.NoSetPropertyInvocation, + () => base.Update_without_property_to_set_throws(async)); + + public override Task Update_with_invalid_lambda_throws(bool async) + => AssertTranslationFailed( + RelationalStrings.InvalidArgumentToExecuteUpdate, + () => base.Update_with_invalid_lambda_throws(async)); + + public override Task Update_with_invalid_lambda_in_set_property_throws(bool async) + => AssertTranslationFailed( + RelationalStrings.InvalidPropertyInSetProperty( + new ExpressionPrinter().PrintExpression((OrderDetail e) => e.MaybeScalar(e => e.OrderID))), + () => base.Update_with_invalid_lambda_in_set_property_throws(async)); + + public override Task Update_multiple_tables_throws(bool async) + => AssertTranslationFailed( + RelationalStrings.MultipleTablesInExecuteUpdate("c => c.Customer.ContactName", "c => c.e.OrderDate"), + () => base.Update_multiple_tables_throws(async)); + + public override Task Update_unmapped_property_throws(bool async) + => AssertTranslationFailed( + RelationalStrings.InvalidPropertyInSetProperty("c => c.IsLondon"), + () => base.Update_unmapped_property_throws(async)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_FromSql_set_constant(bool async) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + () => Fixture.CreateContext(), + (facade, transaction) => Fixture.UseTransaction(facade, transaction), + async context => + { + var queryable = context.Set().FromSqlRaw( + NormalizeDelimitersInRawString( + @"SELECT [Region], [PostalCode], [Phone], [Fax], [CustomerID], [Country], [ContactTitle], [ContactName], [CompanyName], [City], [Address] +FROM [Customers] +WHERE [CustomerID] LIKE 'A%'")); + + if (async) + { + await queryable.ExecuteUpdateAsync(s => s.SetProperty(c => c.ContactName, "Updated")); + } + else + { + queryable.ExecuteUpdate(s => s.SetProperty(c => c.ContactName, "Updated")); + } + }); + + protected static async Task AssertTranslationFailed(string details, Func query) + => Assert.Contains( + RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + (await Assert.ThrowsAsync(query)).Message); + + protected string NormalizeDelimitersInRawString(string sql) + => Fixture.TestStore.NormalizeDelimitersInRawString(sql); + + protected virtual void ClearLog() + => Fixture.TestSqlLoggerFactory.Clear(); +} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs index 001f6301af0..e838bdda9c9 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs @@ -5,16 +5,10 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPCFiltersInheritanceBulkUpdatesTestBase : FiltersInheritanceBulkUpdatesTestBase +public abstract class TPCFiltersInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : FiltersInheritanceBulkUpdatesRelationalTestBase(fixture, testOutputHelper) where TFixture : TPCInheritanceBulkUpdatesFixture, new() { - protected TPCFiltersInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - // Keyless entities are mapped as TPH only public override Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) => Task.CompletedTask; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesFixture.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesFixture.cs index cbcd81f4364..e7d6bbbe2d5 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesFixture.cs @@ -7,14 +7,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPCInheritanceBulkUpdatesFixture : InheritanceBulkUpdatesFixtureBase, ITestSqlLoggerFactory +public abstract class TPCInheritanceBulkUpdatesFixture : InheritanceBulkUpdatesRelationalFixtureBase { protected override string StoreName => "TPCInheritanceBulkUpdatesTest"; - public TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - public override bool HasDiscriminator => false; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs index ba35a7c8dd0..2a847ac5cc0 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs @@ -5,11 +5,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPCInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesTestBase +public abstract class TPCInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesRelationalTestBase where TFixture : TPCInheritanceBulkUpdatesFixture, new() { protected TPCInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) + : base(fixture, testOutputHelper) { ClearLog(); Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesFixture.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesFixture.cs index a38b366ecda..5e0cac5e5eb 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesFixture.cs @@ -7,14 +7,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPHInheritanceBulkUpdatesFixture : InheritanceBulkUpdatesFixtureBase, ITestSqlLoggerFactory +public abstract class TPHInheritanceBulkUpdatesFixture : InheritanceBulkUpdatesRelationalFixtureBase { protected override string StoreName => "TPHInheritanceBulkUpdatesTest"; - public TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - // #31378 public override bool EnableComplexTypes => false; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesTestBase.cs index 5c12d13923d..30690134732 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPHInheritanceBulkUpdatesTestBase.cs @@ -5,11 +5,6 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPHInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesTestBase - where TFixture : InheritanceBulkUpdatesFixtureBase, new() -{ - protected TPHInheritanceBulkUpdatesTestBase(TFixture fixture) - : base(fixture) - { - } -} +public abstract class TPHInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : InheritanceBulkUpdatesRelationalTestBase(fixture, testOutputHelper) + where TFixture : InheritanceBulkUpdatesRelationalFixtureBase, new(); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs index 82dff763d9c..0d54fee9c1e 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs @@ -5,16 +5,10 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPTFiltersInheritanceBulkUpdatesTestBase : FiltersInheritanceBulkUpdatesTestBase +public abstract class TPTFiltersInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : FiltersInheritanceBulkUpdatesRelationalTestBase(fixture, testOutputHelper) where TFixture : TPTInheritanceBulkUpdatesFixture, new() { - protected TPTFiltersInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - // Keyless entities are mapped as TPH only public override Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) => Task.CompletedTask; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesFixture.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesFixture.cs index 98c89d228e0..7a2a4795dc9 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesFixture.cs @@ -7,14 +7,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPTInheritanceBulkUpdatesFixture : InheritanceBulkUpdatesFixtureBase, ITestSqlLoggerFactory +public abstract class TPTInheritanceBulkUpdatesFixture : InheritanceBulkUpdatesRelationalFixtureBase { protected override string StoreName => "TPTInheritanceBulkUpdatesTest"; - public TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - public override bool HasDiscriminator => false; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs index 0d527401e39..f6a8c22ad69 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs @@ -5,11 +5,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class TPTInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesTestBase +public abstract class TPTInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesRelationalTestBase where TFixture : TPTInheritanceBulkUpdatesFixture, new() { protected TPTInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) + : base(fixture, testOutputHelper) { Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); ClearLog(); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs similarity index 84% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs index f0b5d8834b1..8b53b43414a 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/BulkUpdatesTestBase.cs @@ -38,9 +38,4 @@ public Task AssertUpdate( Action, IReadOnlyList> asserter = null) where TResult : class => BulkUpdatesAsserter.AssertUpdate(async, query, entitySelector, setPropertyCalls, rowsAffectedCount, asserter); - - protected static async Task AssertTranslationFailed(string details, Func query) - => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], - (await Assert.ThrowsAsync(query)).Message); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs similarity index 63% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs index f77cc45158e..fc2c4342629 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesFixtureBase.cs @@ -5,11 +5,10 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class ComplexTypeBulkUpdatesFixtureBase : ComplexTypeQueryRelationalFixtureBase, IBulkUpdatesFixtureBase +public abstract class ComplexTypeBulkUpdatesFixtureBase : ComplexTypeQueryFixtureBase, IBulkUpdatesFixtureBase { protected override string StoreName => "ComplexTypeBulkUpdatesTest"; - public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - => facade.UseTransaction(transaction.GetDbTransaction()); + public abstract void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs similarity index 85% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs index 10d6cda20f4..9c5224e2fa6 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesTestBase.cs @@ -7,15 +7,26 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class ComplexTypeBulkUpdatesTestBase : BulkUpdatesTestBase +public abstract class ComplexTypeBulkUpdatesTestBase(TFixture fixture) : BulkUpdatesTestBase(fixture) where TFixture : ComplexTypeBulkUpdatesFixtureBase, new() { - protected ComplexTypeBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_complex_type(bool async) + => AssertDelete( + async, + ss => ss.Set().Select(c => c.ShippingAddress), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_projected_complex_type_via_OrderBy_Skip(bool async) + => AssertUpdate( + async, + ss => ss.Set().Select(c => c.ShippingAddress).OrderBy(a => a.ZipCode).Skip(1), + a => a, + s => s.SetProperty(c => c.ZipCode, 12345), + rowsAffectedCount: 3); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -25,16 +36,6 @@ public virtual Task Delete_entity_type_with_complex_type(bool async) ss => ss.Set().Where(e => e.Name == "Monty Elias"), rowsAffectedCount: 1); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_complex_type_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteDeleteOnNonEntityType, - () => AssertDelete( - async, - ss => ss.Set().Select(c => c.ShippingAddress), - rowsAffectedCount: 0)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_property_inside_complex_type(bool async) @@ -96,18 +97,6 @@ public virtual Task Update_multiple_projected_complex_types_via_anonymous_type(b .SetProperty(x => x.BillingAddress.ZipCode, 54321), rowsAffectedCount: 3); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_projected_complex_type_via_OrderBy_Skip_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteUpdateSubqueryNotSupportedOverComplexTypes("Customer.ShippingAddress#Address"), - () => AssertUpdate( - async, - ss => ss.Set().Select(c => c.ShippingAddress).OrderBy(a => a.ZipCode).Skip(1), - a => a, - s => s.SetProperty(c => c.ZipCode, 12345), - rowsAffectedCount: 3)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_complex_type_to_parameter(bool async) @@ -222,7 +211,4 @@ public virtual Task Update_collection_inside_complex_type(bool async) c => c, s => s.SetProperty(x => x.ShippingAddress.Tags, new List { "new_tag1", "new_tag2" }), rowsAffectedCount: 3); - - private void ClearLog() - => Fixture.TestSqlLoggerFactory.Clear(); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs similarity index 85% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs index 4f9f3b323a7..9a753835de7 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs @@ -87,16 +87,6 @@ public virtual Task Delete_GroupBy_Where_Select_First_3(bool async) .Where(g => g.Count() < 3).Select(g => g.First()).Any(i => i == e)), rowsAffectedCount: 1); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteDelete", "EagleQuery"), - () => AssertDelete( - async, - ss => ss.Set().Where(e => e.CountryId > 0), - rowsAffectedCount: 1)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_base_type(bool async) @@ -180,18 +170,4 @@ public virtual Task Update_where_using_hierarchy_derived(bool async) e => e, s => s.SetProperty(e => e.Name, "Monovia"), rowsAffectedCount: 1); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteUpdate", "EagleQuery"), - () => AssertUpdate( - async, - ss => ss.Set().Where(e => e.CountryId > 0), - e => e, - s => s.SetProperty(e => e.Name, "Eagle"), - rowsAffectedCount: 1)); - - protected abstract void ClearLog(); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/IBulkUpdatesFixtureBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/IBulkUpdatesFixtureBase.cs similarity index 100% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/IBulkUpdatesFixtureBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/IBulkUpdatesFixtureBase.cs diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs similarity index 83% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs index 24f40dbdc70..f8841a46d87 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs @@ -15,6 +15,5 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build CoreEventId.MappedPropertyIgnoredWarning, CoreEventId.MappedNavigationIgnoredWarning)); - public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - => facade.UseTransaction(transaction.GetDbTransaction()); + public abstract void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs similarity index 84% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs index 5682f4c5a09..195b23bd23e 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs @@ -7,14 +7,9 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class InheritanceBulkUpdatesTestBase : BulkUpdatesTestBase +public abstract class InheritanceBulkUpdatesTestBase(TFixture fixture) : BulkUpdatesTestBase(fixture) where TFixture : InheritanceBulkUpdatesFixtureBase, new() { - protected InheritanceBulkUpdatesTestBase(TFixture fixture) - : base(fixture) - { - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_where_hierarchy(bool async) @@ -87,16 +82,6 @@ public virtual Task Delete_GroupBy_Where_Select_First_3(bool async) .Where(g => g.Count() < 3).Select(g => g.First()).Any(i => i == e)), rowsAffectedCount: 2); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteDelete", "EagleQuery"), - () => AssertDelete( - async, - ss => ss.Set().Where(e => e.CountryId > 0), - rowsAffectedCount: 1)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_base_type(bool async) @@ -181,18 +166,6 @@ public virtual Task Update_where_using_hierarchy_derived(bool async) s => s.SetProperty(e => e.Name, "Monovia"), rowsAffectedCount: 1); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator("ExecuteUpdate", "EagleQuery"), - () => AssertUpdate( - async, - ss => ss.Set().Where(e => e.CountryId > 0), - e => e, - s => s.SetProperty(e => e.Name, "Eagle"), - rowsAffectedCount: 1)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_with_interface_in_property_expression(bool async) @@ -212,7 +185,4 @@ public virtual Task Update_with_interface_in_EF_Property_in_property_expression( e => e, // ReSharper disable once RedundantCast s => s.SetProperty(c => EF.Property((ISugary)c, nameof(ISugary.SugarGrams)), 0), - rowsAffectedCount: 1); - - protected abstract void ClearLog(); -} + rowsAffectedCount: 1);} diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs similarity index 77% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs index 3863718e20d..c5cca8e6334 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs @@ -42,24 +42,6 @@ await AssertDelete( context => context.Set(), rowsAffectedCount: 0); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: mb => - { - mb.Entity().HasOne().WithOne().HasForeignKey(e => e.Id); - mb.Entity().ToTable(nameof(Owner)); - }); - - await AssertTranslationFailedWithDetails( - () => AssertDelete( - async, contextFactory.CreateContext, - context => context.Set(), rowsAffectedCount: 0), - RelationalStrings.ExecuteDeleteOnTableSplitting(nameof(Owner))); - } - [ConditionalTheory] // #33937, #33946 [MemberData(nameof(IsAsyncData))] public virtual async Task Replace_ColumnExpression_in_column_setter(bool async) @@ -186,62 +168,6 @@ await AssertUpdate( rowsAffectedCount: 0); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Update_main_table_in_entity_with_entity_splitting(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity() - .ToTable("Blogs") - .SplitToTable( - "BlogsPart1", tb => - { - tb.Property(b => b.Title); - tb.Property(b => b.Rating); - }), - seed: async context => - { - context.Set().Add(new Blog { Title = "SomeBlog" }); - await context.SaveChangesAsync(); - }); - - await AssertUpdate( - async, - contextFactory.CreateContext, - ss => ss.Set(), - s => s.SetProperty(b => b.CreationTimestamp, b => new DateTime(2020, 1, 1)), - rowsAffectedCount: 1); - } - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Update_non_main_table_in_entity_with_entity_splitting(bool async) - { - var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity() - .ToTable("Blogs") - .SplitToTable( - "BlogsPart1", tb => - { - tb.Property(b => b.Title); - tb.Property(b => b.Rating); - }), - seed: async context => - { - context.Set().Add(new Blog { Title = "SomeBlog" }); - await context.SaveChangesAsync(); - }); - - await AssertUpdate( - async, - contextFactory.CreateContext, - ss => ss.Set(), - s => s - .SetProperty(b => b.Title, b => b.Rating.ToString()) - .SetProperty(b => b.Rating, b => b.Title!.Length), - rowsAffectedCount: 1); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Delete_entity_with_auto_include(bool async) @@ -414,20 +340,7 @@ public Task AssertUpdate( Assert.Equal(rowsAffectedCount, result); }); - protected static async Task AssertTranslationFailedWithDetails(Func query, string details) - => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], - (await Assert.ThrowsAsync(query)) - .Message); - - public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - => facade.UseTransaction(transaction.GetDbTransaction()); - - protected TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - - protected void ClearLog() - => TestSqlLoggerFactory.Clear(); + public abstract void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction); #endregion } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesFixture.cs b/test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesFixture.cs similarity index 67% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesFixture.cs rename to test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesFixture.cs index f6bd8eba4ae..238f0ec5917 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesFixture.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesFixture.cs @@ -5,13 +5,12 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class NorthwindBulkUpdatesFixture : NorthwindQueryRelationalFixture, +public abstract class NorthwindBulkUpdatesFixture : NorthwindQueryFixtureBase, IBulkUpdatesFixtureBase where TModelCustomizer : ITestModelCustomizer, new() { protected override string StoreName => "BulkUpdatesNorthwind"; - public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - => facade.UseTransaction(transaction.GetDbTransaction()); + public abstract void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs b/test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs similarity index 87% rename from test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs rename to test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs index b013d393c5c..50750b0913b 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs @@ -7,15 +7,88 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public abstract class NorthwindBulkUpdatesTestBase : BulkUpdatesTestBase +public abstract class NorthwindBulkUpdatesTestBase(TFixture fixture) : BulkUpdatesTestBase(fixture) where TFixture : NorthwindBulkUpdatesFixture, new() { - protected NorthwindBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_non_entity_projection(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(od => od.OrderID < 10250).Select(e => e.ProductID), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_non_entity_projection_2(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(od => od.OrderID < 10250) + .Select(e => new OrderDetail { OrderID = e.OrderID, ProductID = e.ProductID }), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_non_entity_projection_3(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(od => od.OrderID < 10250) + .Select(e => new { OrderDetail = e, e.ProductID }), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_without_property_to_set_throws(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(od => od.OrderID < 10250), + e => e, + s => s, + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_invalid_lambda_throws(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(od => od.OrderID < 10250), + e => e, + s => s.Maybe(e => e), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_with_invalid_lambda_in_set_property_throws(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(od => od.OrderID < 10250), + e => e, + s => s.SetProperty(e => e.MaybeScalar(e => e.OrderID), 10300), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_multiple_tables_throws(bool async) + => AssertUpdate( + async, + ss => ss.Set() + .Where(o => o.CustomerID.StartsWith("F")) + .Select(e => new { e, e.Customer }), + e => e.Customer, + s => s + .SetProperty(c => c.Customer.ContactName, "Name") + .SetProperty(c => c.e.OrderDate, new DateTime(2020, 1, 1)), + rowsAffectedCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_unmapped_property_throws(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), + e => e, + s => s.SetProperty(c => c.IsLondon, true), + rowsAffectedCount: 0); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -242,62 +315,6 @@ public virtual Task Delete_Except(bool async) .Except(ss.Set().Where(od => od.OrderID > 11250)), rowsAffectedCount: 5); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_non_entity_projection(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteDeleteOnNonEntityType, - () => AssertDelete( - async, - ss => ss.Set().Where(od => od.OrderID < 10250).Select(e => e.ProductID), - rowsAffectedCount: 0)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_non_entity_projection_2(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteDeleteOnNonEntityType, - () => AssertDelete( - async, - ss => ss.Set().Where(od => od.OrderID < 10250) - .Select(e => new OrderDetail { OrderID = e.OrderID, ProductID = e.ProductID }), - rowsAffectedCount: 0)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_non_entity_projection_3(bool async) - => AssertTranslationFailed( - RelationalStrings.ExecuteDeleteOnNonEntityType, - () => AssertDelete( - async, - ss => ss.Set().Where(od => od.OrderID < 10250) - .Select(e => new { OrderDetail = e, e.ProductID }), - rowsAffectedCount: 0)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Delete_FromSql_converted_to_subquery(bool async) - => TestHelpers.ExecuteWithStrategyInTransactionAsync( - () => Fixture.CreateContext(), - (facade, transaction) => Fixture.UseTransaction(facade, transaction), - async context => - { - var queryable = context.Set().FromSqlRaw( - NormalizeDelimitersInRawString( - @"SELECT [OrderID], [ProductID], [UnitPrice], [Quantity], [Discount] -FROM [Order Details] -WHERE [OrderID] < 10300")); - - if (async) - { - await queryable.ExecuteDeleteAsync(); - } - else - { - queryable.ExecuteDelete(); - } - }); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_Where_optional_navigation_predicate(bool async) @@ -719,30 +736,6 @@ public virtual Task Update_Where_set_null(bool async) rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Null(c.ContactName))); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_without_property_to_set_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.NoSetPropertyInvocation, - () => AssertUpdate( - async, - ss => ss.Set().Where(od => od.OrderID < 10250), - e => e, - s => s, - rowsAffectedCount: 0)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_with_invalid_lambda_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.InvalidArgumentToExecuteUpdate, - () => AssertUpdate( - async, - ss => ss.Set().Where(od => od.OrderID < 10250), - e => e, - s => s.Maybe(e => e), - rowsAffectedCount: 0)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Where_multiple_set(bool async) @@ -762,47 +755,6 @@ public virtual Task Update_Where_multiple_set(bool async) })); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_with_invalid_lambda_in_set_property_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.InvalidPropertyInSetProperty( - new ExpressionPrinter().PrintExpression((OrderDetail e) => e.MaybeScalar(e => e.OrderID))), - () => AssertUpdate( - async, - ss => ss.Set().Where(od => od.OrderID < 10250), - e => e, - s => s.SetProperty(e => e.MaybeScalar(e => e.OrderID), 10300), - rowsAffectedCount: 0)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_multiple_tables_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.MultipleTablesInExecuteUpdate("c => c.Customer.ContactName", "c => c.e.OrderDate"), - () => AssertUpdate( - async, - ss => ss.Set() - .Where(o => o.CustomerID.StartsWith("F")) - .Select(e => new { e, e.Customer }), - e => e.Customer, - s => s - .SetProperty(c => c.Customer.ContactName, "Name") - .SetProperty(c => c.e.OrderDate, new DateTime(2020, 1, 1)), - rowsAffectedCount: 0)); - - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_unmapped_property_throws(bool async) - => AssertTranslationFailed( - RelationalStrings.InvalidPropertyInSetProperty("c => c.IsLondon"), - () => AssertUpdate( - async, - ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), - e => e, - s => s.SetProperty(c => c.IsLondon, true), - rowsAffectedCount: 0)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Union_set_constant(bool async) @@ -973,30 +925,6 @@ from o in ss.Set().Where(o => o.OrderID < 10300 && o.OrderDate.Value.Year rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Update_FromSql_set_constant(bool async) - => TestHelpers.ExecuteWithStrategyInTransactionAsync( - () => Fixture.CreateContext(), - (facade, transaction) => Fixture.UseTransaction(facade, transaction), - async context => - { - var queryable = context.Set().FromSqlRaw( - NormalizeDelimitersInRawString( - @"SELECT [Region], [PostalCode], [Phone], [Fax], [CustomerID], [Country], [ContactTitle], [ContactName], [CompanyName], [City], [Address] -FROM [Customers] -WHERE [CustomerID] LIKE 'A%'")); - - if (async) - { - await queryable.ExecuteUpdateAsync(s => s.SetProperty(c => c.ContactName, "Updated")); - } - else - { - queryable.ExecuteUpdate(s => s.SetProperty(c => c.ContactName, "Updated")); - } - }); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Where_SelectMany_subquery_set_null(bool async) @@ -1058,10 +986,4 @@ public virtual Task Update_with_two_inner_joins(bool async) s => s.SetProperty(od => od.Quantity, 1), rowsAffectedCount: 228, (b, a) => Assert.All(a, od => Assert.Equal(1, od.Quantity))); - - protected string NormalizeDelimitersInRawString(string sql) - => Fixture.TestStore.NormalizeDelimitersInRawString(sql); - - protected virtual void ClearLog() - => Fixture.TestSqlLoggerFactory.Clear(); } diff --git a/test/EFCore.Specification.Tests/TestUtilities/BulkUpdatesAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/BulkUpdatesAsserter.cs new file mode 100644 index 00000000000..c03cecd9125 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestUtilities/BulkUpdatesAsserter.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable +using Microsoft.EntityFrameworkCore.BulkUpdates; + +namespace Microsoft.EntityFrameworkCore.TestUtilities; + +public class BulkUpdatesAsserter(IBulkUpdatesFixtureBase queryFixture, Func rewriteServerQueryExpression) +{ + private readonly Func _contextCreator = queryFixture.GetContextCreator(); + private readonly Action _useTransaction = queryFixture.GetUseTransaction(); + private readonly Func _setSourceCreator = queryFixture.GetSetSourceCreator(); + private readonly Func _rewriteServerQueryExpression = rewriteServerQueryExpression; + private readonly IReadOnlyDictionary _entitySorters = queryFixture.EntitySorters ?? new Dictionary(); + + public Task AssertDelete( + bool async, + Func> query, + int rowsAffectedCount) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + _contextCreator, _useTransaction, + async context => + { + var processedQuery = RewriteServerQuery(query(_setSourceCreator(context))); + + var result = async + ? await processedQuery.ExecuteDeleteAsync() + : processedQuery.ExecuteDelete(); + + Assert.Equal(rowsAffectedCount, result); + }); + + public Task AssertUpdate( + bool async, + Func> query, + Expression> entitySelector, + Expression, SetPropertyCalls>> setPropertyCalls, + int rowsAffectedCount, + Action, IReadOnlyList> asserter) + where TResult : class + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + _contextCreator, _useTransaction, + async context => + { + var elementSorter = (Func)_entitySorters[typeof(TEntity)]; + + var processedQuery = RewriteServerQuery(query(_setSourceCreator(context))); + + var before = processedQuery.AsNoTracking().Select(entitySelector).OrderBy(elementSorter).ToList(); + + var result = async + ? await processedQuery.ExecuteUpdateAsync(setPropertyCalls) + : processedQuery.ExecuteUpdate(setPropertyCalls); + + Assert.Equal(rowsAffectedCount, result); + + var after = processedQuery.AsNoTracking().Select(entitySelector).OrderBy(elementSorter).ToList(); + + asserter?.Invoke(before, after); + }); + + private IQueryable RewriteServerQuery(IQueryable query) + => query.Provider.CreateQuery(_rewriteServerQueryExpression(query.Expression)); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqlServerTest.cs index 4ad3392a469..1c719aa04e4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class ComplexTypeBulkUpdatesSqlServerTest(ComplexTypeBulkUpdatesSqlServerTest.ComplexTypeBulkUpdatesSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : ComplexTypeBulkUpdatesTestBase< +public class ComplexTypeBulkUpdatesSqlServerTest(ComplexTypeBulkUpdatesSqlServerTest.ComplexTypeBulkUpdatesSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : ComplexTypeBulkUpdatesRelationalTestBase< ComplexTypeBulkUpdatesSqlServerTest.ComplexTypeBulkUpdatesSqlServerFixture>(fixture, testOutputHelper) { public override async Task Delete_entity_type_with_complex_type(bool async) @@ -20,9 +20,9 @@ FROM [Customer] AS [c] """); } - public override async Task Delete_complex_type_throws(bool async) + public override async Task Delete_complex_type(bool async) { - await base.Delete_complex_type_throws(async); + await base.Delete_complex_type(async); AssertSql(); } @@ -93,9 +93,9 @@ FROM [Customer] AS [c] """); } - public override async Task Update_projected_complex_type_via_OrderBy_Skip_throws(bool async) + public override async Task Update_projected_complex_type_via_OrderBy_Skip(bool async) { - await base.Update_projected_complex_type_via_OrderBy_Skip_throws(async); + await base.Update_projected_complex_type_via_OrderBy_Skip(async); AssertExecuteUpdateSql(); } @@ -241,7 +241,7 @@ private void AssertSql(params string[] expected) protected void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); - public class ComplexTypeBulkUpdatesSqlServerFixture : ComplexTypeBulkUpdatesFixtureBase + public class ComplexTypeBulkUpdatesSqlServerFixture : ComplexTypeBulkUpdatesRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs index e973a55ab36..81b06f363b9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class NonSharedModelBulkUpdatesSqlServerTest : NonSharedModelBulkUpdatesTestBase +public class NonSharedModelBulkUpdatesSqlServerTest : NonSharedModelBulkUpdatesRelationalTestBase { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerFixture.cs index 0bec7c67a22..f59376bb3c9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerFixture.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class NorthwindBulkUpdatesSqlServerFixture : NorthwindBulkUpdatesFixture +public class NorthwindBulkUpdatesSqlServerFixture : NorthwindBulkUpdatesRelationalFixture where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs index dc962065b0c..27dd7719563 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; public class NorthwindBulkUpdatesSqlServerTest( NorthwindBulkUpdatesSqlServerFixture fixture, - ITestOutputHelper testOutputHelper) : NorthwindBulkUpdatesTestBase>(fixture, testOutputHelper) + ITestOutputHelper testOutputHelper) : NorthwindBulkUpdatesRelationalTestBase>(fixture, testOutputHelper) { [ConditionalFact] public virtual void Check_all_tests_overridden() diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqlServerTest.cs index 49231367e12..1972ddd3415 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqlServerTest.cs @@ -5,18 +5,12 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class TPHFiltersInheritanceBulkUpdatesSqlServerTest : FiltersInheritanceBulkUpdatesTestBase< - TPHFiltersInheritanceBulkUpdatesSqlServerFixture> +public class TPHFiltersInheritanceBulkUpdatesSqlServerTest( + TPHFiltersInheritanceBulkUpdatesSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : FiltersInheritanceBulkUpdatesRelationalTestBase< + TPHFiltersInheritanceBulkUpdatesSqlServerFixture>(fixture, testOutputHelper) { - public TPHFiltersInheritanceBulkUpdatesSqlServerTest( - TPHFiltersInheritanceBulkUpdatesSqlServerFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqlServerTest.cs index 15cd8e98c32..abf70551a6b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqlServerTest.cs @@ -5,17 +5,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class TPHInheritanceBulkUpdatesSqlServerTest : TPHInheritanceBulkUpdatesTestBase +public class TPHInheritanceBulkUpdatesSqlServerTest( + TPHInheritanceBulkUpdatesSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : TPHInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPHInheritanceBulkUpdatesSqlServerTest( - TPHInheritanceBulkUpdatesSqlServerFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqliteTest.cs index 62699586106..8b6bccba5b5 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesSqliteTest.cs @@ -5,10 +5,10 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class ComplexTypeBulkUpdatesSqliteTest(ComplexTypeBulkUpdatesSqliteTest.ComplexTypeBulkUpdatesSqliteFixture fixture, ITestOutputHelper testOutputHelper) : ComplexTypeBulkUpdatesTestBase< +public class ComplexTypeBulkUpdatesSqliteTest(ComplexTypeBulkUpdatesSqliteTest.ComplexTypeBulkUpdatesSqliteFixture fixture, ITestOutputHelper testOutputHelper) : ComplexTypeBulkUpdatesRelationalTestBase< ComplexTypeBulkUpdatesSqliteTest.ComplexTypeBulkUpdatesSqliteFixture>(fixture, testOutputHelper) { - public class ComplexTypeBulkUpdatesSqliteFixture : ComplexTypeBulkUpdatesFixtureBase + public class ComplexTypeBulkUpdatesSqliteFixture : ComplexTypeBulkUpdatesRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs index 6809d2912e7..52763b754ae 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class NonSharedModelBulkUpdatesSqliteTest : NonSharedModelBulkUpdatesTestBase +public class NonSharedModelBulkUpdatesSqliteTest : NonSharedModelBulkUpdatesRelationalTestBase { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteFixture.cs index 2ddb4cc6efa..9c63144fe5f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteFixture.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class NorthwindBulkUpdatesSqliteFixture : NorthwindBulkUpdatesFixture +public class NorthwindBulkUpdatesSqliteFixture : NorthwindBulkUpdatesRelationalFixture where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs index 39516e4e8b4..b9d24ca7352 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; public class NorthwindBulkUpdatesSqliteTest( NorthwindBulkUpdatesSqliteFixture fixture, - ITestOutputHelper testOutputHelper) : NorthwindBulkUpdatesTestBase>(fixture, testOutputHelper) + ITestOutputHelper testOutputHelper) : NorthwindBulkUpdatesRelationalTestBase>(fixture, testOutputHelper) { [ConditionalFact] public virtual void Check_all_tests_overridden() diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqliteTest.cs index 9ab20e3d02d..0546ac8b6ea 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesSqliteTest.cs @@ -5,18 +5,12 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class TPHFiltersInheritanceBulkUpdatesSqliteTest : FiltersInheritanceBulkUpdatesTestBase< - TPHFiltersInheritanceBulkUpdatesSqliteFixture> +public class TPHFiltersInheritanceBulkUpdatesSqliteTest( + TPHFiltersInheritanceBulkUpdatesSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : FiltersInheritanceBulkUpdatesRelationalTestBase< + TPHFiltersInheritanceBulkUpdatesSqliteFixture>(fixture, testOutputHelper) { - public TPHFiltersInheritanceBulkUpdatesSqliteTest( - TPHFiltersInheritanceBulkUpdatesSqliteFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqliteTest.cs index b712ab1119f..ceb88407b32 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesSqliteTest.cs @@ -5,17 +5,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates; #nullable disable -public class TPHInheritanceBulkUpdatesSqliteTest : TPHInheritanceBulkUpdatesTestBase +public class TPHInheritanceBulkUpdatesSqliteTest( + TPHInheritanceBulkUpdatesSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : TPHInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPHInheritanceBulkUpdatesSqliteTest( - TPHInheritanceBulkUpdatesSqliteFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); From 11a9e3e7f1898e2dd6671a9c9da702fea8b5fab9 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 22 Jul 2024 10:56:41 +0100 Subject: [PATCH 2/2] Move dispatch to core --- .../Properties/RelationalStrings.Designer.cs | 8 - .../Properties/RelationalStrings.resx | 3 - ...nslatingExpressionVisitor.ExecuteDelete.cs | 9 +- ...nslatingExpressionVisitor.ExecuteUpdate.cs | 18 +- ...yableMethodTranslatingExpressionVisitor.cs | 33 --- src/EFCore/Properties/CoreStrings.Designer.cs | 16 + src/EFCore/Properties/CoreStrings.resx | 6 + ...yableMethodTranslatingExpressionVisitor.cs | 61 ++++ ...rsInheritanceBulkUpdatesInMemoryFixture.cs | 10 - ...ltersInheritanceBulkUpdatesInMemoryTest.cs | 66 ----- .../InheritanceBulkUpdatesInMemoryFixture.cs | 19 -- .../InheritanceBulkUpdatesInMemoryTest.cs | 72 ----- .../NonSharedModelBulkUpdatesInMemoryTest.cs | 62 ---- .../NorthwindBulkUpdatesInMemoryFixture.cs | 23 -- .../NorthwindBulkUpdatesInMemoryTest.cs | 273 ------------------ .../InMemoryComplianceTest.cs | 5 + ...omplexTypeBulkUpdatesRelationalTestBase.cs | 2 +- ...nheritanceBulkUpdatesRelationalTestBase.cs | 2 +- ...nheritanceBulkUpdatesRelationalTestBase.cs | 2 +- ...haredModelBulkUpdatesRelationalTestBase.cs | 2 +- .../NorthwindBulkUpdatesRelationalTestBase.cs | 2 +- .../EntitySplittingTestBase.cs | 2 +- .../TableSplittingTestBase.cs | 2 +- test/EFCore.Tests/Query/QueryProviderTest.cs | 23 ++ 24 files changed, 122 insertions(+), 599 deletions(-) delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs delete mode 100644 test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 8fa50e47227..817c3f92fa7 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1413,14 +1413,6 @@ public static string NonConstantOrParameterAsInExpressionValue(object? type) public static string NoneRelationalTypeMappingOnARelationalTypeMappingSource => GetString("NoneRelationalTypeMappingOnARelationalTypeMappingSource"); - /// - /// The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. - /// - public static string NonQueryTranslationFailedWithDetails(object? expression, object? details) - => string.Format( - GetString("NonQueryTranslationFailedWithDetails", nameof(expression), nameof(details)), - expression, details); - /// /// Cannot set 'IsNullable' on DbFunction '{functionName}' since the function does not represent a scalar function. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index edccbd9e5aa..a3e8dae1fe8 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -957,9 +957,6 @@ 'FindMapping' was called on a 'RelationalTypeMappingSource' with a non-relational 'TypeMappingInfo'. - - The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. - Cannot set 'IsNullable' on DbFunction '{functionName}' since the function does not represent a scalar function. diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs index 50ff19d5002..a21bdaf7ba5 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs @@ -9,13 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Query; public partial class RelationalQueryableMethodTranslatingExpressionVisitor { - /// - /// Translates method - /// over the given source. - /// - /// The shaped query on which the operator is applied. - /// The non query after translation. - protected virtual NonQueryExpression? TranslateExecuteDelete(ShapedQueryExpression source) + /// + protected override NonQueryExpression? TranslateExecuteDelete(ShapedQueryExpression source) { source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression)); diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs index 2fabecd5762..b627332a274 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs @@ -14,22 +14,8 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor private static readonly MethodInfo ParameterValueExtractorMethod = typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterValueExtractor))!; - /// - /// Translates - /// - /// method - /// over the given source. - /// - /// The shaped query on which the operator is applied. - /// - /// The lambda expression containing - /// - /// statements. - /// - /// The non query after translation. - protected virtual NonQueryExpression? TranslateExecuteUpdate(ShapedQueryExpression source, LambdaExpression setPropertyCalls) + /// + protected override NonQueryExpression? TranslateExecuteUpdate(ShapedQueryExpression source, LambdaExpression setPropertyCalls) { // Our source may have IncludeExpressions because of owned entities or auto-include; unwrap these, as they're meaningless for // ExecuteUpdate's lambdas. Note that we don't currently support updates across tables. diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 44fc1da2904..f2e9c167e70 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -226,43 +226,10 @@ [new FromSqlExpression(alias, sqlQueryRootExpression.Sql, sqlQueryRootExpression } } - private static readonly MethodInfo ExecuteDeleteMethodInfo - = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod( - nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))!; - - private static readonly MethodInfo ExecuteUpdateMethodInfo - = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod( - nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))!; - /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { var method = methodCallExpression.Method; - if (method.DeclaringType == typeof(EntityFrameworkQueryableExtensions)) - { - var source = Visit(methodCallExpression.Arguments[0]); - if (source is ShapedQueryExpression shapedQueryExpression) - { - var genericMethod = method.IsGenericMethod ? method.GetGenericMethodDefinition() : null; - switch (method.Name) - { - case nameof(EntityFrameworkQueryableExtensions.ExecuteDelete) - when genericMethod == ExecuteDeleteMethodInfo: - return TranslateExecuteDelete(shapedQueryExpression) - ?? throw new InvalidOperationException( - RelationalStrings.NonQueryTranslationFailedWithDetails( - methodCallExpression.Print(), TranslationErrorDetails)); - - case nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate) - when genericMethod == ExecuteUpdateMethodInfo: - return TranslateExecuteUpdate(shapedQueryExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()) - ?? throw new InvalidOperationException( - RelationalStrings.NonQueryTranslationFailedWithDetails( - methodCallExpression.Print(), TranslationErrorDetails)); - } - } - } - var translated = base.VisitMethodCall(methodCallExpression); // For Contains over a collection parameter, if the provider hasn't implemented TranslateCollection (e.g. OPENJSON on SQL diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index ba1e675ecc3..d190abe69e6 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1140,6 +1140,14 @@ public static string ErrorMaterializingPropertyInvalidCast(object? entityType, o GetString("ErrorMaterializingPropertyInvalidCast", nameof(entityType), nameof(property), nameof(expectedType), nameof(actualType)), entityType, property, expectedType, actualType); + /// + /// The methods '{methodName}' and '{asyncMethodName}' are not supported by the current database provider. Please contact the publisher of the database provider for more information. + /// + public static string ExecuteQueriesNotSupported(object? methodName, object? asyncMethodName) + => string.Format( + GetString("ExecuteQueriesNotSupported", nameof(methodName), nameof(asyncMethodName)), + methodName, asyncMethodName); + /// /// The configured execution strategy '{strategy}' does not support user-initiated transactions. Use the execution strategy returned by '{getExecutionStrategyMethod}' to execute all the operations in the transaction as a retriable unit. /// @@ -2137,6 +2145,14 @@ public static string NonIndexerEntityType(object? property, object? entityType, GetString("NonIndexerEntityType", nameof(property), nameof(entityType), nameof(type)), property, entityType, type); + /// + /// The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. + /// + public static string NonQueryTranslationFailedWithDetails(object? expression, object? details) + => string.Format( + GetString("NonQueryTranslationFailedWithDetails", nameof(expression), nameof(details)), + expression, details); + /// /// The collection type '{2_collectionType}' being used for navigation '{1_entityType}.{0_navigation}' does not implement 'INotifyCollectionChanged'. Any entity type configured to use the '{changeTrackingStrategy}' change tracking strategy must use collections that implement 'INotifyCollectionChanged'. Consider using 'ObservableCollection<T>' for this. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index f688153f94b..d271b9fec11 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -546,6 +546,9 @@ An error occurred while reading a database value for property '{entityType}.{property}'. The expected type was '{expectedType}' but the actual value was of type '{actualType}'. + + The methods '{methodName}' and '{asyncMethodName}' are not supported by the current database provider. Please contact the publisher of the database provider for more information. + The configured execution strategy '{strategy}' does not support user-initiated transactions. Use the execution strategy returned by '{getExecutionStrategyMethod}' to execute all the operations in the transaction as a retriable unit. @@ -1258,6 +1261,9 @@ The collection type '{2_collectionType}' being used for navigation '{1_entityType}.{0_navigation}' does not implement 'INotifyCollectionChanged'. Any entity type configured to use the '{changeTrackingStrategy}' change tracking strategy must use collections that implement 'INotifyCollectionChanged'. Consider using 'ObservableCollection<T>' for this. + + The LINQ expression '{expression}' could not be translated. Additional information: {details} See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. + The foreign key {foreignKeyProperties} on the entity type '{declaringEntityType}' cannot have a required dependent end since it is not unique. diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index 7f48d4d6559..4b33dfe6311 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -138,6 +138,32 @@ protected override Expression VisitExtension(Expression extensionExpression) protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { var method = methodCallExpression.Method; + + if (method.DeclaringType == typeof(EntityFrameworkQueryableExtensions)) + { + var source = Visit(methodCallExpression.Arguments[0]); + if (source is ShapedQueryExpression shapedQueryExpression) + { + var genericMethod = method.IsGenericMethod ? method.GetGenericMethodDefinition() : null; + switch (method.Name) + { + case nameof(EntityFrameworkQueryableExtensions.ExecuteDelete) + when genericMethod == EntityFrameworkQueryableExtensions.ExecuteDeleteMethodInfo: + return TranslateExecuteDelete(shapedQueryExpression) + ?? throw new InvalidOperationException( + CoreStrings.NonQueryTranslationFailedWithDetails( + methodCallExpression.Print(), TranslationErrorDetails)); + + case nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate) + when genericMethod == EntityFrameworkQueryableExtensions.ExecuteUpdateMethodInfo: + return TranslateExecuteUpdate(shapedQueryExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()) + ?? throw new InvalidOperationException( + CoreStrings.NonQueryTranslationFailedWithDetails( + methodCallExpression.Print(), TranslationErrorDetails)); + } + } + } + if (method.DeclaringType == typeof(Queryable) || method.DeclaringType == typeof(QueryableExtensions)) { @@ -1000,4 +1026,39 @@ protected abstract ShapedQueryExpression TranslateSelect( => null; #endregion Queryable collection support + + #region ExecuteUpdate/ExecuteDelete + + /// + /// Translates method + /// over the given source. + /// + /// The shaped query on which the operator is applied. + /// The non query after translation. + protected virtual Expression? TranslateExecuteDelete(ShapedQueryExpression source) + => throw new InvalidOperationException( + CoreStrings.ExecuteQueriesNotSupported( + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete), nameof(EntityFrameworkQueryableExtensions.ExecuteDeleteAsync))); + + /// + /// Translates + /// + /// method + /// over the given source. + /// + /// The shaped query on which the operator is applied. + /// + /// The lambda expression containing + /// + /// statements. + /// + /// The non query after translation. + protected virtual Expression? TranslateExecuteUpdate(ShapedQueryExpression source, LambdaExpression setPropertyCalls) + => throw new InvalidOperationException( + CoreStrings.ExecuteQueriesNotSupported( + nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate), nameof(EntityFrameworkQueryableExtensions.ExecuteUpdateAsync))); + + #endregion ExecuteUpdate/ExecuteDelete } diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs deleted file mode 100644 index 4936a20c9ab..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryFixture.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class FiltersInheritanceBulkUpdatesInMemoryFixture : InheritanceBulkUpdatesInMemoryFixture -{ - public override bool EnableFilters - => true; -} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs deleted file mode 100644 index 10749c47e34..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesInMemoryTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class FiltersInheritanceBulkUpdatesInMemoryTest(FiltersInheritanceBulkUpdatesInMemoryFixture fixture) - : FiltersInheritanceBulkUpdatesTestBase(fixture) -{ - [ConditionalFact] - public virtual void Check_all_tests_overridden() - => TestHelpers.AssertAllMethodsOverridden(GetType()); - - public override Task Delete_where_hierarchy(bool async) - => AssertTranslationFailed(() => base.Delete_where_hierarchy(async)); - - public override Task Delete_where_hierarchy_subquery(bool async) - => AssertTranslationFailed(() => base.Delete_where_hierarchy_subquery(async)); - - public override Task Delete_where_hierarchy_derived(bool async) - => AssertTranslationFailed(() => base.Delete_where_hierarchy_derived(async)); - - public override Task Delete_where_using_hierarchy(bool async) - => AssertTranslationFailed(() => base.Delete_where_using_hierarchy(async)); - - public override Task Delete_where_using_hierarchy_derived(bool async) - => AssertTranslationFailed(() => base.Delete_where_using_hierarchy_derived(async)); - - public override Task Delete_GroupBy_Where_Select_First(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First(async)); - - public override Task Delete_GroupBy_Where_Select_First_2(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_2(async)); - - public override Task Delete_GroupBy_Where_Select_First_3(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_3(async)); - - public override Task Update_base_type(bool async) - => AssertTranslationFailed(() => base.Update_base_type(async)); - - public override Task Update_base_type_with_OfType(bool async) - => AssertTranslationFailed(() => base.Update_base_type_with_OfType(async)); - - public override Task Update_where_hierarchy_subquery(bool async) - => AssertTranslationFailed(() => base.Update_where_hierarchy_subquery(async)); - - public override Task Update_base_property_on_derived_type(bool async) - => AssertTranslationFailed(() => base.Update_base_property_on_derived_type(async)); - - public override Task Update_derived_property_on_derived_type(bool async) - => AssertTranslationFailed(() => base.Update_derived_property_on_derived_type(async)); - - public override Task Update_base_and_derived_types(bool async) - => AssertTranslationFailed(() => base.Update_base_and_derived_types(async)); - - public override Task Update_where_using_hierarchy(bool async) - => AssertTranslationFailed(() => base.Update_where_using_hierarchy(async)); - - public override Task Update_where_using_hierarchy_derived(bool async) - => AssertTranslationFailed(() => base.Update_where_using_hierarchy_derived(async)); - - protected static async Task AssertTranslationFailed(Func query) - => Assert.Contains( - CoreStrings.TranslationFailed("")[48..], - (await Assert.ThrowsAsync(query)) - .Message); -} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs deleted file mode 100644 index 64ab5c7d1d6..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryFixture.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class InheritanceBulkUpdatesInMemoryFixture : InheritanceBulkUpdatesFixtureBase -{ - protected override ITestStoreFactory TestStoreFactory - => InMemoryTestStoreFactory.Instance; - - public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - { - // TODO: Fake transactions needed for real tests. - } - - public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder) - .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); -} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs deleted file mode 100644 index dc37c9a09b1..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesInMemoryTest.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class InheritanceBulkUpdatesInMemoryTest(InheritanceBulkUpdatesInMemoryFixture fixture) - : InheritanceBulkUpdatesTestBase(fixture) -{ - [ConditionalFact] - public virtual void Check_all_tests_overridden() - => TestHelpers.AssertAllMethodsOverridden(GetType()); - - public override Task Delete_where_hierarchy(bool async) - => AssertTranslationFailed(() => base.Delete_where_hierarchy(async)); - - public override Task Delete_where_hierarchy_subquery(bool async) - => AssertTranslationFailed(() => base.Delete_where_hierarchy_subquery(async)); - - public override Task Delete_where_hierarchy_derived(bool async) - => AssertTranslationFailed(() => base.Delete_where_hierarchy_derived(async)); - - public override Task Delete_where_using_hierarchy(bool async) - => AssertTranslationFailed(() => base.Delete_where_using_hierarchy(async)); - - public override Task Delete_where_using_hierarchy_derived(bool async) - => AssertTranslationFailed(() => base.Delete_where_using_hierarchy_derived(async)); - - public override Task Delete_GroupBy_Where_Select_First(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First(async)); - - public override Task Delete_GroupBy_Where_Select_First_2(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_2(async)); - - public override Task Delete_GroupBy_Where_Select_First_3(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_First_3(async)); - - public override Task Update_base_type(bool async) - => AssertTranslationFailed(() => base.Update_base_type(async)); - - public override Task Update_base_type_with_OfType(bool async) - => AssertTranslationFailed(() => base.Update_base_type_with_OfType(async)); - - public override Task Update_where_hierarchy_subquery(bool async) - => AssertTranslationFailed(() => base.Update_where_hierarchy_subquery(async)); - - public override Task Update_base_property_on_derived_type(bool async) - => AssertTranslationFailed(() => base.Update_base_property_on_derived_type(async)); - - public override Task Update_derived_property_on_derived_type(bool async) - => AssertTranslationFailed(() => base.Update_derived_property_on_derived_type(async)); - - public override Task Update_base_and_derived_types(bool async) - => AssertTranslationFailed(() => base.Update_base_and_derived_types(async)); - - public override Task Update_where_using_hierarchy(bool async) - => AssertTranslationFailed(() => base.Update_where_using_hierarchy(async)); - - public override Task Update_where_using_hierarchy_derived(bool async) - => AssertTranslationFailed(() => base.Update_where_using_hierarchy_derived(async)); - - public override Task Update_with_interface_in_property_expression(bool async) - => AssertTranslationFailed(() => base.Update_with_interface_in_property_expression(async)); - - public override Task Update_with_interface_in_EF_Property_in_property_expression(bool async) - => AssertTranslationFailed(() => base.Update_with_interface_in_EF_Property_in_property_expression(async)); - - protected static async Task AssertTranslationFailed(Func query) - => Assert.Contains( - CoreStrings.TranslationFailed("")[48..], - (await Assert.ThrowsAsync(query)) - .Message); -} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs deleted file mode 100644 index c8f541f6c58..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesInMemoryTest.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class NonSharedModelBulkUpdatesInMemoryTest : NonSharedModelBulkUpdatesTestBase -{ - protected override ITestStoreFactory TestStoreFactory - => InMemoryTestStoreFactory.Instance; - - public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - { - // TODO: Fake transactions needed for real tests. - } - - protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder) - .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); - - [ConditionalFact] - public virtual void Check_all_tests_overridden() - => TestHelpers.AssertAllMethodsOverridden(GetType()); - - public override Task Delete_aggregate_root_when_eager_loaded_owned_collection(bool async) - => AssertTranslationFailed(() => base.Delete_aggregate_root_when_eager_loaded_owned_collection(async)); - - public override Task Delete_with_owned_collection_and_non_natively_translatable_query(bool async) - => AssertTranslationFailed(() => base.Delete_with_owned_collection_and_non_natively_translatable_query(async)); - - public override Task Delete_aggregate_root_when_table_sharing_with_owned(bool async) - => AssertTranslationFailed(() => base.Delete_aggregate_root_when_table_sharing_with_owned(async)); - - public override Task Replace_ColumnExpression_in_column_setter(bool async) - => AssertTranslationFailed(() => base.Replace_ColumnExpression_in_column_setter(async)); - - public override Task Update_non_owned_property_on_entity_with_owned(bool async) - => AssertTranslationFailed(() => base.Update_non_owned_property_on_entity_with_owned(async)); - - public override Task Update_non_owned_property_on_entity_with_owned2(bool async) - => AssertTranslationFailed(() => base.Update_non_owned_property_on_entity_with_owned2(async)); - - public override Task Update_non_owned_property_on_entity_with_owned_in_join(bool async) - => AssertTranslationFailed(() => base.Update_non_owned_property_on_entity_with_owned_in_join(async)); - - public override Task Update_owned_and_non_owned_properties_with_table_sharing(bool async) - => AssertTranslationFailed(() => base.Update_owned_and_non_owned_properties_with_table_sharing(async)); - - public override Task Delete_entity_with_auto_include(bool async) - => AssertTranslationFailed(() => base.Delete_entity_with_auto_include(async)); - - public override Task Delete_predicate_based_on_optional_navigation(bool async) - => AssertTranslationFailed(() => base.Delete_predicate_based_on_optional_navigation(async)); - - public override Task Update_with_alias_uniquification_in_setter_subquery(bool async) - => AssertTranslationFailed(() => base.Update_with_alias_uniquification_in_setter_subquery(async)); - - protected static async Task AssertTranslationFailed(Func query) - => Assert.Contains( - CoreStrings.TranslationFailed("")[48..], - (await Assert.ThrowsAsync(query)) - .Message); -} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs deleted file mode 100644 index 56a0c01f595..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryFixture.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class NorthwindBulkUpdatesInMemoryFixture : NorthwindBulkUpdatesFixture - where TModelCustomizer : ITestModelCustomizer, new() -{ - protected override ITestStoreFactory TestStoreFactory - => InMemoryTestStoreFactory.Instance; - - protected override Type ContextType - => typeof(NorthwindInMemoryContext); - - public override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - { - // TODO: Fake transactions needed for real tests. - } - - public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder) - .ConfigureWarnings(e => e.Ignore(InMemoryEventId.TransactionIgnoredWarning)); -} diff --git a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs deleted file mode 100644 index 37c74c32f87..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesInMemoryTest.cs +++ /dev/null @@ -1,273 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.BulkUpdates; - -public class NorthwindBulkUpdatesInMemoryTest( - NorthwindBulkUpdatesInMemoryFixture fixture) - : NorthwindBulkUpdatesTestBase>(fixture) -{ - [ConditionalFact] - public virtual void Check_all_tests_overridden() - => TestHelpers.AssertAllMethodsOverridden(GetType()); - - public override Task Delete_non_entity_projection(bool async) - => AssertTranslationFailed(() => base.Delete_non_entity_projection(async)); - - public override Task Delete_non_entity_projection_2(bool async) - => AssertTranslationFailed(() => base.Delete_non_entity_projection_2(async)); - - public override Task Delete_non_entity_projection_3(bool async) - => AssertTranslationFailed(() => base.Delete_non_entity_projection_3(async)); - - public override Task Update_without_property_to_set_throws(bool async) - => AssertTranslationFailed(() => base.Update_without_property_to_set_throws(async)); - - public override Task Update_with_invalid_lambda_throws(bool async) - => AssertTranslationFailed(() => base.Update_with_invalid_lambda_throws(async)); - - public override Task Update_with_invalid_lambda_in_set_property_throws(bool async) - => AssertTranslationFailed(() => base.Update_with_invalid_lambda_in_set_property_throws(async)); - - public override Task Update_multiple_tables_throws(bool async) - => AssertTranslationFailed(() => base.Update_multiple_tables_throws(async)); - - public override Task Update_unmapped_property_throws(bool async) - => AssertTranslationFailed(() => base.Update_unmapped_property_throws(async)); - - public override Task Delete_Where_TagWith(bool async) - => AssertTranslationFailed(() => base.Delete_Where_TagWith(async)); - - public override Task Delete_Where(bool async) - => AssertTranslationFailed(() => base.Delete_Where(async)); - - public override Task Delete_Where_parameter(bool async) - => AssertTranslationFailed(() => base.Delete_Where_parameter(async)); - - public override Task Delete_Where_OrderBy(bool async) - => AssertTranslationFailed(() => base.Delete_Where_OrderBy(async)); - - public override Task Delete_Where_OrderBy_Skip(bool async) - => AssertTranslationFailed(() => base.Delete_Where_OrderBy_Skip(async)); - - public override Task Delete_Where_OrderBy_Take(bool async) - => AssertTranslationFailed(() => base.Delete_Where_OrderBy_Take(async)); - - public override Task Delete_Where_OrderBy_Skip_Take(bool async) - => AssertTranslationFailed(() => base.Delete_Where_OrderBy_Skip_Take(async)); - - public override Task Delete_Where_Skip(bool async) - => AssertTranslationFailed(() => base.Delete_Where_Skip(async)); - - public override Task Delete_Where_Take(bool async) - => AssertTranslationFailed(() => base.Delete_Where_Take(async)); - - public override Task Delete_Where_Skip_Take(bool async) - => AssertTranslationFailed(() => base.Delete_Where_Skip_Take(async)); - - public override Task Delete_Where_predicate_with_GroupBy_aggregate(bool async) - => AssertTranslationFailed(() => base.Delete_Where_predicate_with_GroupBy_aggregate(async)); - - public override Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) - => AssertTranslationFailed(() => base.Delete_Where_predicate_with_GroupBy_aggregate_2(async)); - - public override Task Delete_GroupBy_Where_Select(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select(async)); - - public override Task Delete_GroupBy_Where_Select_2(bool async) - => AssertTranslationFailed(() => base.Delete_GroupBy_Where_Select_2(async)); - - public override Task Delete_Where_Skip_Take_Skip_Take_causing_subquery(bool async) - => AssertTranslationFailed(() => base.Delete_Where_Skip_Take_Skip_Take_causing_subquery(async)); - - public override Task Delete_Where_Distinct(bool async) - => AssertTranslationFailed(() => base.Delete_Where_Distinct(async)); - - public override Task Delete_SelectMany(bool async) - => AssertTranslationFailed(() => base.Delete_SelectMany(async)); - - public override Task Delete_SelectMany_subquery(bool async) - => AssertTranslationFailed(() => base.Delete_SelectMany_subquery(async)); - - public override Task Delete_Where_using_navigation(bool async) - => AssertTranslationFailed(() => base.Delete_Where_using_navigation(async)); - - public override Task Delete_Where_using_navigation_2(bool async) - => AssertTranslationFailed(() => base.Delete_Where_using_navigation_2(async)); - - public override Task Delete_Union(bool async) - => AssertTranslationFailed(() => base.Delete_Union(async)); - - public override Task Delete_Concat(bool async) - => AssertTranslationFailed(() => base.Delete_Concat(async)); - - public override Task Delete_Intersect(bool async) - => AssertTranslationFailed(() => base.Delete_Intersect(async)); - - public override Task Delete_Except(bool async) - => AssertTranslationFailed(() => base.Delete_Except(async)); - - public override Task Delete_Where_optional_navigation_predicate(bool async) - => AssertTranslationFailed(() => base.Delete_Where_optional_navigation_predicate(async)); - - public override Task Delete_with_join(bool async) - => AssertTranslationFailed(() => base.Delete_with_join(async)); - - public override Task Delete_with_left_join(bool async) - => AssertTranslationFailed(() => base.Delete_with_left_join(async)); - - public override Task Delete_with_cross_join(bool async) - => AssertTranslationFailed(() => base.Delete_with_cross_join(async)); - - public override Task Delete_with_cross_apply(bool async) - => AssertTranslationFailed(() => base.Delete_with_cross_apply(async)); - - public override Task Delete_with_outer_apply(bool async) - => AssertTranslationFailed(() => base.Delete_with_outer_apply(async)); - - public override Task Update_Where_set_constant_TagWith(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_constant_TagWith(async)); - - public override Task Update_Where_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_constant(async)); - - public override Task Update_Where_parameter_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_parameter_set_constant(async)); - - public override Task Update_Where_set_parameter(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_parameter(async)); - - public override Task Update_Where_set_parameter_from_closure_array(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_parameter_from_closure_array(async)); - - public override Task Update_Where_set_parameter_from_inline_list(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_parameter_from_inline_list(async)); - - public override Task Update_Where_set_parameter_from_multilevel_property_access(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_parameter_from_multilevel_property_access(async)); - - public override Task Update_Where_Skip_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_Skip_set_constant(async)); - - public override Task Update_Where_Take_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_Take_set_constant(async)); - - public override Task Update_Where_Skip_Take_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_Skip_Take_set_constant(async)); - - public override Task Update_Where_OrderBy_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_OrderBy_set_constant(async)); - - public override Task Update_Where_OrderBy_Skip_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_OrderBy_Skip_set_constant(async)); - - public override Task Update_Where_OrderBy_Take_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_OrderBy_Take_set_constant(async)); - - public override Task Update_Where_OrderBy_Skip_Take_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_OrderBy_Skip_Take_set_constant(async)); - - public override Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(async)); - - public override Task Update_Where_GroupBy_aggregate_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_GroupBy_aggregate_set_constant(async)); - - public override Task Update_Where_GroupBy_First_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_GroupBy_First_set_constant(async)); - - public override Task Update_Where_GroupBy_First_set_constant_2(bool async) - => AssertTranslationFailed(() => base.Update_Where_GroupBy_First_set_constant_2(async)); - - public override Task Update_Where_GroupBy_First_set_constant_3(bool async) - // Translation of EF.Property fails before getting to ExecuteUpdate. - => Assert.ThrowsAsync( - () => base.Update_Where_GroupBy_First_set_constant_3(async)); - - public override Task Update_Where_Distinct_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_Distinct_set_constant(async)); - - public override Task Update_Where_using_navigation_set_null(bool async) - => AssertTranslationFailed(() => base.Update_Where_using_navigation_set_null(async)); - - public override Task Update_Where_using_navigation_2_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_using_navigation_2_set_constant(async)); - - public override Task Update_Where_SelectMany_set_null(bool async) - => AssertTranslationFailed(() => base.Update_Where_SelectMany_set_null(async)); - - public override Task Update_Where_set_property_plus_constant(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_property_plus_constant(async)); - - public override Task Update_Where_set_property_plus_parameter(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_property_plus_parameter(async)); - - public override Task Update_Where_set_property_plus_property(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_property_plus_property(async)); - - public override Task Update_Where_set_constant_using_ef_property(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_constant_using_ef_property(async)); - - public override Task Update_Where_set_null(bool async) - => AssertTranslationFailed(() => base.Update_Where_set_null(async)); - - public override Task Update_Where_multiple_set(bool async) - => AssertTranslationFailed(() => base.Update_Where_multiple_set(async)); - - public override Task Update_Union_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Union_set_constant(async)); - - public override Task Update_Concat_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Concat_set_constant(async)); - - public override Task Update_Except_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Except_set_constant(async)); - - public override Task Update_Intersect_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_Intersect_set_constant(async)); - - public override Task Update_with_join_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_join_set_constant(async)); - - public override Task Update_with_left_join_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_left_join_set_constant(async)); - - public override Task Update_with_cross_join_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_cross_join_set_constant(async)); - - public override Task Update_with_cross_apply_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_cross_apply_set_constant(async)); - - public override Task Update_with_outer_apply_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_outer_apply_set_constant(async)); - - public override Task Update_with_cross_join_left_join_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_cross_join_left_join_set_constant(async)); - - public override Task Update_with_cross_join_cross_apply_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_cross_join_cross_apply_set_constant(async)); - - public override Task Update_with_cross_join_outer_apply_set_constant(bool async) - => AssertTranslationFailed(() => base.Update_with_cross_join_outer_apply_set_constant(async)); - - public override Task Update_Where_SelectMany_subquery_set_null(bool async) - => AssertTranslationFailed(() => base.Update_Where_SelectMany_subquery_set_null(async)); - - public override Task Update_Where_Join_set_property_from_joined_single_result_table(bool async) - => AssertTranslationFailed(() => base.Update_Where_Join_set_property_from_joined_single_result_table(async)); - - public override Task Update_Where_Join_set_property_from_joined_table(bool async) - => AssertTranslationFailed(() => base.Update_Where_Join_set_property_from_joined_table(async)); - - public override Task Update_Where_Join_set_property_from_joined_single_result_scalar(bool async) - => AssertTranslationFailed(() => base.Update_Where_Join_set_property_from_joined_single_result_scalar(async)); - - public override Task Update_with_two_inner_joins(bool async) - => AssertTranslationFailed(() => base.Update_with_two_inner_joins(async)); - - protected static async Task AssertTranslationFailed(Func query) - => Assert.Contains( - CoreStrings.TranslationFailed("")[48..], - (await Assert.ThrowsAsync(query)) - .Message); -} diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index 96fe0d54875..51e6af0b2ad 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -19,6 +19,11 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(ConferencePlannerTestBase<>), typeof(ManyToManyQueryTestBase<>), typeof(ComplexTypeBulkUpdatesTestBase<>), + typeof(BulkUpdatesTestBase<>), + typeof(FiltersInheritanceBulkUpdatesTestBase<>), + typeof(InheritanceBulkUpdatesTestBase<>), + typeof(NonSharedModelBulkUpdatesTestBase), + typeof(NorthwindBulkUpdatesTestBase<>), }; protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs index 5eff7e3ee4c..72edb033356 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/ComplexTypeBulkUpdatesRelationalTestBase.cs @@ -27,7 +27,7 @@ public override Task Update_projected_complex_type_via_OrderBy_Skip(bool async) protected static async Task AssertTranslationFailed(string details, Func query) => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + CoreStrings.NonQueryTranslationFailedWithDetails("", details)[21..], (await Assert.ThrowsAsync(query)).Message); private void ClearLog() diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs index 9825d26ae50..ca28343d386 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesRelationalTestBase.cs @@ -41,7 +41,7 @@ public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) protected static async Task AssertTranslationFailed(string details, Func query) => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + CoreStrings.NonQueryTranslationFailedWithDetails("", details)[21..], (await Assert.ThrowsAsync(query)).Message); protected abstract void ClearLog(); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs index 44a60ff868f..5e0856e8699 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesRelationalTestBase.cs @@ -41,7 +41,7 @@ public virtual Task Update_where_keyless_entity_mapped_to_sql_query(bool async) protected static async Task AssertTranslationFailed(string details, Func query) => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + CoreStrings.NonQueryTranslationFailedWithDetails("", details)[21..], (await Assert.ThrowsAsync(query)).Message); protected virtual void ClearLog() diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs index a8221b1b893..c3dc5ad63d8 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs @@ -87,7 +87,7 @@ await AssertUpdate( protected static async Task AssertTranslationFailedWithDetails(Func query, string details) => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + CoreStrings.NonQueryTranslationFailedWithDetails("", details)[21..], (await Assert.ThrowsAsync(query)) .Message); diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs index 4e269f706df..8d19ed1b153 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesRelationalTestBase.cs @@ -106,7 +106,7 @@ FROM [Customers] protected static async Task AssertTranslationFailed(string details, Func query) => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails("", details)[21..], + CoreStrings.NonQueryTranslationFailedWithDetails("", details)[21..], (await Assert.ThrowsAsync(query)).Message); protected string NormalizeDelimitersInRawString(string sql) diff --git a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs index 2303856c50e..353419c20ec 100644 --- a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs @@ -51,7 +51,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, UseTransaction, async context => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails( + CoreStrings.NonQueryTranslationFailedWithDetails( "", RelationalStrings.ExecuteOperationOnEntitySplitting("ExecuteDelete", "MeterReading"))[21..], (await Assert.ThrowsAsync( async () => diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index 293a1cec9ab..04bec022b59 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -768,7 +768,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, UseTransaction, async context => Assert.Contains( - RelationalStrings.NonQueryTranslationFailedWithDetails( + CoreStrings.NonQueryTranslationFailedWithDetails( "", RelationalStrings.ExecuteDeleteOnTableSplitting("Vehicles"))[21..], (await Assert.ThrowsAsync( async () => diff --git a/test/EFCore.Tests/Query/QueryProviderTest.cs b/test/EFCore.Tests/Query/QueryProviderTest.cs index 082ad4bf039..44de09533fe 100644 --- a/test/EFCore.Tests/Query/QueryProviderTest.cs +++ b/test/EFCore.Tests/Query/QueryProviderTest.cs @@ -5,6 +5,29 @@ namespace Microsoft.EntityFrameworkCore.Query; public class QueryProviderTest { + [ConditionalFact] + public async Task ExecuteUpdate_and_ExecuteDelete_throw_when_provider_does_not_implement() + { + using var context = new TestContext(); + var set = context.TestEntities; + + Assert.Equal( + CoreStrings.ExecuteQueriesNotSupported("ExecuteUpdate", "ExecuteUpdateAsync"), + Assert.Throws(() => set.ExecuteUpdate(s => s.SetProperty(e => e.Id, 1))).Message); + + Assert.Equal( + CoreStrings.ExecuteQueriesNotSupported("ExecuteUpdate", "ExecuteUpdateAsync"), + (await Assert.ThrowsAsync(() => set.ExecuteUpdateAsync(s => s.SetProperty(e => e.Id, 1)))).Message); + + Assert.Equal( + CoreStrings.ExecuteQueriesNotSupported("ExecuteDelete", "ExecuteDeleteAsync"), + Assert.Throws(() => set.ExecuteDelete()).Message); + + Assert.Equal( + CoreStrings.ExecuteQueriesNotSupported("ExecuteDelete", "ExecuteDeleteAsync"), + (await Assert.ThrowsAsync(() => set.ExecuteDeleteAsync())).Message); + } + [ConditionalFact] public void Non_generic_ExecuteQuery_does_not_throw() {