Skip to content

Commit 46c9c1d

Browse files
committed
Temp tables and table hints are handled differently, so that all limitations regarding SplittingBehavior and OwnedTypes are lifted.
1 parent a18899c commit 46c9c1d

File tree

33 files changed

+596
-427
lines changed

33 files changed

+596
-427
lines changed

src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/Query/BulkOperationOptimizingVisitor.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/Query/SqlExpressions/TempTableExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public sealed class TempTableExpression : TableExpressionBase, INotNullableSqlEx
2323
/// </summary>
2424
/// <param name="name">The name of the temp table.</param>
2525
/// <param name="alias">The alias of the temp table.</param>
26-
public TempTableExpression(string name, string alias)
26+
public TempTableExpression(string name, string? alias)
2727
: base(alias)
2828
{
2929
Name = name;

src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableQueryContext.cs

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableQueryContextFactory.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
2+
using Thinktecture.EntityFrameworkCore.Query;
3+
using Thinktecture.EntityFrameworkCore.Query.SqlExpressions;
4+
5+
namespace Thinktecture.EntityFrameworkCore.TempTables;
6+
7+
/// <summary>
8+
/// For internal use only.
9+
/// </summary>
10+
public class TempTableTableMetadataProcessor : ITableMetadataProcessor
11+
{
12+
/// <summary>
13+
/// Singleton.
14+
/// </summary>
15+
public static readonly TempTableTableMetadataProcessor Instance = new();
16+
17+
/// <summary>
18+
/// For internal use only.
19+
/// </summary>
20+
public int Order => 10;
21+
22+
/// <summary>
23+
/// For internal use only.
24+
/// </summary>
25+
public TableExpressionBase ProcessMetadata(TableExpressionBase expression, TableWithMetadata tableWithMetadata)
26+
{
27+
if (tableWithMetadata.Metadata.TryGetValue(nameof(TempTableExpression), out var value) && value is string tempTableName)
28+
return new TempTableExpression(tempTableName, expression.Alias);
29+
30+
return expression;
31+
}
32+
}

src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
66
using Microsoft.EntityFrameworkCore.Storage;
77
using Thinktecture.EntityFrameworkCore.Query.SqlExpressions;
8-
using Thinktecture.EntityFrameworkCore.TempTables;
98
using Thinktecture.Internal;
109

1110
// ReSharper disable once CheckNamespace
@@ -22,8 +21,6 @@ public static class BulkOperationsRelationalQueryableMethodTranslatingExpression
2221
/// <param name="visitor">The visitor.</param>
2322
/// <param name="methodCallExpression">Method call to translate.</param>
2423
/// <param name="typeMappingSource">Type mapping source.</param>
25-
/// <param name="queryCompilationContext">Query compilation context.</param>
26-
/// <param name="tempTableQueryContextFactory"></param>
2724
/// <param name="sqlExpressionFactory">SQL expression factory.</param>
2825
/// <returns>Translated method call if a custom method is found; otherwise <c>null</c>.</returns>
2926
/// <exception cref="ArgumentNullException">
@@ -33,8 +30,6 @@ public static class BulkOperationsRelationalQueryableMethodTranslatingExpression
3330
this RelationalQueryableMethodTranslatingExpressionVisitor visitor,
3431
MethodCallExpression methodCallExpression,
3532
IRelationalTypeMappingSource typeMappingSource,
36-
QueryCompilationContext queryCompilationContext,
37-
TempTableQueryContextFactory tempTableQueryContextFactory,
3833
ISqlExpressionFactory sqlExpressionFactory)
3934
{
4035
ArgumentNullException.ThrowIfNull(visitor);
@@ -53,13 +48,9 @@ public static class BulkOperationsRelationalQueryableMethodTranslatingExpression
5348
if (methodCallExpression.Method.Name == nameof(BulkOperationsDbSetExtensions.FromTempTable))
5449
{
5550
var tempTableInfo = ((TempTableInfoExpression)methodCallExpression.Arguments[1]).Value;
56-
57-
if (!tempTableInfo.HasOwnedEntities)
58-
return CreateShapedQueryExpressionForTempTable(sqlExpressionFactory, tempTableInfo);
59-
6051
var shapedQueryExpression = GetShapedQueryExpression(visitor, methodCallExpression);
6152

62-
return TranslateFromTempTable(shapedQueryExpression, tempTableInfo, queryCompilationContext, tempTableQueryContextFactory);
53+
return TranslateFromTempTable(shapedQueryExpression, tempTableInfo);
6354
}
6455

6556
throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print()));
@@ -70,32 +61,14 @@ public static class BulkOperationsRelationalQueryableMethodTranslatingExpression
7061

