Skip to content

Commit d7ca47a

Browse files
committed
BREAKING: TempTables and CollectionParameter are "named entities" to prevent clashes. Changed the configuration-methods.
1 parent 39e1c6a commit d7ca47a

File tree

27 files changed

+457
-285
lines changed

27 files changed

+457
-285
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Thinktecture.EntityFrameworkCore.BulkOperations;
44
using Thinktecture.EntityFrameworkCore.Parameters;
55
using Thinktecture.EntityFrameworkCore.TempTables;
6+
using Thinktecture.Internal;
67

78
// ReSharper disable once CheckNamespace
89
namespace Thinktecture;
@@ -53,7 +54,7 @@ public static Task<ITempTableReference> CreateTempTableAsync(
5354
{
5455
ArgumentNullException.ThrowIfNull(ctx);
5556

56-
var entityType = ctx.Model.GetEntityType(type);
57+
var entityType = ctx.Model.GetEntityType(EntityNameProvider.GetTempTableName(type), type);
5758
return ctx.GetService<ITempTableCreator>().CreateTempTableAsync(entityType, options, cancellationToken);
5859
}
5960

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

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.EntityFrameworkCore.Metadata.Builders;
33
using Thinktecture.EntityFrameworkCore.Parameters;
44
using Thinktecture.EntityFrameworkCore.TempTables;
5+
using Thinktecture.Internal;
56

67
// ReSharper disable once CheckNamespace
78
namespace Thinktecture;
@@ -12,59 +13,111 @@ namespace Thinktecture;
1213
public static class BulkOperationsModelBuilderExtensions
1314
{
1415
/// <summary>
15-
/// Introduces and configures a temp table.
16+
/// Introduces and configures a scalar parameter.
1617
/// </summary>
1718
/// <param name="modelBuilder">A model builder.</param>
18-
/// <param name="isKeyless">Indication whether the entity has a key or not.</param>
19-
/// <typeparam name="T">Type of the temp table.</typeparam>
19+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
20+
/// <typeparam name="T">Type of the column.</typeparam>
2021
/// <returns>An entity type builder for further configuration.</returns>
2122
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
22-
public static EntityTypeBuilder<T> ConfigureTempTableEntity<T>(this ModelBuilder modelBuilder, bool isKeyless = true)
23+
public static void ConfigureScalarCollectionParameter<T>(
24+
this ModelBuilder modelBuilder,
25+
Action<EntityTypeBuilder<ScalarCollectionParameter<T>>>? buildAction = null)
26+
{
27+
var builder = modelBuilder.SharedTypeEntity<ScalarCollectionParameter<T>>(EntityNameProvider.GetCollectionParameterName(typeof(T), true))
28+
.ToTable(typeof(ScalarCollectionParameter<T>).ShortDisplayName(),
29+
static tableBuilder => tableBuilder.ExcludeFromMigrations())
30+
.HasNoKey();
31+
32+
buildAction?.Invoke(builder);
33+
}
34+
35+
/// <summary>
36+
/// Introduces and configures a complex parameter.
37+
/// </summary>
38+
/// <param name="modelBuilder">A model builder.</param>
39+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
40+
/// <typeparam name="T">Type of the parameter.</typeparam>
41+
/// <returns>An entity type builder for further configuration.</returns>
42+
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
43+
public static void ConfigureComplexCollectionParameter<T>(
44+
this ModelBuilder modelBuilder,
45+
Action<EntityTypeBuilder<T>>? buildAction = null)
2346
where T : class
2447
{
25-
return modelBuilder.ConfigureTempTableInternal<T>(isKeyless);
48+
var builder = modelBuilder.SharedTypeEntity<T>(EntityNameProvider.GetCollectionParameterName(typeof(T), false));
49+
50+
builder.ToTable(typeof(T).ShortDisplayName(),
51+
static tableBuilder => tableBuilder.ExcludeFromMigrations())
52+
.HasNoKey();
53+
54+
buildAction?.Invoke(builder);
2655
}
2756

2857
/// <summary>
29-
/// Introduces and configures a scalar parameter.
58+
/// Introduces and configures a keyless temp table.
3059
/// </summary>
3160
/// <param name="modelBuilder">A model builder.</param>
32-
/// <typeparam name="T">Type of the column.</typeparam>
61+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
62+
/// <typeparam name="T">Type of the temp table.</typeparam>
3363
/// <returns>An entity type builder for further configuration.</returns>
3464
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
35-
public static EntityTypeBuilder<ScalarCollectionParameter<T>> ConfigureScalarCollectionParameter<T>(this ModelBuilder modelBuilder)
65+
public static void ConfigureTempTableEntity<T>(
66+
this ModelBuilder modelBuilder,
67+
Action<EntityTypeBuilder<T>>? buildAction)
68+
where T : class
3669
{
37-
return modelBuilder.Entity<ScalarCollectionParameter<T>>()
38-
.ToTable(typeof(ScalarCollectionParameter<T>).ShortDisplayName(),
39-
tableBuilder => tableBuilder.ExcludeFromMigrations())
40-
.HasNoKey();
70+
ConfigureTempTableEntity(modelBuilder, true, buildAction);
4171
}
4272

4373
/// <summary>
44-
/// Introduces and configures a complex parameter.
74+
/// Introduces and configures a temp table.
4575
/// </summary>
4676
/// <param name="modelBuilder">A model builder.</param>
47-
/// <typeparam name="T">Type of the parameter.</typeparam>
77+
/// <param name="isKeyless">Indication whether the entity has a key or not.</param>
78+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
79+
/// <typeparam name="T">Type of the temp table.</typeparam>
4880
/// <returns>An entity type builder for further configuration.</returns>
4981
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
50-
public static EntityTypeBuilder<T> ConfigureComplexCollectionParameter<T>(this ModelBuilder modelBuilder)
82+
public static void ConfigureTempTableEntity<T>(
83+
this ModelBuilder modelBuilder,
84+
bool isKeyless = true,
85+
Action<EntityTypeBuilder<T>>? buildAction = null)
5186
where T : class
5287
{
53-
return modelBuilder.Entity<T>()
54-
.ToTable(typeof(T).ShortDisplayName(),
55-
tableBuilder => tableBuilder.ExcludeFromMigrations())
56-
.HasNoKey();
88+
var builder = modelBuilder.ConfigureTempTableInternal<T>(isKeyless);
89+
90+
buildAction?.Invoke(builder);
91+
}
92+
93+
/// <summary>
94+
/// Introduces and configures a keyless temp table.
95+
/// </summary>
96+
/// <param name="modelBuilder">A model builder.</param>
97+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
98+
/// <typeparam name="TColumn1">Type of the column.</typeparam>
99+
/// <returns>An entity type builder for further configuration.</returns>
100+
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
101+
public static void ConfigureTempTable<TColumn1>(
102+
this ModelBuilder modelBuilder,
103+
Action<EntityTypeBuilder<TempTable<TColumn1>>>? buildAction)
104+
{
105+
ConfigureTempTable(modelBuilder, true, buildAction);
57106
}
58107

59108
/// <summary>
60109
/// Introduces and configures a temp table.
61110
/// </summary>
62111
/// <param name="modelBuilder">A model builder.</param>
63112
/// <param name="isKeyless">Indication whether the entity has a key or not.</param>
113+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
64114
/// <typeparam name="TColumn1">Type of the column.</typeparam>
65115
/// <returns>An entity type builder for further configuration.</returns>
66116
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
67-
public static EntityTypeBuilder<TempTable<TColumn1>> ConfigureTempTable<TColumn1>(this ModelBuilder modelBuilder, bool isKeyless = true)
117+
public static void ConfigureTempTable<TColumn1>(
118+
this ModelBuilder modelBuilder,
119+
bool isKeyless = true,
120+
Action<EntityTypeBuilder<TempTable<TColumn1>>>? buildAction = null)
68121
{
69122
var builder = modelBuilder.ConfigureTempTableInternal<TempTable<TColumn1>>(isKeyless);
70123

@@ -74,19 +127,39 @@ public static EntityTypeBuilder<TempTable<TColumn1>> ConfigureTempTable<TColumn1
74127
builder.Property(t => t.Column1).ValueGeneratedNever();
75128
}
76129

77-
return builder;
130+
buildAction?.Invoke(builder);
131+
}
132+
133+
/// <summary>
134+
/// Introduces and configures a keyless temp table.
135+
/// </summary>
136+
/// <param name="modelBuilder">A model builder.</param>
137+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
138+
/// <typeparam name="TColumn1">Type of the column 1.</typeparam>
139+
/// <typeparam name="TColumn2">Type of the column 2.</typeparam>
140+
/// <returns>An entity type builder for further configuration.</returns>
141+
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
142+
public static void ConfigureTempTable<TColumn1, TColumn2>(
143+
this ModelBuilder modelBuilder,
144+
Action<EntityTypeBuilder<TempTable<TColumn1, TColumn2>>>? buildAction)
145+
{
146+
ConfigureTempTable(modelBuilder, true, buildAction);
78147
}
79148

80149
/// <summary>
81150
/// Introduces and configures a temp table.
82151
/// </summary>
83152
/// <param name="modelBuilder">A model builder.</param>
84153
/// <param name="isKeyless">Indication whether the entity has a key or not.</param>
154+
/// <param name="buildAction">An action that performs configuration of the entity type.</param>
85155
/// <typeparam name="TColumn1">Type of the column 1.</typeparam>
86156
/// <typeparam name="TColumn2">Type of the column 2.</typeparam>
87157
/// <returns>An entity type builder for further configuration.</returns>
88158
/// <exception cref="ArgumentNullException"><paramref name="modelBuilder"/> is <c>null</c>.</exception>
89-
public static EntityTypeBuilder<TempTable<TColumn1, TColumn2>> ConfigureTempTable<TColumn1, TColumn2>(this ModelBuilder modelBuilder, bool isKeyless = true)
159+
public static void ConfigureTempTable<TColumn1, TColumn2>(
160+
this ModelBuilder modelBuilder,
161+
bool isKeyless = true,
162+
Action<EntityTypeBuilder<TempTable<TColumn1, TColumn2>>>? buildAction = null)
90163
{
91164
ArgumentNullException.ThrowIfNull(modelBuilder);
92165

@@ -99,16 +172,17 @@ public static EntityTypeBuilder<TempTable<TColumn1, TColumn2>> ConfigureTempTabl
99172
builder.Property(t => t.Column2).ValueGeneratedNever();
100173
}
101174

102-
return builder;
175+
buildAction?.Invoke(builder);
103176
}
104177

