diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index a78aed237c8..4fffe2709cc 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -1017,6 +1017,14 @@ public static string InvalidPropertyInSetProperty(object? propertyExpression)
GetString("InvalidPropertyInSetProperty", nameof(propertyExpression)),
propertyExpression);
+ ///
+ /// The following lambda argument to 'SetProperty' does not represent a valid value: '{valueExpression}'.
+ ///
+ public static string InvalidValueInSetProperty(object? valueExpression)
+ => string.Format(
+ GetString("InvalidValueInSetProperty", nameof(valueExpression)),
+ valueExpression);
+
///
/// Can't navigate from JSON-mapped entity '{jsonEntity}' to its parent entity '{parentEntity}' using navigation '{navigation}'. Entities mapped to JSON can only navigate to their children.
///
@@ -1300,12 +1308,12 @@ public static string ModificationCommandInvalidEntityStateSensitive(object? enti
entityType, keyValues, entityState);
///
- /// Multiple 'SetProperty' invocations refer to properties on different entity types ('{entityType1}' and '{entityType2}'). A single 'ExecuteUpdate' call can only update the properties of a single entity type.
+ /// Multiple 'SetProperty' invocations refer to different tables ('{propertySelector1}' and '{propertySelector2}'). A single 'ExecuteUpdate' call can only update the columns of a single table.
///
- public static string MultipleEntityPropertiesInSetProperty(object? entityType1, object? entityType2)
+ public static string MultipleTablesInExecuteUpdate(object? propertySelector1, object? propertySelector2)
=> string.Format(
- GetString("MultipleEntityPropertiesInSetProperty", nameof(entityType1), nameof(entityType2)),
- entityType1, entityType2);
+ GetString("MultipleTablesInExecuteUpdate", nameof(propertySelector1), nameof(propertySelector2)),
+ propertySelector1, propertySelector2);
///
/// Multiple relational database provider configurations found. A context can only be configured to use a single database provider.
@@ -1989,14 +1997,6 @@ public static string UnableToBindMemberToEntityProjection(object? memberType, ob
GetString("UnableToBindMemberToEntityProjection", nameof(memberType), nameof(member), nameof(entityType)),
memberType, member, entityType);
- ///
- /// The following 'SetProperty' failed to translate: 'SetProperty({property}, {value})'. {details}
- ///
- public static string UnableToTranslateSetProperty(object? property, object? value, object? details)
- => string.Format(
- GetString("UnableToTranslateSetProperty", nameof(property), nameof(value), nameof(details)),
- property, value, details);
-
///
/// Unhandled annotatable type '{annotatableType}'.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 6ca3e9b0d28..2834edcc92e 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -499,6 +499,9 @@
The following lambda argument to 'SetProperty' does not represent a valid property to be set: '{propertyExpression}'.
+
+ The following lambda argument to 'SetProperty' does not represent a valid value: '{valueExpression}'.
+
Can't navigate from JSON-mapped entity '{jsonEntity}' to its parent entity '{parentEntity}' using navigation '{navigation}'. Entities mapped to JSON can only navigate to their children.
@@ -905,8 +908,8 @@
Cannot save changes for an entity of type '{entityType}' with primary key values {keyValues} in state '{entityState}'. This may indicate a bug in Entity Framework, please open an issue at https://go.microsoft.com/fwlink/?linkid=2142044.
-
- Multiple 'SetProperty' invocations refer to properties on different entity types ('{entityType1}' and '{entityType2}'). A single 'ExecuteUpdate' call can only update the properties of a single entity type.
+
+ Multiple 'SetProperty' invocations refer to different tables ('{propertySelector1}' and '{propertySelector2}'). A single 'ExecuteUpdate' call can only update the columns of a single table.
Multiple relational database provider configurations found. A context can only be configured to use a single database provider.
@@ -1178,9 +1181,6 @@
Unable to bind '{memberType}.{member}' to an entity projection of '{entityType}'.
-
- The following 'SetProperty' failed to translate: 'SetProperty({property}, {value})'. {details}
-
Unhandled annotatable type '{annotatableType}'.
diff --git a/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs b/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs
new file mode 100644
index 00000000000..06f161edce7
--- /dev/null
+++ b/src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs
@@ -0,0 +1,151 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal;
+
+///
+/// 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.
+///
+public sealed class TpcTablesExpression : TableExpressionBase
+{
+ ///
+ /// 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.
+ ///
+ public TpcTablesExpression(
+ string? alias,
+ IEntityType entityType,
+ IReadOnlyList subSelectExpressions)
+ : base(alias)
+ {
+ EntityType = entityType;
+ SelectExpressions = subSelectExpressions;
+ }
+
+ private TpcTablesExpression(
+ string? alias,
+ IEntityType entityType,
+ IReadOnlyList subSelectExpressions,
+ IEnumerable? annotations)
+ : base(alias, annotations)
+ {
+ EntityType = entityType;
+ SelectExpressions = subSelectExpressions;
+ }
+
+ ///
+ /// 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.
+ ///
+ [NotNull]
+ public override string? Alias
+ {
+ get => base.Alias!;
+ internal set => base.Alias = value;
+ }
+
+ ///
+ /// 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.
+ ///
+ public IEntityType EntityType { get; }
+
+ ///
+ /// 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.
+ ///
+ public IReadOnlyList SelectExpressions { get; }
+
+ ///
+ /// 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.
+ ///
+ public TpcTablesExpression Prune(IReadOnlyList discriminatorValues)
+ {
+ var subSelectExpressions = discriminatorValues.Count == 0
+ ? new List { SelectExpressions[0] }
+ : SelectExpressions.Where(
+ se =>
+ discriminatorValues.Contains((string)((SqlConstantExpression)se.Projection[^1].Expression).Value!)).ToList();
+
+ Check.DebugAssert(subSelectExpressions.Count > 0, "TPC must have at least 1 table selected.");
+
+ return new TpcTablesExpression(Alias, EntityType, subSelectExpressions, GetAnnotations());
+ }
+
+ ///
+ /// 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.
+ ///
+ // This is implementation detail hence visitors are not supposed to see inside unless they really need to.
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ => this;
+
+ ///
+ /// 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 TableExpressionBase CreateWithAnnotations(IEnumerable annotations)
+ => new TpcTablesExpression(Alias, EntityType, SelectExpressions, annotations);
+
+ ///
+ /// 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 void Print(ExpressionPrinter expressionPrinter)
+ {
+ expressionPrinter.AppendLine("(");
+ using (expressionPrinter.Indent())
+ {
+ expressionPrinter.VisitCollection(SelectExpressions, e => e.AppendLine().AppendLine("UNION ALL"));
+ }
+
+ expressionPrinter.AppendLine()
+ .AppendLine(") AS " + Alias);
+ PrintAnnotations(expressionPrinter);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ => obj != null
+ && (ReferenceEquals(this, obj)
+ || obj is TpcTablesExpression tpcTablesExpression
+ && Equals(tpcTablesExpression));
+
+ private bool Equals(TpcTablesExpression tpcTablesExpression)
+ {
+ if (!base.Equals(tpcTablesExpression)
+ || EntityType != tpcTablesExpression.EntityType)
+ {
+ return false;
+ }
+
+ return SelectExpressions.SequenceEqual(tpcTablesExpression.SelectExpressions);
+ }
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(base.GetHashCode(), EntityType);
+}
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 01db9271b87..a78e1ffe97a 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
-using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@@ -1416,9 +1415,7 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
/// statements.
///
/// The non query after translation.
- protected virtual NonQueryExpression? TranslateExecuteUpdate(
- ShapedQueryExpression source,
- LambdaExpression setPropertyCalls)
+ protected virtual 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.
@@ -1427,7 +1424,7 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
source = source.UpdateShaperExpression(PruneIncludes(includeExpression));
}
- var propertyValueLambdaExpressions = new List<(LambdaExpression, Expression)>();
+ var propertyValueLambdaExpressions = new List<(LambdaExpression PropertySelector, Expression ValueExpression)>();
PopulateSetPropertyCalls(setPropertyCalls.Body, propertyValueLambdaExpressions, setPropertyCalls.Parameters[0]);
if (TranslationErrorDetails != null)
{
@@ -1440,194 +1437,57 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
return null;
}
- EntityShaperExpression? entityShaperExpression = null;
- var remappedUnwrappedLeftExpressions = new List();
- foreach (var (propertyExpression, _) in propertyValueLambdaExpressions)
+ // Go over the SetProperty calls, and translate the property selectors (left lambda).
+ // The property selectors should get translated to ColumnExpressions (otherwise they' invalid - columns are what we need to update).
+ // All columns must also refer to the same table (since that's how SQL UPDATE works), extract that target table from the translated
+ // columns and validate that only one table is being referenced.
+ // Note that we don't translate the value expressions in this pass, since if the query is complicated, we may need to do a pushdown
+ // (see PushdownWithPkInnerJoinPredicate below); so we defer translation until we have the final source/select. For the property
+ // selectors we need to translate now since we need the table.
+ TableExpressionBase? targetTable = null;
+ Expression? targetTablePropertySelector = null;
+ var columns = new ColumnExpression[propertyValueLambdaExpressions.Count];
+ for (var i = 0; i < propertyValueLambdaExpressions.Count; i++)
{
- var left = RemapLambdaBody(source, propertyExpression);
-
- if (!TryProcessPropertyAccess(RelationalDependencies.Model, ref left, out var ese))
+ var (propertySelector, _) = propertyValueLambdaExpressions[i];
+ var propertySelectorBody = RemapLambdaBody(source, propertySelector).UnwrapTypeConversion(out _);
+ if (_sqlTranslator.Translate(propertySelectorBody) is not ColumnExpression column)
{
- AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertyExpression.Print()));
+ AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertySelector.Print()));
return null;
}
- if (entityShaperExpression is null)
+ if (targetTable is null)
{
- entityShaperExpression = ese;
+ (targetTable, targetTablePropertySelector) = (column.Table, propertySelector);
}
- else if (!ReferenceEquals(ese, entityShaperExpression))
+ else if (!ReferenceEquals(column.Table, targetTable))
{
AddTranslationErrorDetails(
- RelationalStrings.MultipleEntityPropertiesInSetProperty(
- entityShaperExpression.EntityType.DisplayName(), ese.EntityType.DisplayName()));
+ RelationalStrings.MultipleTablesInExecuteUpdate(propertySelector.Print(), targetTablePropertySelector!.Print()));
return null;
}
- remappedUnwrappedLeftExpressions.Add(left);
- }
-
- Check.DebugAssert(entityShaperExpression != null, "EntityShaperExpression should have a value.");
-
- var entityType = entityShaperExpression.EntityType;
- var mappingStrategy = entityType.GetMappingStrategy();
- if (mappingStrategy == RelationalAnnotationNames.TptMappingStrategy)
- {
- AddTranslationErrorDetails(
- RelationalStrings.ExecuteOperationOnTPT(nameof(RelationalQueryableExtensions.ExecuteUpdate), entityType.DisplayName()));
- return null;
+ columns[i] = column;
}
- if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy
- && entityType.GetDirectlyDerivedTypes().Any())
- {
- // We allow TPC is it is leaf type
- AddTranslationErrorDetails(
- RelationalStrings.ExecuteOperationOnTPC(nameof(RelationalQueryableExtensions.ExecuteUpdate), entityType.DisplayName()));
- return null;
- }
+ Check.DebugAssert(targetTable is not null, "Target table should have a value");
- if (entityType.GetViewOrTableMappings().Count() != 1)
+ if (targetTable is TpcTablesExpression tpcTablesExpression)
{
AddTranslationErrorDetails(
- RelationalStrings.ExecuteOperationOnEntitySplitting(
- nameof(RelationalQueryableExtensions.ExecuteUpdate), entityType.DisplayName()));
+ RelationalStrings.ExecuteOperationOnTPC(
+ nameof(RelationalQueryableExtensions.ExecuteUpdate), tpcTablesExpression.EntityType.DisplayName()));
return null;
}
// First, check if the provider has a native translation for the update represented by the select expression.
// The default relational implementation handles simple, universally-supported cases (i.e. no operators except for predicate).
- // Providers may override IsValidSelectExpressionForExecuteDelete to add support for more cases via provider-specific UPDATE syntax.
+ // Providers may override IsValidSelectExpressionForExecuteUpdate to add support for more cases via provider-specific UPDATE syntax.
var selectExpression = (SelectExpression)source.QueryExpression;
- if (IsValidSelectExpressionForExecuteUpdate(selectExpression, entityShaperExpression, out var tableExpression))
- {
- return TranslateSetPropertyExpressions(
- this, source, selectExpression, tableExpression,
- propertyValueLambdaExpressions, remappedUnwrappedLeftExpressions);
- }
-
- // The provider doesn't natively support the update.
- // As a fallback, we place the original query in a subquery and user an INNER JOIN on the primary key columns.
- // Unlike with ExecuteDelete, we cannot use a Contains subquery (which would produce the simpler WHERE Id IN (SELECT ...) syntax),
- // since we allow projecting out to arbitrary shapes (e.g. anonymous types) before the ExecuteUpdate.
- var pk = entityType.FindPrimaryKey();
- if (pk == null)
- {
- AddTranslationErrorDetails(
- RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator(
- nameof(RelationalQueryableExtensions.ExecuteUpdate),
- entityType.DisplayName()));
- return null;
- }
-
- var outer = (ShapedQueryExpression)Visit(new EntityQueryRootExpression(entityType));
- var inner = source;
- var outerParameter = Expression.Parameter(entityType.ClrType);
- var outerKeySelector = Expression.Lambda(outerParameter.CreateKeyValuesExpression(pk.Properties), outerParameter);
- var firstPropertyLambdaExpression = propertyValueLambdaExpressions[0].Item1;
- var entitySource = GetEntitySource(RelationalDependencies.Model, firstPropertyLambdaExpression.Body);
- var innerKeySelector = Expression.Lambda(
- entitySource.CreateKeyValuesExpression(pk.Properties), firstPropertyLambdaExpression.Parameters);
-
- var joinPredicate = CreateJoinPredicate(outer, outerKeySelector, inner, innerKeySelector);
-
- Check.DebugAssert(joinPredicate != null, "Join predicate shouldn't be null");
-
- var outerSelectExpression = (SelectExpression)outer.QueryExpression;
- var outerShaperExpression = outerSelectExpression.AddInnerJoin(inner, joinPredicate, outer.ShaperExpression);
- outer = outer.UpdateShaperExpression(outerShaperExpression);
- var transparentIdentifierType = outer.ShaperExpression.Type;
- var transparentIdentifierParameter = Expression.Parameter(transparentIdentifierType);
-
- var propertyReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Outer");
- var valueReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Inner");
- for (var i = 0; i < propertyValueLambdaExpressions.Count; i++)
- {
- var (propertyExpression, valueExpression) = propertyValueLambdaExpressions[i];
- propertyExpression = Expression.Lambda(
- ReplacingExpressionVisitor.Replace(
- ReplacingExpressionVisitor.Replace(
- firstPropertyLambdaExpression.Parameters[0],
- propertyExpression.Parameters[0],
- entitySource),
- propertyReplacement, propertyExpression.Body),
- transparentIdentifierParameter);
-
- valueExpression = valueExpression is LambdaExpression lambdaExpression
- ? Expression.Lambda(
- ReplacingExpressionVisitor.Replace(lambdaExpression.Parameters[0], valueReplacement, lambdaExpression.Body),
- transparentIdentifierParameter)
- : valueExpression;
-
- propertyValueLambdaExpressions[i] = (propertyExpression, valueExpression);
- }
-
- tableExpression = (TableExpression)outerSelectExpression.Tables[0];
-
- return TranslateSetPropertyExpressions(this, outer, outerSelectExpression, tableExpression, propertyValueLambdaExpressions, null);
-
- static NonQueryExpression? TranslateSetPropertyExpressions(
- RelationalQueryableMethodTranslatingExpressionVisitor visitor,
- ShapedQueryExpression source,
- SelectExpression selectExpression,
- TableExpression tableExpression,
- List<(LambdaExpression, Expression)> propertyValueLambdaExpressions,
- List? leftExpressions)
- {
- var columnValueSetters = new List();
- for (var i = 0; i < propertyValueLambdaExpressions.Count; i++)
- {
- var (propertyExpression, valueExpression) = propertyValueLambdaExpressions[i];
- Expression left;
- if (leftExpressions != null)
- {
- left = leftExpressions[i];
- }
- else
- {
- left = visitor.RemapLambdaBody(source, propertyExpression);
- left = left.UnwrapTypeConversion(out _);
- }
-
- var right = valueExpression is LambdaExpression lambdaExpression
- ? visitor.RemapLambdaBody(source, lambdaExpression)
- : valueExpression;
-
- if (right.Type != left.Type)
- {
- right = Expression.Convert(right, left.Type);
- }
-
- // We generate equality between property = value while translating so that we infer the type mapping from property correctly.
- // Later we decompose it back into left/right components so that the equality is not in the tree which can get affected by
- // null semantics or other visitor.
- var setter = Infrastructure.ExpressionExtensions.CreateEqualsExpression(left, right);
- var translation = visitor._sqlTranslator.Translate(setter);
- if (translation is SqlBinaryExpression
- {
- OperatorType: ExpressionType.Equal, Left: ColumnExpression column
- } sqlBinaryExpression)
- {
- columnValueSetters.Add(
- new ColumnValueSetter(
- column,
- selectExpression.AssignUniqueAliases(sqlBinaryExpression.Right)));
- }
- else
- {
- // We would reach here only if the property is unmapped or value fails to translate.
- visitor.AddTranslationErrorDetails(
- RelationalStrings.UnableToTranslateSetProperty(
- propertyExpression.Print(), valueExpression.Print(), visitor._sqlTranslator.TranslationErrorDetails));
- return null;
- }
- }
-
- selectExpression.ReplaceProjection(new List());
- selectExpression.ApplyProjection();
-
- return new NonQueryExpression(new UpdateExpression(tableExpression, selectExpression, columnValueSetters));
- }
+ return IsValidSelectExpressionForExecuteUpdate(selectExpression, targetTable, out var tableExpression)
+ ? TranslateValueExpressions(this, source, selectExpression, tableExpression, propertyValueLambdaExpressions, columns)
+ : PushdownWithPkInnerJoinPredicate();
void PopulateSetPropertyCalls(
Expression expression,
@@ -1662,70 +1522,195 @@ when methodCallExpression.Method.DeclaringType.GetGenericTypeDefinition() == typ
}
}
- // For property setter selectors in ExecuteUpdate, we support only simple member access, EF.Function, etc.
- // We also unwrap casts to interface/base class (#29618). Note that owned IncludeExpressions have already been pruned from the
- // source before remapping the lambda (#28727).
- static bool TryProcessPropertyAccess(
- IModel model,
- ref Expression expression,
- [NotNullWhen(true)] out EntityShaperExpression? entityShaperExpression)
+ static NonQueryExpression? TranslateValueExpressions(
+ RelationalQueryableMethodTranslatingExpressionVisitor visitor,
+ ShapedQueryExpression source,
+ SelectExpression selectExpression,
+ TableExpression tableExpression,
+ List<(LambdaExpression PropertySelector, Expression ValueExpression)> propertyValueLambdaExpression,
+ ColumnExpression[] columns)
{
- expression = expression.UnwrapTypeConversion(out _);
+ var setters = new ColumnValueSetter[columns.Length];
- if (expression is MemberExpression { Expression : not null } memberExpression
- && memberExpression.Expression.UnwrapTypeConversion(out _) is EntityShaperExpression ese)
+ for (var i = 0; i < propertyValueLambdaExpression.Count; i++)
{
- expression = memberExpression.Update(ese);
- entityShaperExpression = ese;
- return true;
- }
+ var column = columns[i];
+ var (_, valueSelector) = propertyValueLambdaExpression[i];
- if (expression is MethodCallExpression mce)
- {
- if (mce.TryGetEFPropertyArguments(out var source, out _)
- && source.UnwrapTypeConversion(out _) is EntityShaperExpression ese1)
- {
- if (source != ese1)
- {
- var rewrittenArguments = mce.Arguments.ToArray();
- rewrittenArguments[0] = ese1;
- expression = mce.Update(mce.Object, rewrittenArguments);
- }
+ var remappedValueSelector = valueSelector is LambdaExpression lambdaExpression
+ ? visitor.RemapLambdaBody(source, lambdaExpression)
+ : valueSelector;
- entityShaperExpression = ese1;
- return true;
+ if (remappedValueSelector.Type != column.Type)
+ {
+ remappedValueSelector = Expression.Convert(remappedValueSelector, column.Type);
}
- if (mce.TryGetIndexerArguments(model, out var source2, out _)
- && source2.UnwrapTypeConversion(out _) is EntityShaperExpression ese2)
+ if (visitor.TranslateExpression(remappedValueSelector, applyDefaultTypeMapping: false)
+ is not SqlExpression translatedValueSelector)
{
- expression = mce.Update(ese2, mce.Arguments);
- entityShaperExpression = ese2;
- return true;
+ visitor.AddTranslationErrorDetails(RelationalStrings.InvalidValueInSetProperty(valueSelector.Print()));
+ return null;
}
+
+ // Apply the type mapping of the column (translated from the property selector above) to the value,
+ // and apply alias uniquification to it.
+ translatedValueSelector = visitor._sqlExpressionFactory.ApplyTypeMapping(translatedValueSelector, column.TypeMapping);
+ translatedValueSelector = selectExpression.AssignUniqueAliases(translatedValueSelector);
+
+ setters[i] = new ColumnValueSetter(column, translatedValueSelector);
}
- entityShaperExpression = null;
- return false;
+ selectExpression.ReplaceProjection(new List());
+ selectExpression.ApplyProjection();
+
+ return new NonQueryExpression(new UpdateExpression(tableExpression, selectExpression, setters));
}
- static Expression GetEntitySource(IModel model, Expression propertyAccessExpression)
+ NonQueryExpression? PushdownWithPkInnerJoinPredicate()
{
- propertyAccessExpression = propertyAccessExpression.UnwrapTypeConversion(out _);
- if (propertyAccessExpression is MethodCallExpression mce)
+ // The provider doesn't natively support the update.
+ // As a fallback, we place the original query in a subquery and user an INNER JOIN on the primary key columns.
+
+ // Note that unlike with ExecuteDelete, we cannot use a Contains subquery (which would produce the simpler
+ // WHERE Id IN (SELECT ...) syntax), since we allow projecting out to arbitrary shapes (e.g. anonymous types) before the
+ // ExecuteUpdate.
+
+ // To rewrite the query, we need to know the primary key properties, which requires getting the entity type.
+ // Although there may be several entity types involved, we've already verified that they all map to the same table.
+ // Since we don't support table sharing of multiple entity types with different keys, simply get the entity type and key from the
+ // first property selector.
+ var firstPropertySelector = propertyValueLambdaExpressions[0].PropertySelector;
+ var left = RemapLambdaBody(source, firstPropertySelector);
+ if (!TryExtractEntityType(left, RelationalDependencies.Model, out var entityType))
{
- if (mce.TryGetEFPropertyArguments(out var source, out _))
+ AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(firstPropertySelector));
+ return null;
+ }
+
+ var pk = entityType.FindPrimaryKey();
+ if (pk == null)
+ {
+ AddTranslationErrorDetails(
+ RelationalStrings.ExecuteOperationOnKeylessEntityTypeWithUnsupportedOperator(
+ nameof(RelationalQueryableExtensions.ExecuteUpdate),
+ entityType.DisplayName()));
+ return null;
+ }
+
+ // Generate the INNER JOIN around the original query, on the PK properties.
+ var outer = (ShapedQueryExpression)Visit(new EntityQueryRootExpression(entityType));
+ var inner = source;
+ var outerParameter = Expression.Parameter(entityType.ClrType);
+ var outerKeySelector = Expression.Lambda(outerParameter.CreateKeyValuesExpression(pk.Properties), outerParameter);
+ var firstPropertyLambdaExpression = propertyValueLambdaExpressions[0].Item1;
+ var entitySource = GetEntitySource(RelationalDependencies.Model, firstPropertyLambdaExpression.Body);
+ var innerKeySelector = Expression.Lambda(
+ entitySource.CreateKeyValuesExpression(pk.Properties), firstPropertyLambdaExpression.Parameters);
+
+ var joinPredicate = CreateJoinPredicate(outer, outerKeySelector, inner, innerKeySelector);
+
+ Check.DebugAssert(joinPredicate != null, "Join predicate shouldn't be null");
+
+ var outerSelectExpression = (SelectExpression)outer.QueryExpression;
+ var outerShaperExpression = outerSelectExpression.AddInnerJoin(inner, joinPredicate, outer.ShaperExpression);
+ outer = outer.UpdateShaperExpression(outerShaperExpression);
+ var transparentIdentifierType = outer.ShaperExpression.Type;
+ var transparentIdentifierParameter = Expression.Parameter(transparentIdentifierType);
+
+ var propertyReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Outer");
+ var valueReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Inner");
+ for (var i = 0; i < propertyValueLambdaExpressions.Count; i++)
+ {
+ var (propertyExpression, valueExpression) = propertyValueLambdaExpressions[i];
+ propertyExpression = Expression.Lambda(
+ ReplacingExpressionVisitor.Replace(
+ ReplacingExpressionVisitor.Replace(
+ firstPropertyLambdaExpression.Parameters[0],
+ propertyExpression.Parameters[0],
+ entitySource),
+ propertyReplacement, propertyExpression.Body),
+ transparentIdentifierParameter);
+
+ valueExpression = valueExpression is LambdaExpression lambdaExpression
+ ? Expression.Lambda(
+ ReplacingExpressionVisitor.Replace(lambdaExpression.Parameters[0], valueReplacement, lambdaExpression.Body),
+ transparentIdentifierParameter)
+ : valueExpression;
+
+ propertyValueLambdaExpressions[i] = (propertyExpression, valueExpression);
+ }
+
+ tableExpression = (TableExpression)outerSelectExpression.Tables[0];
+
+ // Re-translate the property selectors to get column expressions pointing to the new outer select expression (the original one
+ // has been pushed down into a subquery).
+ for (var i = 0; i < propertyValueLambdaExpressions.Count; i++)
+ {
+ var (propertySelector, _) = propertyValueLambdaExpressions[i];
+ var propertySelectorBody = RemapLambdaBody(outer, propertySelector).UnwrapTypeConversion(out _);
+
+ if (TranslateExpression(propertySelectorBody) is not ColumnExpression column)
{
- return source;
+ AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertySelector.Print()));
+ return null;
}
- if (mce.TryGetIndexerArguments(model, out var source2, out _))
+ columns[i] = column;
+ }
+
+ return TranslateValueExpressions(this, outer, outerSelectExpression, tableExpression, propertyValueLambdaExpressions, columns);
+
+ // For property setter selectors in ExecuteUpdate, we support only simple member access, EF.Function, etc.
+ // We also unwrap casts to interface/base class (#29618). Note that owned IncludeExpressions have already been pruned from the
+ // source before remapping the lambda (#28727).
+ bool TryExtractEntityType(Expression expression, IModel model, [NotNullWhen(true)] out IEntityType? entityType)
+ {
+ expression = expression.UnwrapTypeConversion(out _);
+
+ switch (expression)
{
- return source2;
+ case MemberExpression { Expression : not null } memberExpression
+ when memberExpression.Expression.UnwrapTypeConversion(out _) is EntityShaperExpression ese:
+ entityType = ese.EntityType;
+ return true;
+
+ case MethodCallExpression mce when mce.TryGetEFPropertyArguments(out var source, out _)
+ && source.UnwrapTypeConversion(out _) is EntityShaperExpression ese:
+ {
+ entityType = ese.EntityType;
+ return true;
+ }
+
+ case MethodCallExpression mce when mce.TryGetIndexerArguments(model, out var source2, out _)
+ && source2.UnwrapTypeConversion(out _) is EntityShaperExpression ese:
+ entityType = ese.EntityType;
+ return true;
+
+ default:
+ entityType = null;
+ return false;
}
}
- return ((MemberExpression)propertyAccessExpression).Expression!;
+ static Expression GetEntitySource(IModel model, Expression propertyAccessExpression)
+ {
+ propertyAccessExpression = propertyAccessExpression.UnwrapTypeConversion(out _);
+ if (propertyAccessExpression is MethodCallExpression mce)
+ {
+ if (mce.TryGetEFPropertyArguments(out var source, out _))
+ {
+ return source;
+ }
+
+ if (mce.TryGetIndexerArguments(model, out var source2, out _))
+ {
+ return source2;
+ }
+ }
+
+ return ((MemberExpression)propertyAccessExpression).Expression!;
+ }
}
}
@@ -1787,43 +1772,33 @@ protected virtual bool IsValidSelectExpressionForExecuteDelete(
///
///
/// The select expression to validate.
- /// The entity shaper expression on which the update operation is being applied.
+ /// TODO
/// The table expression from which rows are being deleted.
/// Returns if the current select expression can be used for update as-is, otherwise.
protected virtual bool IsValidSelectExpressionForExecuteUpdate(
SelectExpression selectExpression,
- EntityShaperExpression entityShaperExpression,
+ TableExpressionBase table,
[NotNullWhen(true)] out TableExpression? tableExpression)
{
tableExpression = null;
- if (selectExpression.Offset == null
- && selectExpression.Limit == null
- // If entity type has primary key then Distinct is no-op
- && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null)
- && selectExpression.GroupBy.Count == 0
- && selectExpression.Having == null
- && selectExpression.Orderings.Count == 0
- && selectExpression.Tables.Count > 0)
- {
- TableExpressionBase table;
- if (selectExpression.Tables.Count == 1)
+ if (selectExpression is
{
- table = selectExpression.Tables[0];
- }
- else
+ Offset: null,
+ Limit: null,
+ IsDistinct: false,
+ GroupBy: [],
+ Having: null,
+ Orderings: [],
+ Tables.Count: > 0
+ })
+ {
+ if (selectExpression.Tables.Count > 1)
{
- var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
- var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
- var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First());
- table = column.Table;
- if (ReferenceEquals(selectExpression.Tables[0], table))
+ // If the table we are looking for it first table, then we need to verify if we can lift the next table in FROM clause
+ if (ReferenceEquals(selectExpression.Tables[0], table)
+ && selectExpression.Tables[1] is not InnerJoinExpression and not CrossJoinExpression)
{
- // If the table we are looking for it first table, then we need to verify if we can lift the next table in FROM clause
- var secondTable = selectExpression.Tables[1];
- if (secondTable is not InnerJoinExpression and not CrossJoinExpression)
- {
- return false;
- }
+ return false;
}
if (table is JoinExpressionBase joinExpressionBase)
diff --git a/src/EFCore.Relational/Query/SqlExpressions/ColumnValueSetter.cs b/src/EFCore.Relational/Query/SqlExpressions/ColumnValueSetter.cs
index cd5bdb838eb..fb0ec5cce09 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/ColumnValueSetter.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/ColumnValueSetter.cs
@@ -12,41 +12,11 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
/// not used in application code.
///
///
-public class ColumnValueSetter
+/// A column to be updated.
+/// A value to be assigned to the column.
+[DebuggerDisplay("{DebuggerDisplay(),nq}")]
+public readonly record struct ColumnValueSetter(ColumnExpression Column, SqlExpression Value)
{
- ///
- /// Creates a new instance of the class.
- ///
- /// A column to be updated.
- /// A value to be assigned to the column.
- public ColumnValueSetter(ColumnExpression column, SqlExpression value)
- {
- Column = column;
- Value = value;
- }
-
- ///
- /// The column to update value of.
- ///
- public virtual ColumnExpression Column { get; }
-
- ///
- /// The value to be assigned to the column.
- ///
- public virtual SqlExpression Value { get; }
-
- ///
- public override bool Equals(object? obj)
- => obj != null
- && (ReferenceEquals(this, obj)
- || obj is ColumnValueSetter columnValueSetter
- && Equals(columnValueSetter));
-
- private bool Equals(ColumnValueSetter columnValueSetter)
- => Column == columnValueSetter.Column
- && Value == columnValueSetter.Value;
-
- ///
- public override int GetHashCode()
- => HashCode.Combine(Column, Value);
+ private string DebuggerDisplay()
+ => $"{Column.Print()} = {Value.Print()}";
}
diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs
index 74870389a66..16c5b9fb6e5 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Query.Internal;
namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@@ -480,96 +481,6 @@ public override int GetHashCode()
=> 0;
}
- private sealed class TpcTablesExpression : TableExpressionBase
- {
- public TpcTablesExpression(
- string? alias,
- IEntityType entityType,
- IReadOnlyList subSelectExpressions)
- : base(alias)
- {
- EntityType = entityType;
- SelectExpressions = subSelectExpressions;
- }
-
- private TpcTablesExpression(
- string? alias,
- IEntityType entityType,
- IReadOnlyList subSelectExpressions,
- IEnumerable? annotations)
- : base(alias, annotations)
- {
- EntityType = entityType;
- SelectExpressions = subSelectExpressions;
- }
-
- [NotNull]
- public override string? Alias
- {
- get => base.Alias!;
- internal set => base.Alias = value;
- }
-
- public IEntityType EntityType { get; }
-
- public IReadOnlyList SelectExpressions { get; }
-
- public TpcTablesExpression Prune(IReadOnlyList discriminatorValues)
- {
- var subSelectExpressions = discriminatorValues.Count == 0
- ? new List { SelectExpressions[0] }
- : SelectExpressions.Where(
- se =>
- discriminatorValues.Contains((string)((SqlConstantExpression)se.Projection[^1].Expression).Value!)).ToList();
-
- Check.DebugAssert(subSelectExpressions.Count > 0, "TPC must have at least 1 table selected.");
-
- return new TpcTablesExpression(Alias, EntityType, subSelectExpressions, GetAnnotations());
- }
-
- // This is implementation detail hence visitors are not supposed to see inside unless they really need to.
- protected override Expression VisitChildren(ExpressionVisitor visitor)
- => this;
-
- protected override TableExpressionBase CreateWithAnnotations(IEnumerable annotations)
- => new TpcTablesExpression(Alias, EntityType, SelectExpressions, annotations);
-
- protected override void Print(ExpressionPrinter expressionPrinter)
- {
- expressionPrinter.AppendLine("(");
- using (expressionPrinter.Indent())
- {
- expressionPrinter.VisitCollection(SelectExpressions, e => e.AppendLine().AppendLine("UNION ALL"));
- }
-
- expressionPrinter.AppendLine()
- .AppendLine(") AS " + Alias);
- PrintAnnotations(expressionPrinter);
- }
-
- ///
- public override bool Equals(object? obj)
- => obj != null
- && (ReferenceEquals(this, obj)
- || obj is TpcTablesExpression tpcTablesExpression
- && Equals(tpcTablesExpression));
-
- private bool Equals(TpcTablesExpression tpcTablesExpression)
- {
- if (!base.Equals(tpcTablesExpression)
- || EntityType != tpcTablesExpression.EntityType)
- {
- return false;
- }
-
- return SelectExpressions.SequenceEqual(tpcTablesExpression.SelectExpressions);
- }
-
- ///
- public override int GetHashCode()
- => HashCode.Combine(base.GetHashCode(), EntityType);
- }
-
private sealed class ConcreteColumnExpression : ColumnExpression
{
private readonly TableReferenceExpression _table;
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs
index 36101a611e9..b09202bddc4 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs
@@ -467,31 +467,21 @@ protected override bool IsValidSelectExpressionForExecuteDelete(
///
protected override bool IsValidSelectExpressionForExecuteUpdate(
SelectExpression selectExpression,
- EntityShaperExpression entityShaperExpression,
+ TableExpressionBase table,
[NotNullWhen(true)] out TableExpression? tableExpression)
{
- if (selectExpression.Offset == null
- // If entity type has primary key then Distinct is no-op
- && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null)
- && selectExpression.GroupBy.Count == 0
- && selectExpression.Having == null
- && selectExpression.Orderings.Count == 0)
- {
- TableExpressionBase table;
- if (selectExpression.Tables.Count == 1)
+ if (selectExpression is
{
- table = selectExpression.Tables[0];
- }
- else
+ Offset: null,
+ IsDistinct: false,
+ GroupBy: [],
+ Having: null,
+ Orderings: []
+ })
+ {
+ if (selectExpression.Tables.Count > 1 && table is JoinExpressionBase joinExpressionBase)
{
- var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
- var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
- var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First());
- table = column.Table;
- if (table is JoinExpressionBase joinExpressionBase)
- {
- table = joinExpressionBase.Table;
- }
+ table = joinExpressionBase.Table;
}
if (table is TableExpression te)
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs
index 686fb24805d..50ae5b0d70d 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/FiltersInheritanceBulkUpdatesTestBase.cs
@@ -97,7 +97,7 @@ public virtual Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task Update_where_hierarchy(bool async)
+ public virtual Task Update_base_type(bool async)
=> AssertUpdate(
async,
ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"),
@@ -106,6 +106,17 @@ public virtual Task Update_where_hierarchy(bool async)
rowsAffectedCount: 1,
(b, a) => a.ForEach(e => Assert.Equal("Animal", e.Name)));
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Update_base_type_with_OfType(bool async)
+ => AssertUpdate(
+ async,
+ ss => ss.Set().OfType(),
+ e => e,
+ s => s.SetProperty(e => e.Name, "NewBird"),
+ rowsAffectedCount: 1,
+ (b, a) => a.ForEach(e => Assert.Equal("NewBird", e.Name)));
+
[ConditionalTheory(Skip = "InnerJoin")]
[MemberData(nameof(IsAsyncData))]
public virtual Task Update_where_hierarchy_subquery(bool async)
@@ -118,12 +129,34 @@ public virtual Task Update_where_hierarchy_subquery(bool async)
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task Update_where_hierarchy_derived(bool async)
+ public virtual Task Update_base_property_on_derived_type(bool async)
=> AssertUpdate(
async,
- ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"),
+ ss => ss.Set(),
+ e => e,
+ s => s.SetProperty(e => e.Name, "SomeOtherKiwi"),
+ rowsAffectedCount: 1);
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Update_derived_property_on_derived_type(bool async)
+ => AssertUpdate(
+ async,
+ ss => ss.Set(),
+ e => e,
+ s => s.SetProperty(e => e.FoundOn, Island.North),
+ rowsAffectedCount: 1);
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Update_base_and_derived_types(bool async)
+ => AssertUpdate(
+ async,
+ ss => ss.Set(),
e => e,
- s => s.SetProperty(e => e.Name, "Kiwi"),
+ s => s
+ .SetProperty(e => e.Name, "Kiwi")
+ .SetProperty(e => e.FoundOn, Island.North),
rowsAffectedCount: 1);
[ConditionalTheory]
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs
index 684ef3b26cb..37829115a79 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/InheritanceBulkUpdatesTestBase.cs
@@ -97,7 +97,7 @@ public virtual Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task Update_where_hierarchy(bool async)
+ public virtual Task Update_base_type(bool async)
=> AssertUpdate(
async,
ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"),
@@ -106,6 +106,17 @@ public virtual Task Update_where_hierarchy(bool async)
rowsAffectedCount: 1,
(b, a) => a.ForEach(e => Assert.Equal("Animal", e.Name)));
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Update_base_type_with_OfType(bool async)
+ => AssertUpdate(
+ async,
+ ss => ss.Set().OfType(),
+ e => e,
+ s => s.SetProperty(e => e.Name, "NewBird"),
+ rowsAffectedCount: 1,
+ (b, a) => a.ForEach(e => Assert.Equal("NewBird", e.Name)));
+
[ConditionalTheory(Skip = "InnerJoin")]
[MemberData(nameof(IsAsyncData))]
public virtual Task Update_where_hierarchy_subquery(bool async)
@@ -118,12 +129,34 @@ public virtual Task Update_where_hierarchy_subquery(bool async)
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task Update_where_hierarchy_derived(bool async)
+ public virtual Task Update_base_property_on_derived_type(bool async)
=> AssertUpdate(
async,
- ss => ss.Set().Where(e => e.Name == "Great spotted kiwi"),
+ ss => ss.Set(),
+ e => e,
+ s => s.SetProperty(e => e.Name, "SomeOtherKiwi"),
+ rowsAffectedCount: 1);
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Update_derived_property_on_derived_type(bool async)
+ => AssertUpdate(
+ async,
+ ss => ss.Set(),
+ e => e,
+ s => s.SetProperty(e => e.FoundOn, Island.North),
+ rowsAffectedCount: 1);
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Update_base_and_derived_types(bool async)
+ => AssertUpdate(
+ async,
+ ss => ss.Set(),
e => e,
- s => s.SetProperty(e => e.Name, "Kiwi"),
+ s => s
+ .SetProperty(e => e.Name, "Kiwi")
+ .SetProperty(e => e.FoundOn, Island.North),
rowsAffectedCount: 1);
[ConditionalTheory]
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs
index fa128a8c0d0..ec8258347e9 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesTestBase.cs
@@ -127,6 +127,82 @@ await AssertUpdate(
rowsAffectedCount: 0);
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async)
+ {
+ var contextFactory = await InitializeAsync(
+ onModelCreating: mb =>
+ {
+ mb.Entity().OwnsOne(o => o.OwnedReference);
+ });
+
+ await AssertUpdate(
+ async,
+ contextFactory.CreateContext,
+ ss => ss.Set(),
+ s => s
+ .SetProperty(o => o.Title, o => o.OwnedReference.Number.ToString())
+ .SetProperty(o => o.OwnedReference.Number, o => o.Title.Length),
+ 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: context =>
+ {
+ context.Set().Add(new() { Title = "SomeBlog" });
+ context.SaveChanges();
+ });
+
+ 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: context =>
+ {
+ context.Set().Add(new() { Title = "SomeBlog" });
+ context.SaveChanges();
+ });
+
+ 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)
@@ -261,6 +337,8 @@ public class Blog
{
public int Id { get; set; }
public string? Title { get; set; }
+ public int Rating { get; set; }
+ public DateTime CreationTimestamp { get; set; }
public virtual ICollection Posts { get; } = new List();
}
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs
index d98df4b96e4..260692ee99f 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs
@@ -8,10 +8,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public abstract class NorthwindBulkUpdatesTestBase : BulkUpdatesTestBase
where TFixture : NorthwindBulkUpdatesFixture, new()
{
- protected NorthwindBulkUpdatesTestBase(TFixture fixture)
+ protected NorthwindBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalTheory]
@@ -779,24 +780,25 @@ public virtual Task Update_with_invalid_lambda_in_set_property_throws(bool async
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task Update_multiple_entity_throws(bool async)
+ public virtual Task Update_multiple_tables_throws(bool async)
=> AssertTranslationFailed(
- RelationalStrings.MultipleEntityPropertiesInSetProperty("Order", "Customer"),
+ RelationalStrings.MultipleTablesInExecuteUpdate("c => c.Customer.ContactName", "c => c.e.OrderDate"),
() => AssertUpdate(
async,
- ss => ss.Set().Where(o => o.CustomerID.StartsWith("F"))
+ 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)),
+ 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.UnableToTranslateSetProperty(
- "c => c.IsLondon", "True",
- CoreStrings.QueryUnableToTranslateMember("IsLondon", "Customer")),
+ RelationalStrings.InvalidPropertyInSetProperty("c => c.IsLondon"),
() => AssertUpdate(
async,
ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")),
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs
index e585a687079..f8a51fe0940 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesTestBase.cs
@@ -6,9 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public abstract class TPCFiltersInheritanceBulkUpdatesTestBase : FiltersInheritanceBulkUpdatesTestBase
where TFixture : TPCInheritanceBulkUpdatesFixture, new()
{
- protected TPCFiltersInheritanceBulkUpdatesTestBase(TFixture fixture)
+ protected TPCFiltersInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
+ ClearLog();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
// Keyless entities are mapped as TPH only
@@ -34,8 +36,13 @@ public override Task Delete_GroupBy_Where_Select_First_3(bool async)
public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
=> Task.CompletedTask;
- public override Task Update_where_hierarchy(bool async)
+ public override Task Update_base_type(bool async)
=> AssertTranslationFailed(
RelationalStrings.ExecuteOperationOnTPC("ExecuteUpdate", "Animal"),
- () => base.Update_where_hierarchy(async));
+ () => base.Update_base_type(async));
+
+ public override Task Update_base_type_with_OfType(bool async)
+ => AssertTranslationFailed(
+ RelationalStrings.ExecuteOperationOnTPC("ExecuteUpdate", "Animal"),
+ () => base.Update_base_type_with_OfType(async));
}
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs
index cfa978ebd86..9f8b5ff75d9 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPCInheritanceBulkUpdatesTestBase.cs
@@ -6,9 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public abstract class TPCInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesTestBase
where TFixture : TPCInheritanceBulkUpdatesFixture, new()
{
- protected TPCInheritanceBulkUpdatesTestBase(TFixture fixture)
+ protected TPCInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
+ ClearLog();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
// Keyless entities are mapped as TPH only
@@ -34,8 +36,13 @@ public override Task Delete_GroupBy_Where_Select_First_3(bool async)
public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
=> Task.CompletedTask;
- public override Task Update_where_hierarchy(bool async)
+ public override Task Update_base_type(bool async)
=> AssertTranslationFailed(
RelationalStrings.ExecuteOperationOnTPC("ExecuteUpdate", "Animal"),
- () => base.Update_where_hierarchy(async));
+ () => base.Update_base_type(async));
+
+ public override Task Update_base_type_with_OfType(bool async)
+ => AssertTranslationFailed(
+ RelationalStrings.ExecuteOperationOnTPC("ExecuteUpdate", "Animal"),
+ () => base.Update_base_type_with_OfType(async));
}
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs
index c930587aadb..d6ee12069a1 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs
@@ -6,9 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public abstract class TPTFiltersInheritanceBulkUpdatesTestBase : FiltersInheritanceBulkUpdatesTestBase
where TFixture : TPTInheritanceBulkUpdatesFixture, new()
{
- protected TPTFiltersInheritanceBulkUpdatesTestBase(TFixture fixture)
+ protected TPTFiltersInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
+ ClearLog();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
// Keyless entities are mapped as TPH only
@@ -35,17 +37,12 @@ public override Task Delete_GroupBy_Where_Select_First_3(bool async)
RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"),
() => base.Delete_GroupBy_Where_Select_First_3(async));
+ public override Task Update_base_and_derived_types(bool async)
+ => AssertTranslationFailed(
+ RelationalStrings.MultipleTablesInExecuteUpdate("e => e.Name", "e => e.FoundOn"),
+ () => base.Update_base_and_derived_types(async));
+
// Keyless entities are mapped as TPH only
public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
=> Task.CompletedTask;
-
- public override Task Update_where_hierarchy(bool async)
- => AssertTranslationFailed(
- RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Animal"),
- () => base.Update_where_hierarchy(async));
-
- public override Task Update_where_hierarchy_derived(bool async)
- => AssertTranslationFailed(
- RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Kiwi"),
- () => base.Update_where_hierarchy_derived(async));
}
diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs
index 1a4fe584e5e..b77f8214791 100644
--- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTInheritanceBulkUpdatesTestBase.cs
@@ -6,9 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public abstract class TPTInheritanceBulkUpdatesTestBase : InheritanceBulkUpdatesTestBase
where TFixture : TPTInheritanceBulkUpdatesFixture, new()
{
- protected TPTInheritanceBulkUpdatesTestBase(TFixture fixture)
+ protected TPTInheritanceBulkUpdatesTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ ClearLog();
}
// Keyless entities are mapped as TPH only
@@ -43,27 +45,12 @@ public override Task Delete_where_using_hierarchy(bool async)
public override Task Delete_where_using_hierarchy_derived(bool async)
=> base.Delete_where_using_hierarchy_derived(async);
+ public override Task Update_base_and_derived_types(bool async)
+ => AssertTranslationFailed(
+ RelationalStrings.MultipleTablesInExecuteUpdate("e => e.Name", "e => e.FoundOn"),
+ () => base.Update_base_and_derived_types(async));
+
// Keyless entities are mapped as TPH only
public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
=> Task.CompletedTask;
-
- public override Task Update_where_hierarchy(bool async)
- => AssertTranslationFailed(
- RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Animal"),
- () => base.Update_where_hierarchy(async));
-
- public override Task Update_where_hierarchy_derived(bool async)
- => AssertTranslationFailed(
- RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Kiwi"),
- () => base.Update_where_hierarchy_derived(async));
-
- public override Task Update_with_interface_in_property_expression(bool async)
- => AssertTranslationFailed(
- RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Coke"),
- () => base.Update_with_interface_in_property_expression(async));
-
- public override Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
- => AssertTranslationFailed(
- RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Coke"),
- () => base.Update_with_interface_in_EF_Property_in_property_expression(async));
}
diff --git a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs
index 40c3b4ee043..bf220d55065 100644
--- a/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/EntitySplittingTestBase.cs
@@ -67,35 +67,7 @@ await TestHelpers.ExecuteWithStrategyInTransactionAsync(
}
}
- [ConditionalTheory]
- [MemberData(nameof(IsAsyncData))]
- public virtual async Task ExecuteUpdate_throws_for_entity_splitting(bool async)
- {
- await InitializeAsync(OnModelCreating, sensitiveLogEnabled: true);
-
- if (async)
- {
- await TestHelpers.ExecuteWithStrategyInTransactionAsync(
- CreateContext,
- UseTransaction,
- async context => Assert.Contains(
- RelationalStrings.NonQueryTranslationFailedWithDetails(
- "", RelationalStrings.ExecuteOperationOnEntitySplitting("ExecuteUpdate", "MeterReading"))[21..],
- (await Assert.ThrowsAsync(
- () => context.MeterReadings.ExecuteUpdateAsync(s => s.SetProperty(m => m.CurrentRead, "Value")))).Message));
- }
- else
- {
- TestHelpers.ExecuteWithStrategyInTransaction(
- CreateContext,
- UseTransaction,
- context => Assert.Contains(
- RelationalStrings.NonQueryTranslationFailedWithDetails(
- "", RelationalStrings.ExecuteOperationOnEntitySplitting("ExecuteUpdate", "MeterReading"))[21..],
- Assert.Throws(
- () => context.MeterReadings.ExecuteUpdate(s => s.SetProperty(m => m.CurrentRead, "Value"))).Message));
- }
- }
+ // See additional tests bulk update tests in NonSharedModelBulkUpdatesTestBase
public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());
diff --git a/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs
index d2288b03edf..ab35341fddf 100644
--- a/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs
@@ -21,12 +21,6 @@ public override Task Can_use_optional_dependents_with_shared_concurrency_tokens(
public override Task ExecuteDelete_throws_for_table_sharing(bool async)
=> Task.CompletedTask;
- public override async Task ExecuteUpdate_works_for_table_sharing(bool async)
- => Assert.Contains(
- RelationalStrings.NonQueryTranslationFailedWithDetails(
- "", RelationalStrings.ExecuteOperationOnTPT("ExecuteUpdate", "Vehicle"))[21..],
- (await Assert.ThrowsAsync(() => base.ExecuteUpdate_works_for_table_sharing(async))).Message);
-
protected override string StoreName
=> "TPTTableSplittingTest";
diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs
index 04967e9564f..8274b492d5d 100644
--- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs
@@ -1876,27 +1876,13 @@ protected class OwnedPerson
public object this[string name]
{
- get
- {
- if (string.Equals(name, "Name", StringComparison.Ordinal))
- {
- return _name;
- }
+ get => string.Equals(name, "Name", StringComparison.Ordinal)
+ ? _name
+ : throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
- throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
- }
-
- set
- {
- if (string.Equals(name, "Name", StringComparison.Ordinal))
- {
- _name = (string)value;
- }
- else
- {
- throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
- }
- }
+ set => _name = string.Equals(name, "Name", StringComparison.Ordinal)
+ ? (string)value
+ : throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
}
public OwnedAddress PersonAddress { get; set; }
@@ -1914,27 +1900,13 @@ protected class Order
public object this[string name]
{
- get
- {
- if (string.Equals(name, "OrderDate", StringComparison.Ordinal))
- {
- return _orderDate;
- }
+ get => string.Equals(name, "OrderDate", StringComparison.Ordinal)
+ ? _orderDate
+ : throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
- throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
- }
-
- set
- {
- if (string.Equals(name, "OrderDate", StringComparison.Ordinal))
- {
- _orderDate = (DateTime)value;
- }
- else
- {
- throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
- }
- }
+ set => _orderDate = string.Equals(name, "OrderDate", StringComparison.Ordinal)
+ ? (DateTime)value
+ : throw new InvalidOperationException($"Indexer property with key {name} is not defined on {nameof(OwnedPerson)}.");
}
public OwnedPerson Client { get; set; }
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs
index ea96534a9ed..6adf756c725 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqlServerTest.cs
@@ -6,10 +6,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class FiltersInheritanceBulkUpdatesSqlServerTest : FiltersInheritanceBulkUpdatesTestBase<
FiltersInheritanceBulkUpdatesSqlServerFixture>
{
- public FiltersInheritanceBulkUpdatesSqlServerTest(FiltersInheritanceBulkUpdatesSqlServerFixture fixture)
+ public FiltersInheritanceBulkUpdatesSqlServerTest(
+ FiltersInheritanceBulkUpdatesSqlServerFixture fixture,
+ ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -133,9 +136,9 @@ OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
""");
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
AssertExecuteUpdateSql(
"""
@@ -146,6 +149,19 @@ FROM [Animals] AS [a]
""");
}
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'NewBird'
+FROM [Animals] AS [a]
+WHERE [a].[CountryId] = 1 AND [a].[Discriminator] = N'Kiwi'
+""");
+ }
+
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -153,16 +169,43 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE [a]
-SET [a].[Name] = N'Kiwi'
+SET [a].[Name] = N'SomeOtherKiwi'
FROM [Animals] AS [a]
-WHERE [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] = 1 AND [a].[Name] = N'Great spotted kiwi'
+WHERE [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] = 1
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[FoundOn] = CAST(0 AS tinyint)
+FROM [Animals] AS [a]
+WHERE [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] = 1
+""");
+ }
+
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[FoundOn] = CAST(0 AS tinyint),
+ [a].[Name] = N'Kiwi'
+FROM [Animals] AS [a]
+WHERE [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] = 1
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs
index 042ba6095d5..df6b054cc47 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqlServerTest.cs
@@ -135,9 +135,9 @@ OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
""");
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
AssertExecuteUpdateSql(
"""
@@ -148,6 +148,19 @@ FROM [Animals] AS [a]
""");
}
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'NewBird'
+FROM [Animals] AS [a]
+WHERE [a].[Discriminator] = N'Kiwi'
+""");
+ }
+
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -155,16 +168,29 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE [a]
-SET [a].[Name] = N'Kiwi'
+SET [a].[Name] = N'SomeOtherKiwi'
FROM [Animals] AS [a]
-WHERE [a].[Discriminator] = N'Kiwi' AND [a].[Name] = N'Great spotted kiwi'
+WHERE [a].[Discriminator] = N'Kiwi'
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[FoundOn] = CAST(0 AS tinyint)
+FROM [Animals] AS [a]
+WHERE [a].[Discriminator] = N'Kiwi'
""");
}
@@ -184,6 +210,20 @@ FROM [Animals] AS [a]
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[FoundOn] = CAST(0 AS tinyint),
+ [a].[Name] = N'Kiwi'
+FROM [Animals] AS [a]
+WHERE [a].[Discriminator] = N'Kiwi'
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs
index 21f550176c6..5bd99552d72 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs
@@ -65,6 +65,40 @@ FROM [Owner] AS [o]
""");
}
+ public override async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async)
+ {
+ await base.Update_owned_and_non_owned_properties_with_table_sharing(async);
+
+ AssertSql(
+"""
+UPDATE [o]
+SET [o].[OwnedReference_Number] = CAST(LEN([o].[Title]) AS int),
+ [o].[Title] = CONVERT(varchar(11), [o].[OwnedReference_Number])
+FROM [Owner] AS [o]
+""");
+ }
+
+ public override async Task Update_main_table_in_entity_with_entity_splitting(bool async)
+ {
+ await base.Update_main_table_in_entity_with_entity_splitting(async);
+
+ AssertSql(
+"""
+UPDATE [b]
+SET [b].[CreationTimestamp] = '2020-01-01T00:00:00.0000000'
+FROM [Blogs] AS [b]
+""");
+ }
+
+ public override async Task Update_non_main_table_in_entity_with_entity_splitting(bool async)
+ {
+ // #28643
+ await Assert.ThrowsAsync(
+ () => base.Update_non_main_table_in_entity_with_entity_splitting(async));
+
+ AssertSql();
+ }
+
public override async Task Delete_entity_with_auto_include(bool async)
{
await base.Delete_entity_with_auto_include(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs
index 72467e0a3bd..49430899ea3 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs
@@ -8,10 +8,8 @@ public class NorthwindBulkUpdatesSqlServerTest : NorthwindBulkUpdatesTestBase fixture,
ITestOutputHelper testOutputHelper)
- : base(fixture)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
- Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -971,7 +969,11 @@ public override async Task Update_Where_Distinct_set_constant(bool async)
UPDATE [c]
SET [c].[ContactName] = N'Updated'
FROM [Customers] AS [c]
-WHERE [c].[CustomerID] LIKE N'F%'
+INNER JOIN (
+ SELECT DISTINCT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region]
+ FROM [Customers] AS [c0]
+ WHERE [c0].[CustomerID] LIKE N'F%'
+) AS [t] ON [c].[CustomerID] = [t].[CustomerID]
""");
}
@@ -1122,9 +1124,9 @@ public override async Task Update_with_invalid_lambda_in_set_property_throws(boo
AssertExecuteUpdateSql();
}
- public override async Task Update_multiple_entity_throws(bool async)
+ public override async Task Update_multiple_tables_throws(bool async)
{
- await base.Update_multiple_entity_throws(async);
+ await base.Update_multiple_tables_throws(async);
AssertExecuteUpdateSql();
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs
index 61342d0e34c..6aa33917b35 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqlServerTest.cs
@@ -6,10 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class TPCFiltersInheritanceBulkUpdatesSqlServerTest : TPCFiltersInheritanceBulkUpdatesTestBase<
TPCFiltersInheritanceBulkUpdatesSqlServerFixture>
{
- public TPCFiltersInheritanceBulkUpdatesSqlServerTest(TPCFiltersInheritanceBulkUpdatesSqlServerFixture fixture)
- : base(fixture)
+ public TPCFiltersInheritanceBulkUpdatesSqlServerTest(
+ TPCFiltersInheritanceBulkUpdatesSqlServerFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
}
[ConditionalFact]
@@ -109,9 +110,16 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
AssertExecuteUpdateSql();
}
@@ -123,16 +131,29 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE [k]
-SET [k].[Name] = N'Kiwi'
+SET [k].[Name] = N'SomeOtherKiwi'
FROM [Kiwi] AS [k]
-WHERE [k].[CountryId] = 1 AND [k].[Name] = N'Great spotted kiwi'
+WHERE [k].[CountryId] = 1
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [k]
+SET [k].[FoundOn] = CAST(0 AS tinyint)
+FROM [Kiwi] AS [k]
+WHERE [k].[CountryId] = 1
""");
}
@@ -158,6 +179,20 @@ FROM [Kiwi] AS [k]
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [k]
+SET [k].[FoundOn] = CAST(0 AS tinyint),
+ [k].[Name] = N'Kiwi'
+FROM [Kiwi] AS [k]
+WHERE [k].[CountryId] = 1
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs
index 8912d92fafb..9d8853ec1f8 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqlServerTest.cs
@@ -8,10 +8,8 @@ public class TPCInheritanceBulkUpdatesSqlServerTest : TPCInheritanceBulkUpdatesT
public TPCInheritanceBulkUpdatesSqlServerTest(
TPCInheritanceBulkUpdatesSqlServerFixture fixture,
ITestOutputHelper testOutputHelper)
- : base(fixture)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
- Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -111,9 +109,16 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
AssertExecuteUpdateSql();
}
@@ -125,16 +130,27 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE [k]
-SET [k].[Name] = N'Kiwi'
+SET [k].[Name] = N'SomeOtherKiwi'
+FROM [Kiwi] AS [k]
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [k]
+SET [k].[FoundOn] = CAST(0 AS tinyint)
FROM [Kiwi] AS [k]
-WHERE [k].[Name] = N'Great spotted kiwi'
""");
}
@@ -160,6 +176,19 @@ FROM [Kiwi] AS [k]
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [k]
+SET [k].[FoundOn] = CAST(0 AS tinyint),
+ [k].[Name] = N'Kiwi'
+FROM [Kiwi] AS [k]
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs
index 37f7432301a..0d36ccb22eb 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs
@@ -6,10 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class TPTFiltersInheritanceBulkUpdatesSqlServerTest : TPTFiltersInheritanceBulkUpdatesTestBase<
TPTFiltersInheritanceBulkUpdatesSqlServerFixture>
{
- public TPTFiltersInheritanceBulkUpdatesSqlServerTest(TPTFiltersInheritanceBulkUpdatesSqlServerFixture fixture)
- : base(fixture)
+ public TPTFiltersInheritanceBulkUpdatesSqlServerTest(
+ TPTFiltersInheritanceBulkUpdatesSqlServerFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
}
[ConditionalFact]
@@ -101,11 +102,31 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'Animal'
+FROM [Animals] AS [a]
+WHERE [a].[CountryId] = 1 AND [a].[Name] = N'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'NewBird'
+FROM [Animals] AS [a]
+LEFT JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
+WHERE [a].[CountryId] = 1 AND [k].[Id] IS NOT NULL
+""");
}
public override async Task Update_where_hierarchy_subquery(bool async)
@@ -115,9 +136,39 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'SomeOtherKiwi'
+FROM [Animals] AS [a]
+INNER JOIN [Birds] AS [b] ON [a].[Id] = [b].[Id]
+INNER JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
+WHERE [a].[CountryId] = 1
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [k]
+SET [k].[FoundOn] = CAST(0 AS tinyint)
+FROM [Animals] AS [a]
+INNER JOIN [Birds] AS [b] ON [a].[Id] = [b].[Id]
+INNER JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
+WHERE [a].[CountryId] = 1
+""");
+ }
+
+ public override async Task Update_base_and_derived_types(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_and_derived_types(async);
AssertExecuteUpdateSql();
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs
index 8cc494db1a1..c3275ee1610 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqlServerTest.cs
@@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class TPTInheritanceBulkUpdatesSqlServerTest : TPTInheritanceBulkUpdatesTestBase
{
- public TPTInheritanceBulkUpdatesSqlServerTest(TPTInheritanceBulkUpdatesSqlServerFixture fixture)
- : base(fixture)
+ public TPTInheritanceBulkUpdatesSqlServerTest(TPTInheritanceBulkUpdatesSqlServerFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
ClearLog();
}
@@ -78,11 +78,31 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'Animal'
+FROM [Animals] AS [a]
+WHERE [a].[Name] = N'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'NewBird'
+FROM [Animals] AS [a]
+LEFT JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
+WHERE [k].[Id] IS NOT NULL
+""");
}
public override async Task Update_where_hierarchy_subquery(bool async)
@@ -92,9 +112,37 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
+ {
+ await base.Update_base_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [a]
+SET [a].[Name] = N'SomeOtherKiwi'
+FROM [Animals] AS [a]
+INNER JOIN [Birds] AS [b] ON [a].[Id] = [b].[Id]
+INNER JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE [k]
+SET [k].[FoundOn] = CAST(0 AS tinyint)
+FROM [Animals] AS [a]
+INNER JOIN [Birds] AS [b] ON [a].[Id] = [b].[Id]
+INNER JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
+""");
+ }
+
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
AssertExecuteUpdateSql();
}
@@ -148,14 +196,26 @@ public override async Task Update_with_interface_in_property_expression(bool asy
{
await base.Update_with_interface_in_property_expression(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE [c]
+SET [c].[SugarGrams] = 0
+FROM [Drinks] AS [d]
+INNER JOIN [Coke] AS [c] ON [d].[Id] = [c].[Id]
+""");
}
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE [c]
+SET [c].[SugarGrams] = 0
+FROM [Drinks] AS [d]
+INNER JOIN [Coke] AS [c] ON [d].[Id] = [c].[Id]
+""");
}
protected override void ClearLog()
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs
index cb70ca68fd4..db45171e8a6 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs
@@ -5,10 +5,13 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class FiltersInheritanceBulkUpdatesSqliteTest : FiltersInheritanceBulkUpdatesTestBase
{
- public FiltersInheritanceBulkUpdatesSqliteTest(FiltersInheritanceBulkUpdatesSqliteFixture fixture)
+ public FiltersInheritanceBulkUpdatesSqliteTest(
+ FiltersInheritanceBulkUpdatesSqliteFixture fixture,
+ ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -127,9 +130,9 @@ HAVING COUNT(*) < 3
""");
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
AssertExecuteUpdateSql(
"""
@@ -139,6 +142,18 @@ public override async Task Update_where_hierarchy(bool async)
""");
}
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "Name" = 'NewBird'
+WHERE "a"."CountryId" = 1 AND "a"."Discriminator" = 'Kiwi'
+""");
+ }
+
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -146,15 +161,27 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE "Animals" AS "a"
-SET "Name" = 'Kiwi'
-WHERE "a"."Discriminator" = 'Kiwi' AND "a"."CountryId" = 1 AND "a"."Name" = 'Great spotted kiwi'
+SET "Name" = 'SomeOtherKiwi'
+WHERE "a"."Discriminator" = 'Kiwi' AND "a"."CountryId" = 1
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "FoundOn" = 0
+WHERE "a"."Discriminator" = 'Kiwi' AND "a"."CountryId" = 1
""");
}
@@ -173,6 +200,19 @@ SELECT COUNT(*)
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "FoundOn" = 0,
+ "Name" = 'Kiwi'
+WHERE "a"."Discriminator" = 'Kiwi' AND "a"."CountryId" = 1
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs
index 775e7fd914e..e82624853d9 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs
@@ -129,9 +129,9 @@ HAVING COUNT(*) < 3
""");
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
AssertExecuteUpdateSql(
"""
@@ -141,6 +141,18 @@ public override async Task Update_where_hierarchy(bool async)
""");
}
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "Name" = 'NewBird'
+WHERE "a"."Discriminator" = 'Kiwi'
+""");
+ }
+
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -148,15 +160,27 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE "Animals" AS "a"
-SET "Name" = 'Kiwi'
-WHERE "a"."Discriminator" = 'Kiwi' AND "a"."Name" = 'Great spotted kiwi'
+SET "Name" = 'SomeOtherKiwi'
+WHERE "a"."Discriminator" = 'Kiwi'
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "FoundOn" = 0
+WHERE "a"."Discriminator" = 'Kiwi'
""");
}
@@ -175,6 +199,19 @@ SELECT COUNT(*)
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "FoundOn" = 0,
+ "Name" = 'Kiwi'
+WHERE "a"."Discriminator" = 'Kiwi'
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs
index 92f1d4ef8b9..86a4199b3dc 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs
@@ -61,6 +61,41 @@ public override async Task Update_non_owned_property_on_entity_with_owned2(bool
""");
}
+ public override async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async)
+ {
+ await base.Update_owned_and_non_owned_properties_with_table_sharing(async);
+
+ AssertSql(
+"""
+UPDATE "Owner" AS "o"
+SET "OwnedReference_Number" = length("o"."Title"),
+ "Title" = CAST("o"."OwnedReference_Number" AS TEXT)
+""");
+ }
+
+ public override async Task Update_main_table_in_entity_with_entity_splitting(bool async)
+ {
+ await base.Update_main_table_in_entity_with_entity_splitting(async);
+
+ AssertSql(
+"""
+UPDATE "Blogs" AS "b"
+SET "CreationTimestamp" = '2020-01-01 00:00:00'
+""");
+ }
+
+ public override async Task Update_non_main_table_in_entity_with_entity_splitting(bool async)
+ {
+ await base.Update_non_main_table_in_entity_with_entity_splitting(async);
+
+ AssertSql(
+"""
+UPDATE "BlogsPart1" AS "b0"
+SET "Rating" = length("b0"."Title"),
+ "Title" = CAST("b0"."Rating" AS TEXT)
+""");
+ }
+
public override Task Delete_entity_with_auto_include(bool async)
=> Assert.ThrowsAsync(() => base.Delete_entity_with_auto_include(async));
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs
index 8b11f496cc1..73e572a8f1c 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs
@@ -10,10 +10,8 @@ public class NorthwindBulkUpdatesSqliteTest : NorthwindBulkUpdatesTestBase fixture,
ITestOutputHelper testOutputHelper)
- : base(fixture)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
- Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -946,7 +944,12 @@ public override async Task Update_Where_Distinct_set_constant(bool async)
"""
UPDATE "Customers" AS "c"
SET "ContactName" = 'Updated'
-WHERE "c"."CustomerID" LIKE 'F%'
+FROM (
+ SELECT DISTINCT "c0"."CustomerID", "c0"."Address", "c0"."City", "c0"."CompanyName", "c0"."ContactName", "c0"."ContactTitle", "c0"."Country", "c0"."Fax", "c0"."Phone", "c0"."PostalCode", "c0"."Region"
+ FROM "Customers" AS "c0"
+ WHERE "c0"."CustomerID" LIKE 'F%'
+) AS "t"
+WHERE "c"."CustomerID" = "t"."CustomerID"
""");
}
@@ -1093,9 +1096,9 @@ public override async Task Update_with_invalid_lambda_in_set_property_throws(boo
AssertExecuteUpdateSql();
}
- public override async Task Update_multiple_entity_throws(bool async)
+ public override async Task Update_multiple_tables_throws(bool async)
{
- await base.Update_multiple_entity_throws(async);
+ await base.Update_multiple_tables_throws(async);
AssertExecuteUpdateSql();
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs
index 31a8814172b..bf6bac6dd4d 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs
@@ -6,10 +6,11 @@ namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class TPCFiltersInheritanceBulkUpdatesSqliteTest : TPCFiltersInheritanceBulkUpdatesTestBase<
TPCFiltersInheritanceBulkUpdatesSqliteFixture>
{
- public TPCFiltersInheritanceBulkUpdatesSqliteTest(TPCFiltersInheritanceBulkUpdatesSqliteFixture fixture)
- : base(fixture)
+ public TPCFiltersInheritanceBulkUpdatesSqliteTest(
+ TPCFiltersInheritanceBulkUpdatesSqliteFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
}
[ConditionalFact]
@@ -106,9 +107,16 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
AssertExecuteUpdateSql();
}
@@ -120,15 +128,27 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE "Kiwi" AS "k"
-SET "Name" = 'Kiwi'
-WHERE "k"."CountryId" = 1 AND "k"."Name" = 'Great spotted kiwi'
+SET "Name" = 'SomeOtherKiwi'
+WHERE "k"."CountryId" = 1
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Kiwi" AS "k"
+SET "FoundOn" = 0
+WHERE "k"."CountryId" = 1
""");
}
@@ -153,6 +173,19 @@ UNION ALL
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+ """
+UPDATE "Kiwi" AS "k"
+SET "FoundOn" = 0,
+ "Name" = 'Kiwi'
+WHERE "k"."CountryId" = 1
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs
index 4a52aad1c83..14f0c7e7641 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs
@@ -8,10 +8,8 @@ public class TPCInheritanceBulkUpdatesSqliteTest : TPCInheritanceBulkUpdatesTest
public TPCInheritanceBulkUpdatesSqliteTest(
TPCInheritanceBulkUpdatesSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
- : base(fixture)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
- Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -108,9 +106,16 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_base_type_with_OfType(bool async)
+ {
+ await base.Update_base_type_with_OfType(async);
AssertExecuteUpdateSql();
}
@@ -122,15 +127,25 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ public override async Task Update_base_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_base_property_on_derived_type(async);
AssertExecuteUpdateSql(
"""
UPDATE "Kiwi" AS "k"
-SET "Name" = 'Kiwi'
-WHERE "k"."Name" = 'Great spotted kiwi'
+SET "Name" = 'SomeOtherKiwi'
+""");
+ }
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
+ {
+ await base.Update_derived_property_on_derived_type(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Kiwi" AS "k"
+SET "FoundOn" = 0
""");
}
@@ -155,6 +170,18 @@ UNION ALL
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Kiwi" AS "k"
+SET "FoundOn" = 0,
+ "Name" = 'Kiwi'
+""");
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs
index 396a55f3bd5..c42ad2bdb65 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs
@@ -1,15 +1,18 @@
// 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;
+
namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class TPTFiltersInheritanceBulkUpdatesSqliteTest : TPTFiltersInheritanceBulkUpdatesTestBase<
TPTFiltersInheritanceBulkUpdatesSqliteFixture>
{
- public TPTFiltersInheritanceBulkUpdatesSqliteTest(TPTFiltersInheritanceBulkUpdatesSqliteFixture fixture)
- : base(fixture)
+ public TPTFiltersInheritanceBulkUpdatesSqliteTest(
+ TPTFiltersInheritanceBulkUpdatesSqliteFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
}
[ConditionalFact]
@@ -99,13 +102,33 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "Name" = 'Animal'
+FROM (
+ SELECT "a0"."Id", "a0"."CountryId", "a0"."Name", "a0"."Species", "b0"."EagleId", "b0"."IsFlightless", "e0"."Group", "k0"."FoundOn", CASE
+ WHEN "k0"."Id" IS NOT NULL THEN 'Kiwi'
+ WHEN "e0"."Id" IS NOT NULL THEN 'Eagle'
+ END AS "Discriminator"
+ FROM "Animals" AS "a0"
+ LEFT JOIN "Birds" AS "b0" ON "a0"."Id" = "b0"."Id"
+ LEFT JOIN "Eagle" AS "e0" ON "a0"."Id" = "e0"."Id"
+ LEFT JOIN "Kiwi" AS "k0" ON "a0"."Id" = "k0"."Id"
+ WHERE "a0"."CountryId" = 1 AND "a0"."Name" = 'Great spotted kiwi'
+) AS "t"
+WHERE "a"."Id" = "t"."Id"
+""");
}
+ // #31402
+ public override Task Update_base_type_with_OfType(bool async)
+ => Assert.ThrowsAsync(() => base.Update_base_property_on_derived_type(async));
+
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -113,11 +136,22 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ // #31402
+ public override Task Update_base_property_on_derived_type(bool async)
+ => Assert.ThrowsAsync(() => base.Update_base_property_on_derived_type(async));
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_derived_property_on_derived_type(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Kiwi" AS "k"
+SET "FoundOn" = 0
+FROM "Animals" AS "a"
+INNER JOIN "Birds" AS "b" ON "a"."Id" = "b"."Id"
+WHERE "a"."Id" = "k"."Id" AND "a"."CountryId" = 1
+""");
}
public override async Task Update_where_using_hierarchy(bool async)
@@ -138,6 +172,13 @@ SELECT COUNT(*)
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql();
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs
index 04dddbe476a..e111a974deb 100644
--- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.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.Data.Sqlite;
+
namespace Microsoft.EntityFrameworkCore.BulkUpdates;
public class TPTInheritanceBulkUpdatesSqliteTest : TPTInheritanceBulkUpdatesTestBase
@@ -8,10 +10,8 @@ public class TPTInheritanceBulkUpdatesSqliteTest : TPTInheritanceBulkUpdatesTest
public TPTInheritanceBulkUpdatesSqliteTest(
TPTInheritanceBulkUpdatesSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
- : base(fixture)
+ : base(fixture, testOutputHelper)
{
- ClearLog();
- Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
[ConditionalFact]
@@ -81,13 +81,33 @@ public override async Task Delete_where_hierarchy_subquery(bool async)
AssertSql();
}
- public override async Task Update_where_hierarchy(bool async)
+ public override async Task Update_base_type(bool async)
{
- await base.Update_where_hierarchy(async);
+ await base.Update_base_type(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Animals" AS "a"
+SET "Name" = 'Animal'
+FROM (
+ SELECT "a0"."Id", "a0"."CountryId", "a0"."Name", "a0"."Species", "b0"."EagleId", "b0"."IsFlightless", "e0"."Group", "k0"."FoundOn", CASE
+ WHEN "k0"."Id" IS NOT NULL THEN 'Kiwi'
+ WHEN "e0"."Id" IS NOT NULL THEN 'Eagle'
+ END AS "Discriminator"
+ FROM "Animals" AS "a0"
+ LEFT JOIN "Birds" AS "b0" ON "a0"."Id" = "b0"."Id"
+ LEFT JOIN "Eagle" AS "e0" ON "a0"."Id" = "e0"."Id"
+ LEFT JOIN "Kiwi" AS "k0" ON "a0"."Id" = "k0"."Id"
+ WHERE "a0"."Name" = 'Great spotted kiwi'
+) AS "t"
+WHERE "a"."Id" = "t"."Id"
+""");
}
+ // #31402
+ public override Task Update_base_type_with_OfType(bool async)
+ => Assert.ThrowsAsync(() => base.Update_base_property_on_derived_type(async));
+
public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);
@@ -95,11 +115,22 @@ public override async Task Update_where_hierarchy_subquery(bool async)
AssertExecuteUpdateSql();
}
- public override async Task Update_where_hierarchy_derived(bool async)
+ // #31402
+ public override Task Update_base_property_on_derived_type(bool async)
+ => Assert.ThrowsAsync(() => base.Update_base_property_on_derived_type(async));
+
+ public override async Task Update_derived_property_on_derived_type(bool async)
{
- await base.Update_where_hierarchy_derived(async);
+ await base.Update_derived_property_on_derived_type(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Kiwi" AS "k"
+SET "FoundOn" = 0
+FROM "Animals" AS "a"
+INNER JOIN "Birds" AS "b" ON "a"."Id" = "b"."Id"
+WHERE "a"."Id" = "k"."Id"
+""");
}
public override async Task Update_where_using_hierarchy(bool async)
@@ -120,6 +151,13 @@ SELECT COUNT(*)
""");
}
+ public override async Task Update_base_and_derived_types(bool async)
+ {
+ await base.Update_base_and_derived_types(async);
+
+ AssertExecuteUpdateSql();
+ }
+
public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);
@@ -149,14 +187,26 @@ public override async Task Update_with_interface_in_property_expression(bool asy
{
await base.Update_with_interface_in_property_expression(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Coke" AS "c"
+SET "SugarGrams" = 0
+FROM "Drinks" AS "d"
+WHERE "d"."Id" = "c"."Id"
+""");
}
public override async Task Update_with_interface_in_EF_Property_in_property_expression(bool async)
{
await base.Update_with_interface_in_EF_Property_in_property_expression(async);
- AssertExecuteUpdateSql();
+ AssertExecuteUpdateSql(
+"""
+UPDATE "Coke" AS "c"
+SET "SugarGrams" = 0
+FROM "Drinks" AS "d"
+WHERE "d"."Id" = "c"."Id"
+""");
}
protected override void ClearLog()