7162
private static Expression TranslateFromTempTable(
7263
ShapedQueryExpression shapedQueryExpression,
73-
TempTableInfo tempTableInfo,
74-
QueryCompilationContext queryCompilationContext,
75-
TempTableQueryContextFactory tempTableQueryContextFactory)
64+
TempTableInfo tempTableInfo)
7665
{
7766
var tempTableName = tempTableInfo.Name ?? throw new Exception("No temp table name provided.");
78-
var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression;
79-
var tableExpression = (TableExpression)selectExpression.Tables.Single();
8067

81-
var ctx = tempTableQueryContextFactory.Create(tableExpression, tempTableName ?? throw new Exception("No temp table name provided."));
82-
var extractor = Expression.Lambda<Func<QueryContext, TempTableQueryContext>>(Expression.Constant(ctx), QueryCompilationContext.QueryContextParameter);
83-
84-
queryCompilationContext.RegisterRuntimeParameter(ctx.ParameterName, extractor);
85-
86-
return shapedQueryExpression;
87-
}
88-
89-
private static Expression CreateShapedQueryExpressionForTempTable(ISqlExpressionFactory sqlExpressionFactory, TempTableInfo tempTableInfo)
90-
{
91-
var tempTableName = tempTableInfo.Name ?? throw new Exception("No temp table name provided.");
92-
var tempTableExpression = new TempTableExpression(tempTableName, "#");
93-
var selectExpression = sqlExpressionFactory.Select(tempTableInfo.EntityType, tempTableExpression);
68+
var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression;
69+
var newSelectExpression = selectExpression.AddTableMetadata(nameof(TempTableExpression), _ => tempTableName);
9470

95-
return new ShapedQueryExpression(selectExpression,
96-
new RelationalEntityShaperExpression(tempTableInfo.EntityType,
97-
new ProjectionBindingExpression(selectExpression, new ProjectionMember(), typeof(ValueBuffer)),
98-
false));
71+
return shapedQueryExpression.Update(newSelectExpression, shapedQueryExpression.ShaperExpression);
9972
}
10073

10174
private static Expression TranslateBulkDelete(ShapedQueryExpression shapedQueryExpression, IRelationalTypeMappingSource typeMappingSource)
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
using Microsoft.EntityFrameworkCore.Metadata;
2-
31
namespace Thinktecture.Internal;
42

53
/// <summary>
64
/// Temp table infos.
75
/// </summary>
86
/// <param name="Name">The name of the temp table.</param>
9-
/// <param name="HasOwnedEntities">Indication whether the temp table has owned entities.</param>
10-
/// <param name="EntityType">The entity type of the temp table.</param>
11-
public record TempTableInfo(string Name, bool HasOwnedEntities, IEntityType EntityType);
7+
public record TempTableInfo(string Name);

src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtension.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ public bool UseThinktectureRelationalQueryContextFactory
8080

8181
internal List<ConventionToRemove> ConventionsToRemove { get; }
8282

83+
private readonly List<ITableMetadataProcessor> _metadataProcessors;
84+
internal IReadOnlyList<ITableMetadataProcessor> MetadataProcessors => _metadataProcessors;
85+
8386
/// <summary>
8487
/// Initializes new instance of <see cref="RelationalDbContextOptionsExtension"/>.
8588
/// </summary>
@@ -88,6 +91,7 @@ public RelationalDbContextOptionsExtension()
8891
_serviceDescriptors = new List<ServiceDescriptor>();
8992
_evaluatableExpressionFilterPlugins = new List<Type>();
9093
ConventionsToRemove = new List<ConventionToRemove>();
94+
_metadataProcessors = new List<ITableMetadataProcessor> { TableHintsTableMetadataProcessor.Instance };
9195
_stringBuilderPolicy = new StringBuilderPooledObjectPolicy
9296
{
9397
InitialCapacity = _DEFAULT_INITIAL_CAPACITY,
@@ -103,6 +107,8 @@ public void ApplyServices(IServiceCollection services)
103107
services.TryAddSingleton<RelationalDbContextOptionsExtensionOptions>();
104108
services.AddSingleton<ISingletonOptions>(provider => provider.GetRequiredService<RelationalDbContextOptionsExtensionOptions>());
105109

110+
services.AddSingleton<RelationalOptimizingVisitor>();
111+
106112
services.Add<IConventionSetPlugin, RelationalConventionSetPlugin>(GetLifetime<IConventionSetPlugin>());
107113

108114
services.TryAddSingleton<ITenantDatabaseProviderFactory>(DummyTenantDatabaseProviderFactory.Instance);
@@ -157,6 +163,20 @@ public void Validate(IDbContextOptions options)
157163
throw new InvalidOperationException($"TenantDatabaseSupport is enabled but there is no registration of an implementation of '{nameof(ITenantDatabaseProviderFactory)}'.");
158164
}
159165

