Skip to content

Commit

Permalink
Multiple entity support for ExecuteUpdate
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Aug 3, 2023
1 parent a1c3aba commit 727876f
Show file tree
Hide file tree
Showing 34 changed files with 1,282 additions and 643 deletions.
24 changes: 12 additions & 12 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,9 @@
<data name="InvalidPropertyInSetProperty" xml:space="preserve">
<value>The following lambda argument to 'SetProperty' does not represent a valid property to be set: '{propertyExpression}'.</value>
</data>
<data name="InvalidValueInSetProperty" xml:space="preserve">
<value>The following lambda argument to 'SetProperty' does not represent a valid value: '{valueExpression}'.</value>
</data>
<data name="JsonCantNavigateToParentEntity" xml:space="preserve">
<value>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.</value>
</data>
Expand Down Expand Up @@ -905,8 +908,8 @@
<data name="ModificationCommandInvalidEntityStateSensitive" xml:space="preserve">
<value>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.</value>
</data>
<data name="MultipleEntityPropertiesInSetProperty" xml:space="preserve">
<value>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.</value>
<data name="MultipleTablesInExecuteUpdate" xml:space="preserve">
<value>Multiple 'SetProperty' invocations refer to different tables ('{propertySelector1}' and '{propertySelector2}'). A single 'ExecuteUpdate' call can only update the columns of a single table.</value>
</data>
<data name="MultipleProvidersConfigured" xml:space="preserve">
<value>Multiple relational database provider configurations found. A context can only be configured to use a single database provider.</value>
Expand Down Expand Up @@ -1178,9 +1181,6 @@
<data name="UnableToBindMemberToEntityProjection" xml:space="preserve">
<value>Unable to bind '{memberType}.{member}' to an entity projection of '{entityType}'.</value>
</data>
<data name="UnableToTranslateSetProperty" xml:space="preserve">
<value>The following 'SetProperty' failed to translate: 'SetProperty({property}, {value})'. {details}</value>
</data>
<data name="UnhandledAnnotatableType" xml:space="preserve">
<value>Unhandled annotatable type '{annotatableType}'.</value>
</data>
Expand Down
151 changes: 151 additions & 0 deletions src/EFCore.Relational/Query/Internal/TpcTablesExpression.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
public sealed class TpcTablesExpression : TableExpressionBase
{
/// <summary>
/// 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.
/// </summary>
public TpcTablesExpression(
string? alias,
IEntityType entityType,
IReadOnlyList<SelectExpression> subSelectExpressions)
: base(alias)
{
EntityType = entityType;
SelectExpressions = subSelectExpressions;
}

private TpcTablesExpression(
string? alias,
IEntityType entityType,
IReadOnlyList<SelectExpression> subSelectExpressions,
IEnumerable<IAnnotation>? annotations)
: base(alias, annotations)
{
EntityType = entityType;
SelectExpressions = subSelectExpressions;
}

/// <summary>
/// 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.
/// </summary>
[NotNull]
public override string? Alias
{
get => base.Alias!;
internal set => base.Alias = value;
}

/// <summary>
/// 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.
/// </summary>
public IEntityType EntityType { get; }

/// <summary>
/// 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.
/// </summary>
public IReadOnlyList<SelectExpression> SelectExpressions { get; }

/// <summary>
/// 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.
/// </summary>
public TpcTablesExpression Prune(IReadOnlyList<string> discriminatorValues)
{
var subSelectExpressions = discriminatorValues.Count == 0
? new List<SelectExpression> { 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());
}

/// <summary>
/// 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.
/// </summary>
// This is implementation detail hence visitors are not supposed to see inside unless they really need to.
protected override Expression VisitChildren(ExpressionVisitor visitor)
=> this;

/// <summary>
/// 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.
/// </summary>
protected override TableExpressionBase CreateWithAnnotations(IEnumerable<IAnnotation> annotations)
=> new TpcTablesExpression(Alias, EntityType, SelectExpressions, annotations);

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

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

/// <inheritdoc />
public override int GetHashCode()
=> HashCode.Combine(base.GetHashCode(), EntityType);
}
Loading

0 comments on commit 727876f

Please sign in to comment.