105178
private static EntityTypeBuilder<T> ConfigureTempTableInternal<T>(this ModelBuilder modelBuilder, bool isKeyless)
106179
where T : class
107180
{
108181
ArgumentNullException.ThrowIfNull(modelBuilder);
109182

110-
var builder = modelBuilder.Entity<T>().ToTable($"#{typeof(T).ShortDisplayName()}",
111-
tableBuilder => tableBuilder.ExcludeFromMigrations());
183+
var builder = modelBuilder.SharedTypeEntity<T>(EntityNameProvider.GetTempTableName(typeof(T)))
184+
.ToTable($"#{typeof(T).ShortDisplayName()}",
185+
tableBuilder => tableBuilder.ExcludeFromMigrations());
112186

113187
if (isKeyless)
114188
builder.HasNoKey();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
3+
namespace Thinktecture.Internal;
4+
5+
/// <summary>
6+
/// This is an internal API.
7+
/// </summary>
8+
public static class EntityNameProvider
9+
{
10+
/// <summary>
11+
/// This is an internal API.
12+
/// </summary>
13+
public static string GetCollectionParameterName(Type type, bool isScalar)
14+
{
15+
return $"Thinktecture:{(isScalar ? "Scalar" : "Complex")}CollectionParameter:{type.Namespace}.{type.ShortDisplayName()}";
16+
}
17+
18+
/// <summary>
19+
/// This is an internal API.
20+
/// </summary>
21+
public static string GetTempTableName(Type type)
22+
{
23+
return $"Thinktecture:TempTable:{type.Namespace}.{type.ShortDisplayName()}";
24+
}
25+
}

src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalModelExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,36 @@ public static IEntityType GetEntityType(this IModel model, Type type)
3333

3434
return entityType;
3535
}
36+
37+
/// <summary>
38+
/// Fetches meta data for entity of provided <paramref name="name"/>.
39+
/// </summary>
40+
/// <param name="model">Model of a database context.</param>
41+
/// <param name="name">Entity name.</param>
42+
/// <param name="fallbackEntityType">Type of the entity to search for. This type is used only then if the search with the <paramref name="name"/> wasn't successful.</param>
43+
/// <returns>An instance of type <see cref="IEntityType"/>.</returns>
44+
/// <exception cref="ArgumentNullException">
45+
/// <paramref name="model"/> is <c>null</c>
46+
/// - or
47+
/// <paramref name="name"/> is <c>null</c>.
48+
/// </exception>
49+
/// <exception cref="ArgumentException">The provided type <paramref name="name"/> is not known by provided <paramref name="model"/>.</exception>
50+
public static IEntityType GetEntityType(this IModel model, string name, Type? fallbackEntityType = null)
51+
{
52+
ArgumentNullException.ThrowIfNull(model);
53+
ArgumentNullException.ThrowIfNull(name);
54+
55+
var entityType = model.FindEntityType(name);
56+
57+
if (entityType is null && fallbackEntityType is not null)
58+
entityType = model.FindEntityType(fallbackEntityType);
59+
60+
if (entityType is not null)
61+
return entityType;
62+
63+
if (fallbackEntityType is not null)
64+
throw new ArgumentException($"The provided name '{name}' and the type '{fallbackEntityType.ShortDisplayName()}' were not part of the provided Entity Framework model.", nameof(name));
65+
66+
throw new ArgumentException($"The provided name '{name}' is not part of the provided Entity Framework model.", nameof(name));
67+
}
3668
}