166+
/// <summary>
167+
/// Adds <paramref name="tableMetadataProcessor"/>.
168+
/// </summary>
169+
/// <param name="tableMetadataProcessor">Table metadata processor.</param>
170+
public RelationalDbContextOptionsExtension AddTableMetadataProcessor(ITableMetadataProcessor tableMetadataProcessor)
171+
{
172+
ArgumentNullException.ThrowIfNull(tableMetadataProcessor);
173+
174+
if (!_metadataProcessors.Contains(tableMetadataProcessor))
175+
_metadataProcessors.Add(tableMetadataProcessor);
176+
177+
return this;
178+
}
179+
160180
/// <summary>
161181
/// Removes convention of type <paramref name="implementationTypeToRemove"/> from the current convention set.
162182
/// </summary>

src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtensionOptions.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.EntityFrameworkCore.Infrastructure;
22
using Thinktecture.EntityFrameworkCore.Conventions;
3+
using Thinktecture.EntityFrameworkCore.Query;
34
using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators;
45

56
namespace Thinktecture.EntityFrameworkCore.Infrastructure;
@@ -21,6 +22,8 @@ public class RelationalDbContextOptionsExtensionOptions : ISingletonOptions
2122

2223
internal IReadOnlyList<ConventionToRemove> ConventionsToRemove { get; private set; } = Array.Empty<ConventionToRemove>();
2324

25+
internal IReadOnlyList<ITableMetadataProcessor> MetadataProcessors { get; private set; } = Array.Empty<ITableMetadataProcessor>();
26+
2427
/// <inheritdoc />
2528
public void Initialize(IDbContextOptions options)
2629
{
@@ -29,6 +32,7 @@ public void Initialize(IDbContextOptions options)
2932
RowNumberSupportEnabled = extension.AddRowNumberSupport;
3033
TenantDatabaseSupportEnabled = extension.AddTenantDatabaseSupport;
3134
ConventionsToRemove = extension.ConventionsToRemove.ToList();
35+
MetadataProcessors = extension.MetadataProcessors.ToList();
3236
}
3337

3438
/// <inheritdoc />
@@ -44,16 +48,30 @@ public void Validate(IDbContextOptions options)
4448

4549
if (!Equal(extension.ConventionsToRemove, ConventionsToRemove))
4650
throw new InvalidOperationException($"The setting '{nameof(RelationalDbContextOptionsExtension.ConventionsToRemove)}' has been changed.");
51+
52+
if (!Equal(extension.MetadataProcessors, MetadataProcessors))
53+
throw new InvalidOperationException($"The setting '{nameof(RelationalDbContextOptionsExtension.MetadataProcessors)}' has been changed.");
4754
}
4855

49-
private bool Equal(IReadOnlyList<ConventionToRemove> conventionsToRemove, IReadOnlyList<ConventionToRemove> other)
56+
private bool Equal<T>(IReadOnlyList<T> collection, IReadOnlyList<T> otherCollection)
5057
{
51-
if (conventionsToRemove.Count != other.Count)
58+
if (collection.Count != otherCollection.Count)
5259
return false;
5360

5461
for (var i = 0; i < ConventionsToRemove.Count; i++)
5562
{
56-
if (!conventionsToRemove[i].Equals(other[i]))
63+
var item = collection[i];
64+
var other = otherCollection[i];
65+
66+
if (item is null)
67+
{
68+
if (other is null)
69+
continue;
70+
71+
return false;
72+
}
73+
74+
if (!item.Equals(other))
5775
return false;
5876
}
5977

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
2+
3+
namespace Thinktecture.EntityFrameworkCore.Query;
4+
5+
/// <summary>
6+
/// For internal use only.
7+
/// </summary>
8+
public interface ITableMetadataProcessor
9+
{
10+
/// <summary>
11+
/// For internal use only.
12+
/// </summary>
13+
int Order { get; }
14+
15+
/// <summary>
16+
/// For internal use only.
17+
/// </summary>
18+
TableExpressionBase ProcessMetadata(TableExpressionBase expression, TableWithMetadata tableWithMetadata);
19+
}

0 commit comments

Comments
 (0)