diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index f32420c4660..1cbec0576f6 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -83,7 +83,7 @@ protected virtual void GenerateRootCommand(Expression queryExpression) switch (queryExpression) { case SelectExpression selectExpression: - GenerateTagsHeaderComment(selectExpression); + GenerateTagsHeaderComment(selectExpression.Tags); if (selectExpression.IsNonComposedFromSql()) { @@ -95,6 +95,16 @@ protected virtual void GenerateRootCommand(Expression queryExpression) } break; + case UpdateExpression updateExpression: + GenerateTagsHeaderComment(updateExpression.Tags); + VisitUpdate(updateExpression); + break; + + case DeleteExpression deleteExpression: + GenerateTagsHeaderComment(deleteExpression.Tags); + VisitDelete(deleteExpression); + break; + default: base.Visit(queryExpression); break; @@ -117,6 +127,7 @@ protected virtual IRelationalCommandBuilder Sql /// Generates the head comment for tags. /// /// A select expression to generate tags for. + [Obsolete("Use the method which takes tags instead.")] protected virtual void GenerateTagsHeaderComment(SelectExpression selectExpression) { if (selectExpression.Tags.Count > 0) @@ -130,6 +141,23 @@ protected virtual void GenerateTagsHeaderComment(SelectExpression selectExpressi } } + /// + /// Generates the head comment for tags. + /// + /// A set of tags to print as comment. + protected virtual void GenerateTagsHeaderComment(ISet tags) + { + if (tags.Count > 0) + { + foreach (var tag in tags) + { + _relationalCommandBuilder.AppendLines(_sqlGenerationHelper.GenerateComment(tag)); + } + + _relationalCommandBuilder.AppendLine(); + } + } + /// protected override Expression VisitSqlFragment(SqlFragmentExpression sqlFragmentExpression) { diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 99afa6f307e..6ba42322788 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1079,13 +1079,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp var clrType = entityType.ClrType; var entityParameter = Expression.Parameter(clrType); Expression predicateBody; - //if (pk.Properties.Count == 1) - //{ - // predicateBody = Expression.Call( - // EnumerableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter); - //} - //else - //{ var innerParameter = Expression.Parameter(clrType); predicateBody = Expression.Call( QueryableMethods.AnyWithPredicate.MakeGenericMethod(clrType), @@ -1093,7 +1086,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp Expression.Quote(Expression.Lambda( Infrastructure.ExpressionExtensions.CreateEqualsExpression(innerParameter, entityParameter), innerParameter))); - //} var newSource = Expression.Call( QueryableMethods.Where.MakeGenericMethod(clrType), diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index 733eab08943..103c641f8ae 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -56,11 +56,24 @@ protected override Expression VisitExtension(Expression extensionExpression) /// An expression which executes a non-query operation. protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression) { + // Apply tags + var innerExpression = nonQueryExpression.Expression; + switch (innerExpression) + { + case UpdateExpression updateExpression: + innerExpression = updateExpression.ApplyTags(_tags); + break; + + case DeleteExpression deleteExpression: + innerExpression = deleteExpression.ApplyTags(_tags); + break; + } + var relationalCommandCache = new RelationalCommandCache( Dependencies.MemoryCache, RelationalDependencies.QuerySqlGeneratorFactory, RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - nonQueryExpression.Expression, + innerExpression, _useRelationalNulls); return Expression.Call( diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 83f40952443..a7e46feef10 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; @@ -606,8 +605,82 @@ protected override Expression VisitExtension(Expression extensionExpression) return new EntityReferenceExpression(entityShaperExpression); case ProjectionBindingExpression projectionBindingExpression: - return ((SelectExpression)projectionBindingExpression.QueryExpression) - .GetProjection(projectionBindingExpression); + return Visit(((SelectExpression)projectionBindingExpression.QueryExpression) + .GetProjection(projectionBindingExpression)); + + case ShapedQueryExpression shapedQueryExpression: + if (shapedQueryExpression.ResultCardinality == ResultCardinality.Enumerable) + { + return QueryCompilationContext.NotTranslatedExpression; + } + + var shaperExpression = shapedQueryExpression.ShaperExpression; + ProjectionBindingExpression? mappedProjectionBindingExpression = null; + + var innerExpression = shaperExpression; + Type? convertedType = null; + if (shaperExpression is UnaryExpression unaryExpression + && unaryExpression.NodeType == ExpressionType.Convert) + { + convertedType = unaryExpression.Type; + innerExpression = unaryExpression.Operand; + } + + if (innerExpression is EntityShaperExpression ese + && (convertedType == null + || convertedType.IsAssignableFrom(ese.Type))) + { + return new EntityReferenceExpression(shapedQueryExpression.UpdateShaperExpression(innerExpression)); + } + + if (innerExpression is ProjectionBindingExpression pbe + && (convertedType == null + || convertedType.MakeNullable() == innerExpression.Type)) + { + mappedProjectionBindingExpression = pbe; + } + + if (mappedProjectionBindingExpression == null + && shaperExpression is BlockExpression blockExpression + && blockExpression.Expressions.Count == 2 + && blockExpression.Expressions[0] is BinaryExpression binaryExpression + && binaryExpression.NodeType == ExpressionType.Assign + && binaryExpression.Right is ProjectionBindingExpression pbe2) + { + mappedProjectionBindingExpression = pbe2; + } + + if (mappedProjectionBindingExpression == null) + { + return QueryCompilationContext.NotTranslatedExpression; + } + + var subquery = (SelectExpression)shapedQueryExpression.QueryExpression; + var projection = subquery.GetProjection(mappedProjectionBindingExpression); + if (projection is not SqlExpression sqlExpression) + { + return QueryCompilationContext.NotTranslatedExpression; + } + + if (subquery.Tables.Count == 0) + { + return sqlExpression; + } + + subquery.ReplaceProjection(new List { sqlExpression }); + subquery.ApplyProjection(); + + SqlExpression scalarSubqueryExpression = new ScalarSubqueryExpression(subquery); + + if (shapedQueryExpression.ResultCardinality == ResultCardinality.SingleOrDefault + && !shaperExpression.Type.IsNullableType()) + { + scalarSubqueryExpression = _sqlExpressionFactory.Coalesce( + scalarSubqueryExpression, + (SqlExpression)Visit(shaperExpression.Type.GetDefaultValueConstant())); + } + + return scalarSubqueryExpression; default: return QueryCompilationContext.NotTranslatedExpression; @@ -632,7 +705,7 @@ protected override Expression VisitMember(MemberExpression memberExpression) var innerExpression = Visit(memberExpression.Expression); return TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member)) - ?? (TranslationFailed(memberExpression.Expression, Visit(memberExpression.Expression), out var sqlInnerExpression) + ?? (TranslationFailed(memberExpression.Expression, innerExpression, out var sqlInnerExpression) ? QueryCompilationContext.NotTranslatedExpression : Dependencies.MemberTranslatorProvider.Translate( sqlInnerExpression, memberExpression.Member, memberExpression.Type, _queryCompilationContext.Logger)) @@ -662,6 +735,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } + // EF.Default + if (methodCallExpression.Method.IsEFDefaultMethod()) + { + return new SqlFragmentExpression("DEFAULT"); + } + var method = methodCallExpression.Method; var arguments = methodCallExpression.Arguments; EnumerableExpression? enumerableExpression = null; @@ -792,9 +871,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp : method; var enumerableSource = Visit(arguments[0]); - if (enumerableSource is EnumerableExpression) + if (enumerableSource is EnumerableExpression ee) { - enumerableExpression = (EnumerableExpression)enumerableSource; + enumerableExpression = ee; switch (method.Name) { case nameof(Queryable.AsQueryable) @@ -928,10 +1007,10 @@ when QueryableMethods.IsSumWithSelector(genericMethod): && !skipVisitChildren) { var @object = Visit(methodCallExpression.Object); - if (@object is EnumerableExpression) + if (@object is EnumerableExpression eeo) { // This is safe since if enumerableExpression is non-null then it was static method - enumerableExpression = (EnumerableExpression)@object; + enumerableExpression = eeo; } else if (TranslationFailed(methodCallExpression.Object, @object, out sqlObject)) { @@ -944,7 +1023,7 @@ when QueryableMethods.IsSumWithSelector(genericMethod): { var argument = arguments[i]; var visitedArgument = Visit(argument); - if (visitedArgument is EnumerableExpression) + if (visitedArgument is EnumerableExpression eea) { if (enumerableExpression != null) { @@ -952,7 +1031,7 @@ when QueryableMethods.IsSumWithSelector(genericMethod): break; } - enumerableExpression = (EnumerableExpression)visitedArgument; + enumerableExpression = eea; continue; } @@ -1009,83 +1088,10 @@ when QueryableMethods.IsSumWithSelector(genericMethod): // Subquery case var subqueryTranslation = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); - if (subqueryTranslation != null) - { - if (subqueryTranslation.ResultCardinality == ResultCardinality.Enumerable) - { - return QueryCompilationContext.NotTranslatedExpression; - } - - var shaperExpression = subqueryTranslation.ShaperExpression; - ProjectionBindingExpression? mappedProjectionBindingExpression = null; - - var innerExpression = shaperExpression; - Type? convertedType = null; - if (shaperExpression is UnaryExpression unaryExpression - && unaryExpression.NodeType == ExpressionType.Convert) - { - convertedType = unaryExpression.Type; - innerExpression = unaryExpression.Operand; - } - if (innerExpression is EntityShaperExpression ese - && (convertedType == null - || convertedType.IsAssignableFrom(ese.Type))) - { - return new EntityReferenceExpression(subqueryTranslation.UpdateShaperExpression(innerExpression)); - } - - if (innerExpression is ProjectionBindingExpression pbe - && (convertedType == null - || convertedType.MakeNullable() == innerExpression.Type)) - { - mappedProjectionBindingExpression = pbe; - } - - if (mappedProjectionBindingExpression == null - && shaperExpression is BlockExpression blockExpression - && blockExpression.Expressions.Count == 2 - && blockExpression.Expressions[0] is BinaryExpression binaryExpression - && binaryExpression.NodeType == ExpressionType.Assign - && binaryExpression.Right is ProjectionBindingExpression pbe2) - { - mappedProjectionBindingExpression = pbe2; - } - - if (mappedProjectionBindingExpression == null) - { - return QueryCompilationContext.NotTranslatedExpression; - } - - var subquery = (SelectExpression)subqueryTranslation.QueryExpression; - var projection = subquery.GetProjection(mappedProjectionBindingExpression); - if (projection is not SqlExpression sqlExpression) - { - return QueryCompilationContext.NotTranslatedExpression; - } - - if (subquery.Tables.Count == 0) - { - return sqlExpression; - } - - subquery.ReplaceProjection(new List { sqlExpression }); - subquery.ApplyProjection(); - - SqlExpression scalarSubqueryExpression = new ScalarSubqueryExpression(subquery); - - if (subqueryTranslation.ResultCardinality == ResultCardinality.SingleOrDefault - && !shaperExpression.Type.IsNullableType()) - { - scalarSubqueryExpression = _sqlExpressionFactory.Coalesce( - scalarSubqueryExpression, - (SqlExpression)Visit(shaperExpression.Type.GetDefaultValueConstant())); - } - - return scalarSubqueryExpression; - } - - return QueryCompilationContext.NotTranslatedExpression; + return subqueryTranslation == null + ? QueryCompilationContext.NotTranslatedExpression + : Visit(subqueryTranslation); } /// @@ -1394,12 +1400,7 @@ private static EnumerableExpression ProcessSelector(EnumerableExpression enumera { var lambdaBody = RemapLambda(enumerableExpression, lambdaExpression); var predicate = TranslateInternal(lambdaBody); - if (predicate == null) - { - return null; - } - - return enumerableExpression.ApplyPredicate(predicate); + return predicate == null ? null : enumerableExpression.ApplyPredicate(predicate); } private static Expression TryRemoveImplicitConvert(Expression expression) diff --git a/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs index 1af829df28e..b46b336fd08 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/DeleteExpression.cs @@ -19,11 +19,22 @@ public sealed class DeleteExpression : Expression, IPrintableExpression /// A table on which the delete operation is being applied. /// A select expression which is used to determine which rows to delete. public DeleteExpression(TableExpression table, SelectExpression selectExpression) + : this(table, selectExpression, new HashSet()) + { + } + + private DeleteExpression(TableExpression table, SelectExpression selectExpression, ISet tags) { Table = table; SelectExpression = selectExpression; + Tags = tags; } + /// + /// The list of tags applied to this . + /// + public ISet Tags { get; } + /// /// The table on which the delete operation is being applied. /// @@ -34,6 +45,13 @@ public DeleteExpression(TableExpression table, SelectExpression selectExpression /// public SelectExpression SelectExpression { get; } + /// + /// Applies a given set of tags. + /// + /// A list of tags to apply. + public DeleteExpression ApplyTags(ISet tags) + => new(Table, SelectExpression, tags); + /// public override Type Type => typeof(object); @@ -58,12 +76,17 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// This expression if no children changed, or an expression with the updated children. public DeleteExpression Update(SelectExpression selectExpression) => selectExpression != SelectExpression - ? new DeleteExpression(Table, selectExpression) + ? new DeleteExpression(Table, selectExpression, Tags) : this; /// public void Print(ExpressionPrinter expressionPrinter) { + foreach (var tag in Tags) + { + expressionPrinter.Append($"-- {tag}"); + } + expressionPrinter.AppendLine(); expressionPrinter.AppendLine($"DELETE FROM {Table.Name} AS {Table.Alias}"); expressionPrinter.Visit(SelectExpression); } diff --git a/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs index 7a41ae30dca..2260a28aa72 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/UpdateExpression.cs @@ -21,12 +21,24 @@ public sealed class UpdateExpression : Expression, IPrintableExpression /// A select expression which is used to determine which rows to update and to get data from additional tables. /// A list of which specifies columns and their corresponding values to update. public UpdateExpression(TableExpression table, SelectExpression selectExpression, IReadOnlyList setColumnValues) + : this(table, selectExpression, setColumnValues, new HashSet()) + { + } + + private UpdateExpression( + TableExpression table, SelectExpression selectExpression, IReadOnlyList setColumnValues, ISet tags) { Table = table; SelectExpression = selectExpression; SetColumnValues = setColumnValues; + Tags = tags; } + /// + /// The list of tags applied to this . + /// + public ISet Tags { get; } + /// /// The table on which the update operation is being applied. /// @@ -42,6 +54,13 @@ public UpdateExpression(TableExpression table, SelectExpression selectExpression /// public IReadOnlyList SetColumnValues { get; } + /// + /// Applies a given set of tags. + /// + /// A list of tags to apply. + public UpdateExpression ApplyTags(ISet tags) + => new(Table, SelectExpression, SetColumnValues, tags); + /// public override Type Type => typeof(object); @@ -89,12 +108,17 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// This expression if no children changed, or an expression with the updated children. public UpdateExpression Update(SelectExpression selectExpression, IReadOnlyList setColumnValues) => selectExpression != SelectExpression || !SetColumnValues.SequenceEqual(setColumnValues) - ? new UpdateExpression(Table, selectExpression, setColumnValues) + ? new UpdateExpression(Table, selectExpression, setColumnValues, Tags) : this; /// public void Print(ExpressionPrinter expressionPrinter) { + foreach (var tag in Tags) + { + expressionPrinter.Append($"-- {tag}"); + } + expressionPrinter.AppendLine(); expressionPrinter.AppendLine($"UPDATE {Table.Name} AS {Table.Alias}"); expressionPrinter.AppendLine("SET"); using (expressionPrinter.Indent()) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 1eaf3487b90..8e51fc612da 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -112,7 +112,7 @@ private UpdateExpression VisitUpdate(UpdateExpression updateExpression) return selectExpression != updateExpression.SelectExpression || setColumnValues != null - ? new UpdateExpression(updateExpression.Table, selectExpression, setColumnValues ?? updateExpression.SetColumnValues) + ? updateExpression.Update(selectExpression, setColumnValues ?? updateExpression.SetColumnValues) : updateExpression; } diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs index 14e2b35fd63..82e837b30f6 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs @@ -37,6 +37,12 @@ public static string AggregateOperationNotSupported(object? aggregateOperator, o public static string ApplyNotSupported => GetString("ApplyNotSupported"); + /// + /// Translating this operation requires the 'DEFAULT', which is not supported on SQLite. + /// + public static string DefaultNotSupported + => GetString("DefaultNotSupported"); + /// /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different SRIDs. /// @@ -74,7 +80,7 @@ public static string SequencesNotSupported => GetString("SequencesNotSupported"); /// - /// SQLite does not support stored procedures. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + /// SQLite does not support stored procedures, but one has been configured on entity type '{entityType}'. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. /// public static string StoredProceduresNotSupported(object? entityType) => string.Format( diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx index 89d12f3924a..f18928093f5 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx @@ -123,6 +123,9 @@ Translating this query requires the SQL APPLY operation, which is not supported on SQLite. + + Translating this operation requires the 'DEFAULT', which is not supported on SQLite. + '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different SRIDs. @@ -201,4 +204,4 @@ SQLite does not support stored procedures, but one has been configured on entity type '{entityType}'. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. - + \ No newline at end of file diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 665b25159d5..4977e4875f7 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Internal; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; @@ -211,6 +212,24 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return visitedExpression; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + // EF.Default + if (methodCallExpression.Method.IsEFDefaultMethod()) + { + AddTranslationErrorDetails(SqliteStrings.DefaultNotSupported); + return QueryCompilationContext.NotTranslatedExpression; + } + + return base.VisitMethodCall(methodCallExpression); + } + private static Type? GetProviderType(SqlExpression? expression) => expression == null ? null diff --git a/src/EFCore/EF.cs b/src/EFCore/EF.cs index 57b650f9251..6129242cd82 100644 --- a/src/EFCore/EF.cs +++ b/src/EFCore/EF.cs @@ -17,6 +17,9 @@ public static partial class EF internal static readonly MethodInfo PropertyMethod = typeof(EF).GetTypeInfo().GetDeclaredMethod(nameof(Property))!; + internal static readonly MethodInfo DefaultMethod + = typeof(EF).GetTypeInfo().GetDeclaredMethod(nameof(Default))!; + /// /// This flag is set to when code is being run from a design-time tool, such /// as "dotnet ef" or one of the Package Manager Console PowerShell commands "Add-Migration", "Update-Database", etc. @@ -55,6 +58,20 @@ public static TProperty Property( [NotParameterized] string propertyName) => throw new InvalidOperationException(CoreStrings.PropertyMethodInvoked); + /// + /// Used set a property to its default value within or + /// . + /// + /// + /// Depending on how the property is configured, this may be , or another value defined via + /// or similar. + /// + /// The type of the property being set. + /// The default value of the property. + public static T Default() + // TODO: Update exception message + => throw new InvalidOperationException(CoreStrings.DefaultMethodInvoked); + /// /// Provides CLR methods that get translated to database functions when used in LINQ to Entities queries. /// Calling these methods in other contexts (e.g. LINQ to Objects) will throw a . diff --git a/src/EFCore/Infrastructure/MethodInfoExtensions.cs b/src/EFCore/Infrastructure/MethodInfoExtensions.cs index b19bd5da776..644342577a9 100644 --- a/src/EFCore/Infrastructure/MethodInfoExtensions.cs +++ b/src/EFCore/Infrastructure/MethodInfoExtensions.cs @@ -25,11 +25,24 @@ public static class MethodInfoExtensions /// /// The method. /// if the method is ; otherwise. - public static bool IsEFPropertyMethod(this MethodInfo? methodInfo) - => Equals(methodInfo, EF.PropertyMethod) - // fallback to string comparison because MethodInfo.Equals is not - // always true in .NET Native even if methods are the same - || methodInfo?.IsGenericMethod == true - && methodInfo.Name == nameof(EF.Property) - && methodInfo.DeclaringType?.FullName == EFTypeName; + public static bool IsEFPropertyMethod(this MethodInfo methodInfo) + => methodInfo.IsGenericMethod + && (Equals(methodInfo.GetGenericMethodDefinition(), EF.PropertyMethod) + // fallback to string comparison because MethodInfo.Equals is not + // always true in .NET Native even if methods are the same + || (methodInfo.Name == nameof(EF.Property) + && methodInfo.DeclaringType?.FullName == EFTypeName)); + + /// + /// Returns if the given method is . + /// + /// The method. + /// if the method is ; otherwise. + public static bool IsEFDefaultMethod(this MethodInfo methodInfo) + => methodInfo.IsGenericMethod + && (Equals(methodInfo.GetGenericMethodDefinition(), EF.DefaultMethod) + // fallback to string comparison because MethodInfo.Equals is not + // always true in .NET Native even if methods are the same + || (methodInfo.Name == nameof(EF.DefaultMethod) + && methodInfo.DeclaringType?.FullName == EFTypeName)); } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 27f767ea761..de9d3009197 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -598,6 +598,12 @@ public static string DebugViewQueryStringError(object? message) GetString("DebugViewQueryStringError", nameof(message)), message); + /// + /// The EF.Default<T> property may only be used within Entity Framework ExecuteUpdate method. + /// + public static string DefaultMethodInvoked + => GetString("DefaultMethodInvoked"); + /// /// The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported not on properties making up the foreign key. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 0ed95bdb7cf..32a8f8bbfab 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -336,6 +336,9 @@ Error creating query string: {message}. + + The EF.Default<T> property may only be used within Entity Framework ExecuteUpdate method. + The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported not on properties making up the foreign key. diff --git a/src/EFCore/Query/EvaluatableExpressionFilter.cs b/src/EFCore/Query/EvaluatableExpressionFilter.cs index d3a2d0d9bf8..1dbb572dfc3 100644 --- a/src/EFCore/Query/EvaluatableExpressionFilter.cs +++ b/src/EFCore/Query/EvaluatableExpressionFilter.cs @@ -81,7 +81,8 @@ public virtual bool IsEvaluatableExpression(Expression expression, IModel model) || Equals(method, RandomNextNoArgs) || Equals(method, RandomNextOneArg) || Equals(method, RandomNextTwoArgs) - || method.DeclaringType == typeof(DbFunctionsExtensions)) + || method.DeclaringType == typeof(DbFunctionsExtensions) + || method.IsEFDefaultMethod()) { return false; } diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index 121f3574f1b..4b90c04448f 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -153,10 +153,7 @@ when QueryableMethods.IsAverageWithSelector(method): var source2 = Visit(methodCallExpression.Arguments[1]); if (source2 is ShapedQueryExpression innerShapedQueryExpression) { - return CheckTranslated( - TranslateConcat( - shapedQueryExpression, - innerShapedQueryExpression)); + return CheckTranslated(TranslateConcat(shapedQueryExpression, innerShapedQueryExpression)); } break; @@ -207,10 +204,7 @@ when QueryableMethods.IsAverageWithSelector(method): var source2 = Visit(methodCallExpression.Arguments[1]); if (source2 is ShapedQueryExpression innerShapedQueryExpression) { - return CheckTranslated( - TranslateExcept( - shapedQueryExpression, - innerShapedQueryExpression)); + return CheckTranslated(TranslateExcept(shapedQueryExpression, innerShapedQueryExpression)); } break; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs index 5e85a43743c..7011a2f92d2 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs @@ -21,7 +21,7 @@ public virtual Task Delete_where_hierarchy(bool async) ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"), rowsAffectedCount: 1); - [ConditionalTheory(Skip = "Issue#28524")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_where_hierarchy_subquery(bool async) => AssertDelete( @@ -53,6 +53,35 @@ public virtual Task Delete_where_using_hierarchy_derived(bool async) ss => ss.Set().Where(e => e.Animals.OfType().Where(a => a.CountryId > 0).Count() > 0), rowsAffectedCount: 1); + [ConditionalTheory(Skip = "Issue#28525")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_First(bool async) + => AssertDelete( + async, + ss => ss.Set() + .GroupBy(e => e.CountryId) + .Where(g => g.Count() < 3) + .Select(g => g.First()), + rowsAffectedCount: 1); + + [ConditionalTheory(Skip = "Issue#26753")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_First_2(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(e => e == ss.Set().GroupBy(e => e.CountryId) + .Where(g => g.Count() < 3).Select(g => g.First()).FirstOrDefault()), + rowsAffectedCount: 1); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(e => ss.Set().GroupBy(e => e.CountryId) + .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) diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs index 13a41526378..40227581efa 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesFixtureBase.cs @@ -7,6 +7,9 @@ public abstract class InheritanceBulkUpdatesFixtureBase : InheritanceQueryFixtur { protected override string StoreName => "InheritanceBulkUpdatesTest"; + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(w => w.Log(CoreEventId.FirstWithoutOrderByAndFilterWarning)); + public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); } diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs index 4b491e425d7..7b90eb47c40 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs @@ -21,7 +21,7 @@ public virtual Task Delete_where_hierarchy(bool async) ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"), rowsAffectedCount: 1); - [ConditionalTheory(Skip = "Issue#28524")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_where_hierarchy_subquery(bool async) => AssertDelete( @@ -53,6 +53,35 @@ public virtual Task Delete_where_using_hierarchy_derived(bool async) ss => ss.Set().Where(e => e.Animals.OfType().Where(a => a.CountryId > 0).Count() > 0), rowsAffectedCount: 1); + [ConditionalTheory(Skip = "Issue#28525")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_First(bool async) + => AssertDelete( + async, + ss => ss.Set() + .GroupBy(e => e.CountryId) + .Where( g=> g.Count() < 3) + .Select(g => g.First()), + rowsAffectedCount: 2); + + [ConditionalTheory(Skip = "Issue#26753")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_First_2(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(e => e == ss.Set().GroupBy(e => e.CountryId) + .Where(g => g.Count() < 3).Select(g => g.First()).FirstOrDefault()), + rowsAffectedCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(e => ss.Set().GroupBy(e => e.CountryId) + .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) diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs index dc8d5cca556..38aced574f3 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs @@ -14,6 +14,14 @@ protected NorthwindBulkUpdatesTestBase(TFixture fixture) ClearLog(); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_Where_TagWith(bool async) + => AssertDelete( + async, + ss => ss.Set().Where(e => e.OrderID < 10300).TagWith("MyDelete"), + rowsAffectedCount: 140); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_Where(bool async) @@ -109,7 +117,7 @@ public virtual Task Delete_Where_predicate_with_GroupBy_aggregate(bool async) .Select(g => g.First()).First().OrderID), rowsAffectedCount: 284); - [ConditionalTheory(Skip = "Issue#28524")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) => AssertDelete( @@ -119,7 +127,7 @@ public virtual Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async) .GroupBy(o => o.CustomerID) .Where(g => g.Count() > 9) .Select(g => g.First()).Contains(e.Order)), - rowsAffectedCount: 40); + rowsAffectedCount: 109); [ConditionalTheory(Skip = "Issue#28525")] [MemberData(nameof(IsAsyncData))] @@ -132,6 +140,15 @@ public virtual Task Delete_GroupBy_Where_Select(bool async) .Select(g => g.First()), rowsAffectedCount: 284); + [ConditionalTheory(Skip = "Issue#26753")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Delete_GroupBy_Where_Select_2(bool async) + => AssertDelete( + async, + ss => ss.Set() + .Where(od => od == ss.Set().GroupBy(od => od.OrderID).Where(g => g.Count() > 5).Select(g => g.First()).FirstOrDefault()), + rowsAffectedCount: 284); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Delete_Where_Skip_Take_Skip_Take_causing_subquery(bool async) @@ -341,6 +358,17 @@ from o in ss.Set().Where(o => o.OrderID < od.OrderID).OrderBy(e => e.Orde select od, rowsAffectedCount: 74); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_set_constant_TagWith(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).TagWith("MyUpdate"), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Where_set_constant(bool async) @@ -352,6 +380,17 @@ public virtual Task Update_Where_set_constant(bool async) rowsAffectedCount: 8, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_Where_set_default(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")), + e => e, + s => s.SetProperty(c => c.ContactName, c => EF.Default()), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => Assert.Null(c.ContactName))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Update_Where_parameter_set_constant(bool async) @@ -516,7 +555,7 @@ public virtual Task Update_Where_GroupBy_First_set_constant_2(bool async) rowsAffectedCount: 1, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); - [ConditionalTheory(Skip = "Issue#28524")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Where_GroupBy_First_set_constant_3(bool async) => AssertUpdate( @@ -526,7 +565,7 @@ public virtual Task Update_Where_GroupBy_First_set_constant_3(bool async) .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().Customer).Contains(c)), e => e, s => s.SetProperty(c => c.ContactName, c => "Updated"), - rowsAffectedCount: 1, + rowsAffectedCount: 24, (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName))); [ConditionalTheory] @@ -869,7 +908,7 @@ public virtual Task Update_Where_SelectMany_subquery_set_null(bool async) rowsAffectedCount: 35, (b, a) => Assert.All(a, c => Assert.Null(c.OrderDate))); - [ConditionalTheory(Skip = "Issue#28752")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_Where_Join_set_property_from_joined_single_result_table(bool async) => AssertUpdate( @@ -877,9 +916,19 @@ public virtual Task Update_Where_Join_set_property_from_joined_single_result_tab ss => from c in ss.Set().Where(c => c.CustomerID.StartsWith("F")) select new { c, LastOrder = c.Orders.OrderByDescending(o => o.OrderDate).FirstOrDefault() }, e => e.c, - s => s.SetProperty(c => c.c.City, c => c.LastOrder.OrderDate.ToString()), - rowsAffectedCount: 35, - (b, a) => Assert.All(a, c => Assert.NotNull(c.City))); + s => s.SetProperty(c => c.c.City, c => c.LastOrder.OrderDate.Value.Year.ToString()), + rowsAffectedCount: 8, + (b, a) => Assert.All(a, c => + { + if (c.CustomerID == "FISSA") + { + Assert.Null(c.City); + } + else + { + Assert.NotNull(c.City); + } + })); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs index cbab0c39ede..bf4c657b7ec 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs @@ -19,6 +19,16 @@ public override Task Delete_where_hierarchy(bool async) RelationalStrings.ExecuteOperationOnTPC("ExecuteDelete", "Animal"), () => base.Delete_where_hierarchy(async)); + public override Task Delete_where_hierarchy_subquery(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPC("ExecuteDelete", "Animal"), + () => base.Delete_where_hierarchy_subquery(async)); + + public override Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPC("ExecuteDelete", "Animal"), + () => base.Delete_GroupBy_Where_Select_First_3(async)); + // Keyless entities are mapped as TPH only public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async) => Task.CompletedTask; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs index 1091866fa79..f1d2a57c877 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs @@ -19,6 +19,16 @@ public override Task Delete_where_hierarchy(bool async) RelationalStrings.ExecuteOperationOnTPC("ExecuteDelete", "Animal"), () => base.Delete_where_hierarchy(async)); + public override Task Delete_where_hierarchy_subquery(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPC("ExecuteDelete", "Animal"), + () => base.Delete_where_hierarchy_subquery(async)); + + public override Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPC("ExecuteDelete", "Animal"), + () => base.Delete_GroupBy_Where_Select_First_3(async)); + // Keyless entities are mapped as TPH only public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async) => Task.CompletedTask; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs index 9e144a3b6a1..8eb6cc01c08 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs @@ -19,11 +19,21 @@ public override Task Delete_where_hierarchy(bool async) RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"), () => base.Delete_where_hierarchy(async)); + public override Task Delete_where_hierarchy_subquery(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"), + () => base.Delete_where_hierarchy_subquery(async)); + public override Task Delete_where_hierarchy_derived(bool async) => AssertTranslationFailed( RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Kiwi"), () => base.Delete_where_hierarchy_derived(async)); + public override Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"), + () => base.Delete_GroupBy_Where_Select_First_3(async)); + [ConditionalTheory(Skip = "Issue#28532")] public override Task Delete_where_using_hierarchy(bool async) { diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs index 3d97ec4d2e4..7c6b53d8ca1 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs @@ -19,11 +19,21 @@ public override Task Delete_where_hierarchy(bool async) RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"), () => base.Delete_where_hierarchy(async)); + public override Task Delete_where_hierarchy_subquery(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"), + () => base.Delete_where_hierarchy_subquery(async)); + public override Task Delete_where_hierarchy_derived(bool async) => AssertTranslationFailed( RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Kiwi"), () => base.Delete_where_hierarchy_derived(async)); + public override Task Delete_GroupBy_Where_Select_First_3(bool async) + => AssertTranslationFailed( + RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"), + () => base.Delete_GroupBy_Where_Select_First_3(async)); + [ConditionalTheory(Skip = "FK constraint issue")] public override Task Delete_where_using_hierarchy(bool async) { diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs index b5dc9ff2ce6..3f03db1c022 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs @@ -61,6 +61,38 @@ FROM [Animals] AS [a] WHERE [a].[CountryId] = 1 AND [c].[Id] = [a].[CountryId] AND [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] > 0) > 0"); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql( + @"DELETE FROM [a] +FROM [Animals] AS [a] +WHERE [a].[CountryId] = 1 AND EXISTS ( + SELECT 1 + FROM [Animals] AS [a0] + WHERE [a0].[CountryId] = 1 + GROUP BY [a0].[CountryId] + HAVING COUNT(*) < 3 AND ( + SELECT TOP(1) [a1].[Id] + FROM [Animals] AS [a1] + WHERE [a1].[CountryId] = 1 AND [a0].[CountryId] = [a1].[CountryId]) = [a].[Id])"); + } + public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) { await base.Delete_where_keyless_entity_mapped_to_sql_query(async); @@ -72,7 +104,22 @@ public override async Task Delete_where_hierarchy_subquery(bool async) { await base.Delete_where_hierarchy_subquery(async); - AssertSql(); + AssertSql( + @"@__p_0='0' +@__p_1='3' + +DELETE FROM [a] +FROM [Animals] AS [a] +WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT [a0].[Id], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[Species], [a0].[EagleId], [a0].[IsFlightless], [a0].[Group], [a0].[FoundOn] + FROM [Animals] AS [a0] + WHERE [a0].[CountryId] = 1 AND [a0].[Name] = N'Great spotted kiwi' + ORDER BY [a0].[Name] + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY + ) AS [t] + WHERE [t].[Id] = [a].[Id])"); } public override async Task Update_where_hierarchy(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs index 19086fa182a..b46afb9201d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs @@ -61,6 +61,37 @@ FROM [Animals] AS [a] WHERE [c].[Id] = [a].[CountryId] AND [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] > 0) > 0"); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql( + @"DELETE FROM [a] +FROM [Animals] AS [a] +WHERE EXISTS ( + SELECT 1 + FROM [Animals] AS [a0] + GROUP BY [a0].[CountryId] + HAVING COUNT(*) < 3 AND ( + SELECT TOP(1) [a1].[Id] + FROM [Animals] AS [a1] + WHERE [a0].[CountryId] = [a1].[CountryId]) = [a].[Id])"); + } + public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) { await base.Delete_where_keyless_entity_mapped_to_sql_query(async); @@ -72,7 +103,22 @@ public override async Task Delete_where_hierarchy_subquery(bool async) { await base.Delete_where_hierarchy_subquery(async); - AssertSql(); + AssertSql( + @"@__p_0='0' +@__p_1='3' + +DELETE FROM [a] +FROM [Animals] AS [a] +WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT [a0].[Id], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[Species], [a0].[EagleId], [a0].[IsFlightless], [a0].[Group], [a0].[FoundOn] + FROM [Animals] AS [a0] + WHERE [a0].[Name] = N'Great spotted kiwi' + ORDER BY [a0].[Name] + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY + ) AS [t] + WHERE [t].[Id] = [a].[Id])"); } public override async Task Update_where_hierarchy(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs index 3a95f571010..42ae2dac218 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs @@ -14,6 +14,18 @@ public NorthwindBulkUpdatesSqlServerTest(NorthwindBulkUpdatesSqlServerFixture TestHelpers.AssertAllMethodsOverridden(GetType()); + public override async Task Delete_Where_TagWith(bool async) + { + await base.Delete_Where_TagWith(async); + + AssertSql( + @"-- MyDelete + +DELETE FROM [o] +FROM [Order Details] AS [o] +WHERE [o].[OrderID] < 10300"); + } + public override async Task Delete_Where(bool async) { await base.Delete_Where(async); @@ -190,7 +202,18 @@ public override async Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool { await base.Delete_Where_predicate_with_GroupBy_aggregate_2(async); - AssertSql(); + AssertSql( + @"DELETE FROM [o] +FROM [Order Details] AS [o] +INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] +WHERE EXISTS ( + SELECT 1 + FROM [Orders] AS [o1] + GROUP BY [o1].[CustomerID] + HAVING COUNT(*) > 9 AND ( + SELECT TOP(1) [o2].[OrderID] + FROM [Orders] AS [o2] + WHERE [o1].[CustomerID] = [o2].[CustomerID] OR ([o1].[CustomerID] IS NULL AND [o2].[CustomerID] IS NULL)) = [o0].[OrderID])"); } public override async Task Delete_GroupBy_Where_Select(bool async) @@ -200,6 +223,13 @@ public override async Task Delete_GroupBy_Where_Select(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_2(bool async) + { + await base.Delete_GroupBy_Where_Select_2(async); + + AssertSql(); + } + public override async Task Delete_Where_Skip_Take_Skip_Take_causing_subquery(bool async) { await base.Delete_Where_Skip_Take_Skip_Take_causing_subquery(async); @@ -514,6 +544,19 @@ OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY WHERE [o].[OrderID] < 10276"); } + public override async Task Update_Where_set_constant_TagWith(bool async) + { + await base.Update_Where_set_constant_TagWith(async); + + AssertExecuteUpdateSql( + @"-- MyUpdate + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + public override async Task Update_Where_set_constant(bool async) { await base.Update_Where_set_constant(async); @@ -525,6 +568,17 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } + public override async Task Update_Where_set_default(bool async) + { + await base.Update_Where_set_default(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = DEFAULT +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + public override async Task Update_Where_parameter_set_constant(bool async) { await base.Update_Where_parameter_set_constant(async); @@ -759,7 +813,19 @@ public override async Task Update_Where_GroupBy_First_set_constant_3(bool async) { await base.Update_Where_GroupBy_First_set_constant_3(async); - AssertExecuteUpdateSql(); + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 11 AND ( + SELECT TOP(1) [c0].[CustomerID] + FROM [Orders] AS [o0] + LEFT JOIN [Customers] AS [c0] ON [o0].[CustomerID] = [c0].[CustomerID] + WHERE [o].[CustomerID] = [o0].[CustomerID] OR ([o].[CustomerID] IS NULL AND [o0].[CustomerID] IS NULL)) = [c].[CustomerID])"); } public override async Task Update_Where_Distinct_set_constant(bool async) @@ -1103,7 +1169,15 @@ public override async Task Update_Where_Join_set_property_from_joined_single_res { await base.Update_Where_Join_set_property_from_joined_single_result_table(async); - AssertExecuteUpdateSql(); + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[City] = CONVERT(varchar(11), DATEPART(year, ( + SELECT TOP(1) [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + ORDER BY [o].[OrderDate] DESC))) +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); } public override async Task Update_Where_Join_set_property_from_joined_table(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs index 1aff954c2aa..2599e995da5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs @@ -81,6 +81,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs index 75e7110f4c4..071d1b78c3e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs @@ -81,6 +81,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs index b07f2686746..d11df1e626e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs @@ -69,6 +69,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs index 91ce894d821..b9b523680fd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs @@ -57,6 +57,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs index 362f5e7b3e1..dcdddef09ce 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs @@ -28,7 +28,21 @@ public override async Task Delete_where_hierarchy_subquery(bool async) { await base.Delete_where_hierarchy_subquery(async); - AssertSql(); + AssertSql( + @"@__p_1='3' +@__p_0='0' + +DELETE FROM ""Animals"" AS ""a"" +WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT ""a0"".""Id"", ""a0"".""CountryId"", ""a0"".""Discriminator"", ""a0"".""Name"", ""a0"".""Species"", ""a0"".""EagleId"", ""a0"".""IsFlightless"", ""a0"".""Group"", ""a0"".""FoundOn"" + FROM ""Animals"" AS ""a0"" + WHERE ""a0"".""CountryId"" = 1 AND ""a0"".""Name"" = 'Great spotted kiwi' + ORDER BY ""a0"".""Name"" + LIMIT @__p_1 OFFSET @__p_0 + ) AS ""t"" + WHERE ""t"".""Id"" = ""a"".""Id"")"); } public override async Task Delete_where_hierarchy_derived(bool async) @@ -71,6 +85,38 @@ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql( + @"DELETE FROM ""Animals"" AS ""a"" +WHERE ""a"".""CountryId"" = 1 AND EXISTS ( + SELECT 1 + FROM ""Animals"" AS ""a0"" + WHERE ""a0"".""CountryId"" = 1 + GROUP BY ""a0"".""CountryId"" + HAVING COUNT(*) < 3 AND ( + SELECT ""a1"".""Id"" + FROM ""Animals"" AS ""a1"" + WHERE ""a1"".""CountryId"" = 1 AND ""a0"".""CountryId"" = ""a1"".""CountryId"" + LIMIT 1) = ""a"".""Id"")"); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs index 15da6f75101..b8c70262b20 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs @@ -28,7 +28,21 @@ public override async Task Delete_where_hierarchy_subquery(bool async) { await base.Delete_where_hierarchy_subquery(async); - AssertSql(); + AssertSql( + @"@__p_1='3' +@__p_0='0' + +DELETE FROM ""Animals"" AS ""a"" +WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT ""a0"".""Id"", ""a0"".""CountryId"", ""a0"".""Discriminator"", ""a0"".""Name"", ""a0"".""Species"", ""a0"".""EagleId"", ""a0"".""IsFlightless"", ""a0"".""Group"", ""a0"".""FoundOn"" + FROM ""Animals"" AS ""a0"" + WHERE ""a0"".""Name"" = 'Great spotted kiwi' + ORDER BY ""a0"".""Name"" + LIMIT @__p_1 OFFSET @__p_0 + ) AS ""t"" + WHERE ""t"".""Id"" = ""a"".""Id"")"); } public override async Task Delete_where_hierarchy_derived(bool async) @@ -71,6 +85,37 @@ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql( + @"DELETE FROM ""Animals"" AS ""a"" +WHERE EXISTS ( + SELECT 1 + FROM ""Animals"" AS ""a0"" + GROUP BY ""a0"".""CountryId"" + HAVING COUNT(*) < 3 AND ( + SELECT ""a1"".""Id"" + FROM ""Animals"" AS ""a1"" + WHERE ""a0"".""CountryId"" = ""a1"".""CountryId"" + LIMIT 1) = ""a"".""Id"")"); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs index dd85bb0a8b2..785289a6102 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore.Sqlite.Internal; namespace Microsoft.EntityFrameworkCore.BulkUpdates; @@ -16,6 +17,17 @@ public NorthwindBulkUpdatesSqliteTest(NorthwindBulkUpdatesSqliteFixture TestHelpers.AssertAllMethodsOverridden(GetType()); + public override async Task Delete_Where_TagWith(bool async) + { + await base.Delete_Where_TagWith(async); + + AssertSql( + @"-- MyDelete + +DELETE FROM ""Order Details"" AS ""o"" +WHERE ""o"".""OrderID"" < 10300"); + } + public override async Task Delete_Where(bool async) { await base.Delete_Where(async); @@ -190,7 +202,21 @@ public override async Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool { await base.Delete_Where_predicate_with_GroupBy_aggregate_2(async); - AssertSql(); + AssertSql( + @"DELETE FROM ""Order Details"" AS ""o"" +WHERE EXISTS ( + SELECT 1 + FROM ""Order Details"" AS ""o0"" + INNER JOIN ""Orders"" AS ""o1"" ON ""o0"".""OrderID"" = ""o1"".""OrderID"" + WHERE EXISTS ( + SELECT 1 + FROM ""Orders"" AS ""o2"" + GROUP BY ""o2"".""CustomerID"" + HAVING COUNT(*) > 9 AND ( + SELECT ""o3"".""OrderID"" + FROM ""Orders"" AS ""o3"" + WHERE ""o2"".""CustomerID"" = ""o3"".""CustomerID"" OR (""o2"".""CustomerID"" IS NULL AND ""o3"".""CustomerID"" IS NULL) + LIMIT 1) = ""o1"".""OrderID"") AND ""o0"".""OrderID"" = ""o"".""OrderID"" AND ""o0"".""ProductID"" = ""o"".""ProductID"")"); } public override async Task Delete_GroupBy_Where_Select(bool async) @@ -200,6 +226,13 @@ public override async Task Delete_GroupBy_Where_Select(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_2(bool async) + { + await base.Delete_GroupBy_Where_Select_2(async); + + AssertSql(); + } + public override async Task Delete_Where_Skip_Take_Skip_Take_causing_subquery(bool async) { await base.Delete_Where_Skip_Take_Skip_Take_causing_subquery(async); @@ -495,6 +528,18 @@ public override async Task Delete_with_outer_apply(bool async) SqliteStrings.ApplyNotSupported, (await Assert.ThrowsAsync(() => base.Delete_with_outer_apply(async))).Message); + public override async Task Update_Where_set_constant_TagWith(bool async) + { + await base.Update_Where_set_constant_TagWith(async); + + AssertExecuteUpdateSql( + @"-- MyUpdate + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + public override async Task Update_Where_set_constant(bool async) { await base.Update_Where_set_constant(async); @@ -505,6 +550,12 @@ public override async Task Update_Where_set_constant(bool async) WHERE ""c"".""CustomerID"" LIKE 'F%'"); } + public override Task Update_Where_set_default(bool async) + => AssertTranslationFailed( + RelationalStrings.UnableToTranslateSetProperty( + "c => c.ContactName", "c => EF.Default()", SqliteStrings.DefaultNotSupported), + () => base.Update_Where_set_default(async)); + public override async Task Update_Where_parameter_set_constant(bool async) { await base.Update_Where_parameter_set_constant(async); @@ -741,7 +792,19 @@ public override async Task Update_Where_GroupBy_First_set_constant_3(bool async) { await base.Update_Where_GroupBy_First_set_constant_3(async); - AssertExecuteUpdateSql(); + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +WHERE EXISTS ( + SELECT 1 + FROM ""Orders"" AS ""o"" + GROUP BY ""o"".""CustomerID"" + HAVING COUNT(*) > 11 AND ( + SELECT ""c0"".""CustomerID"" + FROM ""Orders"" AS ""o0"" + LEFT JOIN ""Customers"" AS ""c0"" ON ""o0"".""CustomerID"" = ""c0"".""CustomerID"" + WHERE ""o"".""CustomerID"" = ""o0"".""CustomerID"" OR (""o"".""CustomerID"" IS NULL AND ""o0"".""CustomerID"" IS NULL) + LIMIT 1) = ""c"".""CustomerID"")"); } public override async Task Update_Where_Distinct_set_constant(bool async) @@ -1065,7 +1128,15 @@ public override async Task Update_Where_Join_set_property_from_joined_single_res { await base.Update_Where_Join_set_property_from_joined_single_result_table(async); - AssertExecuteUpdateSql(); + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""City"" = CAST(CAST(strftime('%Y', ( + SELECT ""o"".""OrderDate"" + FROM ""Orders"" AS ""o"" + WHERE ""c"".""CustomerID"" = ""o"".""CustomerID"" + ORDER BY ""o"".""OrderDate"" DESC + LIMIT 1)) AS INTEGER) AS TEXT) +WHERE ""c"".""CustomerID"" LIKE 'F%'"); } public override async Task Update_Where_Join_set_property_from_joined_table(bool async) diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs index 5ff49896ae8..8124cee18f7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs @@ -78,6 +78,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs index 701c9f8435c..f723adff3d3 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs @@ -78,6 +78,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs index 162ac651920..c19e0492324 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs @@ -69,6 +69,27 @@ public override async Task Delete_where_hierarchy_subquery(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Update_where_hierarchy(bool async) { await base.Update_where_hierarchy(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs index 6c276c2076f..7182b55da68 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs @@ -43,6 +43,27 @@ public override async Task Delete_where_using_hierarchy_derived(bool async) AssertSql(); } + public override async Task Delete_GroupBy_Where_Select_First(bool async) + { + await base.Delete_GroupBy_Where_Select_First(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_2(bool async) + { + await base.Delete_GroupBy_Where_Select_First_2(async); + + AssertSql(); + } + + public override async Task Delete_GroupBy_Where_Select_First_3(bool async) + { + await base.Delete_GroupBy_Where_Select_First_3(async); + + AssertSql(); + } + public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async) { await base.Delete_where_keyless_entity_mapped_to_sql_query(async);