src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutor.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ private async Task<ITempTableQuery<T>> BulkInsertIntoTempTableAsync<T, TEntity>(
292292
CancellationToken cancellationToken)
293293
where TEntity : class
294294
{
295-
var entityType = _ctx.Model.GetEntityType(typeof(TEntity));
295+
var type = typeof(TEntity);
296+
var entityTypeName = EntityNameProvider.GetTempTableName(type);
297+
var entityType = _ctx.Model.GetEntityType(entityTypeName, type);
296298
var selectedProperties = options.PropertiesToInsert.DeterminePropertiesForTempTable(entityType, null);
297299

298300
if (selectedProperties.Any(p => !p.IsInlined))
@@ -321,7 +323,11 @@ private async Task<ITempTableQuery<T>> BulkInsertIntoTempTableAsync<T, TEntity>(
321323
await tempTableCreator.CreatePrimaryKeyAsync(_ctx, keyProperties, tempTableReference.Name, tempTableCreationOptions.TruncateTableIfExists, cancellationToken).ConfigureAwait(false);
322324
}
323325

324-
var query = _ctx.Set<TEntity>().FromTempTable(tempTableReference.Name);
326+
var dbSet = entityType.Name == entityTypeName
327+
? _ctx.Set<TEntity>(entityTypeName)
328+
: _ctx.Set<TEntity>();
329+
330+
var query = dbSet.FromTempTable(tempTableReference.Name);
325331

326332
var pk = entityType.FindPrimaryKey();
327333

src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Parameters/SqlServerCollectionParameterConvention.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.EntityFrameworkCore.Infrastructure;
22
using Microsoft.EntityFrameworkCore.Metadata.Builders;
33
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
4+
using Thinktecture.Internal;
45

56
namespace Thinktecture.EntityFrameworkCore.Parameters;
67

@@ -27,7 +28,9 @@ public void ProcessModelInitialized(IConventionModelBuilder modelBuilder, IConve
2728

2829
private static void AddScalarCollectionParameter<TColumn1>(IConventionModelBuilder modelBuilder)
2930
{
30-
var builder = modelBuilder.Entity(typeof(ScalarCollectionParameter<TColumn1>), fromDataAnnotation: true);
31+
var builder = modelBuilder.SharedTypeEntity(EntityNameProvider.GetCollectionParameterName(typeof(TColumn1), true),
32+
typeof(ScalarCollectionParameter<TColumn1>),
33+
fromDataAnnotation: true);
3134

3235
if (builder is null)
3336
return;

src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Parameters/SqlServerCollectionParameterFactory.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.EntityFrameworkCore.Storage;
1010
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
1111
using Microsoft.Extensions.ObjectPool;
12+
using Thinktecture.Internal;
1213

1314
namespace Thinktecture.EntityFrameworkCore.Parameters;
1415

@@ -41,14 +42,15 @@ public SqlServerCollectionParameterFactory(
4142
/// <inheritdoc />
4243
public IQueryable<T> CreateScalarQuery<T>(DbContext ctx, IReadOnlyCollection<T> values, bool applyDistinct)
4344
{
44-
var entityType = ctx.Model.GetEntityType(typeof(ScalarCollectionParameter<T>));
45+
var entityName = EntityNameProvider.GetCollectionParameterName(typeof(T), true);
46+
var entityType = ctx.Model.GetEntityType(entityName);
4547
var parameterInfo = _cache.GetOrAdd(entityType,
4648
static (type, args) => GetScalarParameterInfo<T>(args._stringBuilderPool, args._sqlGenerationHelper, type),
4749
(_stringBuilderPool, _sqlGenerationHelper));
4850

4951
var parameterValue = parameterInfo.ParameterFactory(values, _jsonSerializerOptions);
5052

51-
return ctx.Set<ScalarCollectionParameter<T>>()
53+
return ctx.Set<ScalarCollectionParameter<T>>(entityName)
5254
.FromSqlRaw(applyDistinct ? parameterInfo.StatementWithDistinct : parameterInfo.Statement,
5355
CreateTopParameter(parameterValue),
5456
CreateJsonParameter(parameterValue))
@@ -59,14 +61,16 @@ public IQueryable<T> CreateScalarQuery<T>(DbContext ctx, IReadOnlyCollection<T>
5961
public IQueryable<T> CreateComplexQuery<T>(DbContext ctx, IReadOnlyCollection<T> objects, bool applyDistinct)
6062
where T : class
6163
{
62-
var entityType = ctx.Model.GetEntityType(typeof(T));
64+
var entityName = EntityNameProvider.GetCollectionParameterName(typeof(T), false);
65+
var entityType = ctx.Model.GetEntityType(entityName);
6366
var parameterInfo = _cache.GetOrAdd(entityType, GetComplexParameterInfo<T>);
6467

6568
var parameterValue = parameterInfo.ParameterFactory(objects, _jsonSerializerOptions);
6669

67-
return ctx.Set<T>().FromSqlRaw(applyDistinct ? parameterInfo.StatementWithDistinct : parameterInfo.Statement,
68-
CreateTopParameter(parameterValue),
69-
CreateJsonParameter(parameterValue));
70+
return ctx.Set<T>(entityName)
71+
.FromSqlRaw(applyDistinct ? parameterInfo.StatementWithDistinct : parameterInfo.Statement,
72+
CreateTopParameter(parameterValue),
73+
CreateJsonParameter(parameterValue));
7074
}
7175

7276
private static SqlParameter CreateJsonParameter(JsonCollectionParameter parameterValue)

src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutor.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@ private async Task<ITempTableQuery<T>> BulkInsertIntoTempTableAsync<T, TEntity>(
357357
CancellationToken cancellationToken)
358358
where TEntity : class
359359
{
360-
var entityType = _ctx.Model.GetEntityType(typeof(TEntity));
360+
var type = typeof(TEntity);
361+
var entityTypeName = EntityNameProvider.GetTempTableName(type);
362+
var entityType = _ctx.Model.GetEntityType(entityTypeName, type);
361363
var selectedProperties = options.PropertiesToInsert.DeterminePropertiesForTempTable(entityType, null);
362364

363365
if (selectedProperties.Any(p => !p.IsInlined))
@@ -372,7 +374,11 @@ private async Task<ITempTableQuery<T>> BulkInsertIntoTempTableAsync<T, TEntity>(
372374
var bulkInsertOptions = options.GetBulkInsertOptions();
373375
await BulkInsertAsync(entityType, entitiesOrValues, null, tempTableReference.Name, bulkInsertOptions, bulkOperationContextFactory, cancellationToken).ConfigureAwait(false);
374376

375-
var query = _ctx.Set<TEntity>().FromTempTable(tempTableReference.Name);
377+
var dbSet = entityType.Name == entityTypeName
378+
? _ctx.Set<TEntity>(entityTypeName)
379+
: _ctx.Set<TEntity>();
380+
381+
var query = dbSet.FromTempTable(tempTableReference.Name);
376382

377383
var pk = entityType.FindPrimaryKey();
378384

0 commit comments

Comments
 (0)