From 21fe00420f5614c46569a93685845c8eb8f5fbbd Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 13:48:10 +0200 Subject: [PATCH 01/14] Temp --- .editorconfig | 3 + .../MySqlBulkInsertProvider.cs | 12 +-- .../PostgreSqlBulkInsertProvider.cs | 6 +- .../SqlServerBulkInsertProvider.cs | 5 +- .../SqliteBulkInsertProvider.cs | 5 +- .../Abstractions/IBulkInsertProvider.cs | 6 +- .../BulkInsertOptionsExtension.cs | 7 +- .../BulkInsertProviderBase.cs | 48 ++++-------- .../BulkInsertProviderUntyped.cs | 76 +++++++++++++++++++ .../Dialect/SqlDialectBuilder.cs | 2 +- .../Extensions/PublicExtensions.DbContext.cs | 38 +++++----- .../Extensions/PublicExtensions.DbSet.cs | 38 +++++----- .../Extensions/PublicExtensions.cs | 2 +- .../Metadata/MetadataProviderExtension.cs | 4 +- .../TestDbContext.cs | 9 +-- .../Tests/Basic/BasicTestsBase.cs | 9 +-- .../Tests/Basic/BasicTestsMySql.cs | 5 +- .../Tests/Basic/BasicTestsPostgreSql.cs | 5 +- .../Tests/Basic/BasicTestsSqlServer.cs | 5 +- .../Tests/Basic/BasicTestsSqlite.cs | 5 +- 20 files changed, 153 insertions(+), 137 deletions(-) create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderUntyped.cs diff --git a/.editorconfig b/.editorconfig index 40dca25..8273cfc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -56,5 +56,8 @@ dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion +# IDE0090: Use 'new(...)' +dotnet_diagnostic.IDE0090.severity = none + [*.{csproj,proj,targets}] indent_size = 2 \ No newline at end of file diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs index ff32163..2a81685 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs @@ -9,12 +9,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.MySql; -internal class MySqlBulkInsertProvider : BulkInsertProviderBase +internal class MySqlBulkInsertProvider(ILogger? logger = null) : BulkInsertProviderBase(logger) { - public MySqlBulkInsertProvider(ILogger? logger = null) : base(logger) - { - } - //language=sql /// protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD {BulkInsertId} INT AUTO_INCREMENT PRIMARY KEY;"; @@ -26,13 +22,13 @@ public MySqlBulkInsertProvider(ILogger? logger = null) protected override MySqlBulkInsertOptions CreateDefaultOptions() => new(); /// - public override IAsyncEnumerable BulkInsertReturnEntities( + protected override IAsyncEnumerable BulkInsertReturnEntities( bool sync, DbContext context, TableMetadata tableInfo, IEnumerable entities, - BulkInsertOptions options, - OnConflictOptions? onConflict = null, + MySqlBulkInsertOptions options, + OnConflictOptions? onConflict = null, CancellationToken ctk = default) { throw new NotSupportedException("Provider does not support returning entities."); diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs index 4dd86a2..89f6b50 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs @@ -13,12 +13,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; [UsedImplicitly] -internal class PostgreSqlBulkInsertProvider : BulkInsertProviderBase +internal class PostgreSqlBulkInsertProvider(ILogger? logger = null) : BulkInsertProviderBase(logger) { - public PostgreSqlBulkInsertProvider(ILogger? logger = null) : base(logger) - { - } - //language=sql /// protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD COLUMN {BulkInsertId} SERIAL PRIMARY KEY;"; diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs index 1efbbb4..9bd7197 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs @@ -11,11 +11,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.SqlServer; [UsedImplicitly] -internal class SqlServerBulkInsertProvider : BulkInsertProviderBase +internal class SqlServerBulkInsertProvider(ILogger? logger = null) : BulkInsertProviderBase(logger) { - public SqlServerBulkInsertProvider(ILogger? logger = null) : base(logger) - { - } //language=sql /// diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs index 439d0f2..fdbc452 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs @@ -13,11 +13,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Sqlite; [UsedImplicitly] -internal class SqliteBulkInsertProvider : BulkInsertProviderBase +internal class SqliteBulkInsertProvider(ILogger? logger = null) : BulkInsertProviderBase(logger) { - public SqliteBulkInsertProvider(ILogger? logger = null) : base(logger) - { - } /// protected override string BulkInsertId => "rowid"; diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs index a8fa8e6..e7e8756 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs @@ -20,7 +20,7 @@ internal IAsyncEnumerable BulkInsertReturnEntities( TableMetadata tableInfo, IEnumerable entities, BulkInsertOptions options, - OnConflictOptions? onConflict = null, + OnConflictOptions? onConflict = null, CancellationToken ctk = default ) where T : class; @@ -33,7 +33,7 @@ internal Task BulkInsert( TableMetadata tableInfo, IEnumerable entities, BulkInsertOptions options, - OnConflictOptions? onConflict = null, + OnConflictOptions? onConflict = null, CancellationToken ctk = default ) where T : class; @@ -42,5 +42,5 @@ internal Task BulkInsert( /// /// Make the default options for the provider, can be a subclass of . /// - internal BulkInsertOptions InternalCreateDefaultOptions(); + internal BulkInsertOptions CreateDefaultOptions(); } diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertOptionsExtension.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertOptionsExtension.cs index ebd131b..42a7ffa 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertOptionsExtension.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertOptionsExtension.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; @@ -20,11 +20,8 @@ public void Validate(IDbContextOptions options) { } - private class BulkInsertOptionsExtensionInfo : DbContextOptionsExtensionInfo + private class BulkInsertOptionsExtensionInfo(IDbContextOptionsExtension extension) : DbContextOptionsExtensionInfo(extension) { - public BulkInsertOptionsExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) { } - /// public override int GetServiceProviderHashCode() => 0; diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs index 48fa384..4cb5bd6 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; -using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; using PhenX.EntityFrameworkCore.BulkInsert.Dialect; using PhenX.EntityFrameworkCore.BulkInsert.Extensions; using PhenX.EntityFrameworkCore.BulkInsert.Metadata; @@ -12,41 +11,25 @@ namespace PhenX.EntityFrameworkCore.BulkInsert; -internal abstract class BulkInsertProviderBase(ILogger>? logger = null) : IBulkInsertProvider +internal abstract class BulkInsertProviderBase(ILogger? logger = null) : BulkInsertProviderUntyped where TDialect : SqlDialectBuilder, new() where TOptions : BulkInsertOptions, new() { - protected readonly TDialect SqlDialect = new(); - protected virtual string BulkInsertId => "_bulk_insert_id"; protected abstract string AddTableCopyBulkInsertId { get; } protected virtual string GetTempTableName(string tableName) => $"_temp_bulk_insert_{tableName}"; - SqlDialectBuilder IBulkInsertProvider.SqlDialect => SqlDialect; - - public BulkInsertOptions InternalCreateDefaultOptions() => CreateDefaultOptions(); - - /// - /// Create the default options for the provider, can be a subclass of . - /// - protected abstract TOptions CreateDefaultOptions(); - - public virtual async IAsyncEnumerable BulkInsertReturnEntities( + protected override async IAsyncEnumerable BulkInsertReturnEntities( bool sync, DbContext context, TableMetadata tableInfo, IEnumerable entities, - BulkInsertOptions options, - OnConflictOptions? onConflict, + TOptions options, + OnConflictOptions? onConflict, [EnumeratorCancellation] CancellationToken ctk) where T : class { - if (options is not TOptions providerOptions) - { - throw new InvalidOperationException($"Invalid options type: {options.GetType().Name}. Expected: {typeof(TOptions).Name}"); - } - using var activity = Telemetry.ActivitySource.StartActivity("BulkInsertReturnEntities"); activity?.AddTag("tableName", tableInfo.TableName); activity?.AddTag("synchronous", sync); @@ -59,10 +42,10 @@ public virtual async IAsyncEnumerable BulkInsertReturnEntities( Log.UsingTempTablToReturnData(logger); } - var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, providerOptions, tempTableRequired: true, ctk: ctk); + var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, options, tempTableRequired: true, ctk: ctk); var result = - await CopyFromTempTableAsync(sync, context, tableInfo, tableName, true, providerOptions, onConflict, ctk: ctk) + await CopyFromTempTableAsync(sync, context, tableInfo, tableName, true, options, onConflict, ctk: ctk) ?? throw new InvalidOperationException("Copy returns null enumerable."); await foreach (var item in result.WithCancellation(ctk)) @@ -79,20 +62,15 @@ await CopyFromTempTableAsync(sync, context, tableInfo, tableName, true, pr } } - public virtual async Task BulkInsert( + protected override async Task BulkInsert( bool sync, DbContext context, TableMetadata tableInfo, IEnumerable entities, - BulkInsertOptions options, - OnConflictOptions? onConflict, + TOptions options, + OnConflictOptions? onConflict, CancellationToken ctk) where T : class { - if (options is not TOptions providerOptions) - { - throw new InvalidOperationException($"Invalid options type: {options.GetType().Name}. Expected: {typeof(TOptions).Name}"); - } - using var activity = Telemetry.ActivitySource.StartActivity("BulkInsert"); activity?.AddTag("tableName", tableInfo.TableName); activity?.AddTag("synchronous", sync); @@ -107,9 +85,9 @@ public virtual async Task BulkInsert( Log.UsingTempTableToResolveConflicts(logger); } - var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, providerOptions, tempTableRequired: true, ctk: ctk); + var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, options, tempTableRequired: true, ctk: ctk); - await CopyFromTempTableAsync(sync, context, tableInfo, tableName, false, providerOptions, onConflict, ctk); + await CopyFromTempTableAsync(sync, context, tableInfo, tableName, false, options, onConflict, ctk); } else { @@ -118,7 +96,7 @@ public virtual async Task BulkInsert( Log.UsingDirectInsert(logger); } - await PerformBulkInsertAsync(sync, context, tableInfo, entities, providerOptions, tempTableRequired: false, ctk: ctk); + await PerformBulkInsertAsync(sync, context, tableInfo, entities, options, tempTableRequired: false, ctk: ctk); } // Commit the transaction if we own them. @@ -207,7 +185,7 @@ protected virtual async Task AddBulkInsertIdColumn( string tempTableName, bool returnData, TOptions options, - OnConflictOptions? onConflict, + OnConflictOptions? onConflict, CancellationToken ctk) where T : class where TResult : class { var query = diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderUntyped.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderUntyped.cs new file mode 100644 index 0000000..baab9b2 --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderUntyped.cs @@ -0,0 +1,76 @@ +using Microsoft.EntityFrameworkCore; + +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; +using PhenX.EntityFrameworkCore.BulkInsert.Dialect; +using PhenX.EntityFrameworkCore.BulkInsert.Metadata; +using PhenX.EntityFrameworkCore.BulkInsert.Options; + +namespace PhenX.EntityFrameworkCore.BulkInsert; + +internal abstract class BulkInsertProviderUntyped : IBulkInsertProvider + where TDialect : SqlDialectBuilder, new() + where TOptions : BulkInsertOptions, new() +{ + protected readonly TDialect SqlDialect = new(); + + SqlDialectBuilder IBulkInsertProvider.SqlDialect => SqlDialect; + + BulkInsertOptions IBulkInsertProvider.CreateDefaultOptions() => CreateDefaultOptions(); + + /// + /// Create the default options for the provider, can be a subclass of . + /// + protected abstract TOptions CreateDefaultOptions(); + + public IAsyncEnumerable BulkInsertReturnEntities( + bool sync, + DbContext context, + TableMetadata tableInfo, + IEnumerable entities, + BulkInsertOptions options, + OnConflictOptions? onConflict, + CancellationToken ctk) where T : class + { + if (options is not TOptions providerOptions) + { + throw new InvalidOperationException($"Invalid options type: {options.GetType().Name}. Expected: {typeof(TOptions).Name}"); + } + + return BulkInsertReturnEntities(sync, context, tableInfo, entities, providerOptions, onConflict, ctk); + } + + protected abstract IAsyncEnumerable BulkInsertReturnEntities( + bool sync, + DbContext context, + TableMetadata tableInfo, + IEnumerable entities, + TOptions options, + OnConflictOptions? onConflict, + CancellationToken ctk) where T : class; + + public Task BulkInsert( + bool sync, + DbContext context, + TableMetadata tableInfo, + IEnumerable entities, + BulkInsertOptions options, + OnConflictOptions? onConflict, + CancellationToken ctk) where T : class + { + if (options is not TOptions providerOptions) + { + throw new InvalidOperationException($"Invalid options type: {options.GetType().Name}. Expected: {typeof(TOptions).Name}"); + } + + return BulkInsert(sync, context, tableInfo, entities, providerOptions, onConflict, ctk); + } + + protected abstract Task BulkInsert( + bool sync, + DbContext context, + TableMetadata tableInfo, + IEnumerable entities, + TOptions options, + OnConflictOptions? onConflict, + CancellationToken ctk) where T : class; +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Dialect/SqlDialectBuilder.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Dialect/SqlDialectBuilder.cs index 3dc961a..801d044 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Dialect/SqlDialectBuilder.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Dialect/SqlDialectBuilder.cs @@ -154,7 +154,7 @@ public string QuoteTableName(string? schema, string tableName) /// /// Gets column names for the insert statement, from an object initializer. /// - protected string[] GetColumns(TableMetadata table, Expression> columns) + protected static string[] GetColumns(TableMetadata table, Expression> columns) { return columns.Body switch { diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs index 9bc410b..d4f14e4 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using PhenX.EntityFrameworkCore.BulkInsert.Options; @@ -60,7 +60,7 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class where TConfig : BulkInsertOptions @@ -68,7 +68,7 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( var dbSet = dbContext.Set() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext."); - return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet,false, entities, configure, onConflict, ctk); + return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet,false, entities, configure, onConflict, cancellationToken); } /// @@ -79,11 +79,11 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEntitiesAsync(dbContext, entities, configure, onConflict, ctk); + return ExecuteBulkInsertReturnEntitiesAsync(dbContext, entities, configure, onConflict, cancellationToken); } /// @@ -93,11 +93,11 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( this DbContext dbContext, IEnumerable entities, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEntitiesAsync(dbContext, entities, _ => { }, onConflict, ctk); + return ExecuteBulkInsertReturnEntitiesAsync(dbContext, entities, _ => { }, onConflict, cancellationToken); } /// @@ -108,7 +108,7 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class where TConfig : BulkInsertOptions @@ -116,7 +116,7 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext."); - return dbSet.ExecuteBulkInsertReturnEnumerableAsync(entities, configure, onConflict, ctk); + return dbSet.ExecuteBulkInsertReturnEnumerableAsync(entities, configure, onConflict, cancellationToken); } /// @@ -127,10 +127,10 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEnumerableAsync(dbContext, entities, configure, onConflict, ctk); + return ExecuteBulkInsertReturnEnumerableAsync(dbContext, entities, configure, onConflict, cancellationToken); } /// @@ -140,10 +140,10 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync( this DbContext dbContext, IEnumerable entities, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEnumerableAsync(dbContext, entities, _ => { }, onConflict, ctk); + return ExecuteBulkInsertReturnEnumerableAsync(dbContext, entities, _ => { }, onConflict, cancellationToken); } /// @@ -154,7 +154,7 @@ public static async Task ExecuteBulkInsertAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class where TConfig : BulkInsertOptions @@ -162,7 +162,7 @@ public static async Task ExecuteBulkInsertAsync( var dbSet = dbContext.Set() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext."); - await dbSet.ExecuteBulkInsertAsync(entities, configure, onConflict, ctk); + await dbSet.ExecuteBulkInsertAsync(entities, configure, onConflict, cancellationToken); } /// @@ -173,11 +173,11 @@ public static async Task ExecuteBulkInsertAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - await ExecuteBulkInsertAsync(dbContext, entities, configure, onConflict, ctk); + await ExecuteBulkInsertAsync(dbContext, entities, configure, onConflict, cancellationToken); } /// @@ -187,11 +187,11 @@ public static async Task ExecuteBulkInsertAsync( this DbContext dbContext, IEnumerable entities, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - await ExecuteBulkInsertAsync(dbContext, entities, _ => { }, onConflict, ctk); + await ExecuteBulkInsertAsync(dbContext, entities, _ => { }, onConflict, cancellationToken); } /// diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs index c7cd183..b0bc7ea 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using PhenX.EntityFrameworkCore.BulkInsert.Options; @@ -57,12 +57,12 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class where TOptions : BulkInsertOptions { - return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet, false, entities, configure, onConflict, ctk); + return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet, false, entities, configure, onConflict, cancellationToken); } /// @@ -73,11 +73,11 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEntitiesAsync(dbSet, entities, configure, onConflict, ctk); + return ExecuteBulkInsertReturnEntitiesAsync(dbSet, entities, configure, onConflict, cancellationToken); } /// @@ -87,11 +87,11 @@ public static Task> ExecuteBulkInsertReturnEntitiesAsync( this DbSet dbSet, IEnumerable entities, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEntitiesAsync(dbSet, entities, _ => { }, onConflict, ctk); + return ExecuteBulkInsertReturnEntitiesAsync(dbSet, entities, _ => { }, onConflict, cancellationToken); } /// @@ -102,7 +102,7 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class where TOptions : BulkInsertOptions @@ -110,7 +110,7 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync(), entities, - options, onConflict, ctk); + options, onConflict, cancellationToken); } /// @@ -121,11 +121,11 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEnumerableAsync(dbSet, entities, configure, onConflict, ctk); + return ExecuteBulkInsertReturnEnumerableAsync(dbSet, entities, configure, onConflict, cancellationToken); } /// @@ -135,11 +135,11 @@ public static IAsyncEnumerable ExecuteBulkInsertReturnEnumerableAsync( this DbSet dbSet, IEnumerable entities, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - return ExecuteBulkInsertReturnEnumerableAsync(dbSet, entities, _ => { }, onConflict, ctk); + return ExecuteBulkInsertReturnEnumerableAsync(dbSet, entities, _ => { }, onConflict, cancellationToken); } /// @@ -150,7 +150,7 @@ public static async Task ExecuteBulkInsertAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class where TOptions : BulkInsertOptions @@ -158,7 +158,7 @@ public static async Task ExecuteBulkInsertAsync( var provider = InitProvider(dbSet, configure, out var context, out var options); await provider.BulkInsert(false, context, dbSet.GetDbContext().GetTableInfo(), entities, options, onConflict, - ctk); + cancellationToken); } /// @@ -169,11 +169,11 @@ public static async Task ExecuteBulkInsertAsync( IEnumerable entities, Action configure, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - await ExecuteBulkInsertAsync(dbSet, entities, configure, onConflict, ctk); + await ExecuteBulkInsertAsync(dbSet, entities, configure, onConflict, cancellationToken); } /// @@ -183,11 +183,11 @@ public static async Task ExecuteBulkInsertAsync( this DbSet dbSet, IEnumerable entities, OnConflictOptions? onConflict = null, - CancellationToken ctk = default + CancellationToken cancellationToken = default ) where T : class { - await ExecuteBulkInsertAsync(dbSet, entities, _ => { }, onConflict, ctk); + await ExecuteBulkInsertAsync(dbSet, entities, _ => { }, onConflict, cancellationToken); } /// diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs index 4dcc850..725b37d 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs @@ -52,7 +52,7 @@ out TOptions options context = dbSet.GetDbContext(); var provider = context.GetService(); - var defaultOptions = provider.InternalCreateDefaultOptions(); + var defaultOptions = provider.CreateDefaultOptions(); if (defaultOptions is not TOptions castedOptions) { diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProviderExtension.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProviderExtension.cs index 3fe2a20..9774f1d 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProviderExtension.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProviderExtension.cs @@ -17,10 +17,8 @@ public void Validate(IDbContextOptions options) { } - private class MetadataProviderExtensionInfo : DbContextOptionsExtensionInfo + private class MetadataProviderExtensionInfo(IDbContextOptionsExtension extension) : DbContextOptionsExtensionInfo(extension) { - public MetadataProviderExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) { } /// public override int GetServiceProviderHashCode() => 0; diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Benchmark/TestDbContext.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Benchmark/TestDbContext.cs index 8b31a5d..0e09a4b 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Benchmark/TestDbContext.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Benchmark/TestDbContext.cs @@ -2,17 +2,12 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Benchmark; -public class TestDbContext : DbContext +public class TestDbContext(Action configure) : DbContext { - public Action Configure { get; } + public Action Configure { get; } = configure; public DbSet TestEntities { get; set; } = null!; - public TestDbContext(Action configure) - { - Configure = configure; - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { Configure(optionsBuilder); diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs index d344a1c..c255771 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs @@ -10,17 +10,12 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public abstract class BasicTestsBase : IClassFixture, IAsyncLifetime +public abstract class BasicTestsBase(TestDbContainer dbContainer) : IClassFixture, IAsyncLifetime where TFixture : TestDbContainer { private readonly Guid _run = Guid.NewGuid(); private TestDbContext _context = null!; - protected BasicTestsBase(TestDbContainer dbContainer) - { - DbContainer = dbContainer; - } - public async Task InitializeAsync() { _context = await DbContainer.CreateContextAsync(); @@ -32,7 +27,7 @@ public Task DisposeAsync() return Task.CompletedTask; } - protected TestDbContainer DbContainer { get; } + protected TestDbContainer DbContainer { get; } = dbContainer; [Fact] public async Task InsertsEntitiesSuccessfully() diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs index 9181409..9cc0293 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs @@ -6,9 +6,6 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; [Trait("Category", "MySql")] -public class BasicTestsMySql : BasicTestsBase> +public class BasicTestsMySql(TestDbContainerMySql dbContainer) : BasicTestsBase>(dbContainer) { - public BasicTestsMySql(TestDbContainerMySql dbContainer) : base(dbContainer) - { - } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs index f1a9cf4..0737ea3 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs @@ -6,9 +6,6 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; [Trait("Category", "PostgreSql")] -public class BasicTestsPostgreSql : BasicTestsBase> +public class BasicTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : BasicTestsBase>(dbContainer) { - public BasicTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : base(dbContainer) - { - } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs index 0d658e4..02957c5 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs @@ -6,9 +6,6 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; [Trait("Category", "SqlServer")] -public class BasicTestsSqlServer : BasicTestsBase> +public class BasicTestsSqlServer(TestDbContainerSqlServer dbContainer) : BasicTestsBase>(dbContainer) { - public BasicTestsSqlServer(TestDbContainerSqlServer dbContainer) : base(dbContainer) - { - } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs index 0aabe0a..321951a 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs @@ -6,10 +6,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; [Trait("Category", "Sqlite")] -public class BasicTestsSqlite : BasicTestsBase> +public class BasicTestsSqlite(TestDbContainerSqlite dbContainer) : BasicTestsBase>(dbContainer) { - public BasicTestsSqlite(TestDbContainerSqlite dbContainer) : base(dbContainer) - { - } } From 162ddefcb1d9c4ba10f9c4f8ce0fc6af9285a96f Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 16:36:07 +0200 Subject: [PATCH 02/14] Geometry support. --- .../MySqlBulkInsertProvider.cs | 9 +++-- .../MySqlGeometryConverter.cs | 28 ++++++++++++++++ ...ntityFrameworkCore.BulkInsert.MySql.csproj | 1 + .../SqlServerBulkInsertProvider.cs | 4 +-- .../Abstractions/IValueConverter.cs | 15 +++++++++ .../EnumerableDataReader.cs | 25 ++++++++++++-- .../Options/BulkInsertOptions.cs | 7 ++++ .../DbContainer/TestDbContainerMySql.cs | 5 ++- .../DbContainer/TestDbContainerPostgreSql.cs | 5 ++- .../DbContainer/TestDbContainerSqlServer.cs | 5 ++- .../DbContainer/TestDbContainerSqlite.cs | 5 ++- .../DbContext/TestDbContext.cs | 5 +-- .../DbContext/TestEntityWithGeo.cs | 18 ++++++++++ ...ntityFrameworkCore.BulkInsert.Tests.csproj | 4 +++ .../Tests/Basic/BasicTestsBase.cs | 33 +++++++++++++++++-- 15 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestEntityWithGeo.cs diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs index 2b2ef20..602759e 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs @@ -22,7 +22,10 @@ internal class MySqlBulkInsertProvider(ILogger logger) protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}"; /// - protected override MySqlBulkInsertOptions CreateDefaultOptions() => new(); + protected override MySqlBulkInsertOptions CreateDefaultOptions() => new() + { + Converters = [MySqlGeometryConverter.Instance] + }; /// protected override IAsyncEnumerable BulkInsertReturnEntities( @@ -71,11 +74,11 @@ CancellationToken ctk if (sync) { // ReSharper disable once MethodHasAsyncOverloadWithCancellation - bulkCopy.WriteToServer(new EnumerableDataReader(entities, properties)); + bulkCopy.WriteToServer(new EnumerableDataReader(entities, properties, options.Converters)); } else { - await bulkCopy.WriteToServerAsync(new EnumerableDataReader(entities, properties), ctk); + await bulkCopy.WriteToServerAsync(new EnumerableDataReader(entities, properties, options.Converters), ctk); } } } diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs new file mode 100644 index 0000000..fbfa323 --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs @@ -0,0 +1,28 @@ +using MySqlConnector; + +using NetTopologySuite.Geometries; + +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; + +namespace PhenX.EntityFrameworkCore.BulkInsert.MySql; + +internal sealed class MySqlGeometryConverter : IValueConverter +{ + public static readonly MySqlGeometryConverter Instance = new(); + + private MySqlGeometryConverter() + { + } + + public bool TryConvertValue(object source, out object result) + { + if (source is Geometry geometry) + { + result = MySqlGeometry.FromWkb(geometry.SRID, geometry.ToBinary()); + return true; + } + + result = source; + return false; + } +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/PhenX.EntityFrameworkCore.BulkInsert.MySql.csproj b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/PhenX.EntityFrameworkCore.BulkInsert.MySql.csproj index baca930..2c6d8e7 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/PhenX.EntityFrameworkCore.BulkInsert.MySql.csproj +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/PhenX.EntityFrameworkCore.BulkInsert.MySql.csproj @@ -9,6 +9,7 @@ + diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs index 9fa08d1..c409d55 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs @@ -53,11 +53,11 @@ protected override async Task BulkInsert( if (sync) { // ReSharper disable once MethodHasAsyncOverloadWithCancellation - bulkCopy.WriteToServer(new EnumerableDataReader(entities, columns)); + bulkCopy.WriteToServer(new EnumerableDataReader(entities, columns, options.Converters)); } else { - await bulkCopy.WriteToServerAsync(new EnumerableDataReader(entities, columns), ctk); + await bulkCopy.WriteToServerAsync(new EnumerableDataReader(entities, columns, options.Converters), ctk); } } } diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs new file mode 100644 index 0000000..b4cd426 --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs @@ -0,0 +1,15 @@ +namespace PhenX.EntityFrameworkCore.BulkInsert.Abstractions; + +/// +/// Provide an interface to control how objects are written. +/// +public interface IValueConverter +{ + /// + /// Converts a value before written to the output. + /// + /// The source object. + /// The result type. + /// Indicates if an object should be written. + bool TryConvertValue(object source, out object result); +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs index d57b43c..57d2ee8 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs @@ -1,10 +1,11 @@ using System.Data; +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; using PhenX.EntityFrameworkCore.BulkInsert.Metadata; namespace PhenX.EntityFrameworkCore.BulkInsert; -internal sealed class EnumerableDataReader(IEnumerable rows, IReadOnlyList columns) : IDataReader +internal sealed class EnumerableDataReader(IEnumerable rows, IReadOnlyList columns, List? converters) : IDataReader { private readonly IEnumerator _enumerator = rows.GetEnumerator(); private readonly Dictionary _ordinalMap = @@ -23,7 +24,7 @@ public object GetValue(int i) return DBNull.Value; } - return columns[i].GetValue(current)!; + return GetAndConvertValue(columns[i], current); } public int GetValues(object[] values) @@ -36,12 +37,30 @@ public int GetValues(object[] values) for (var i = 0; i < columns.Count; i++) { - values[i] = columns[i].GetValue(current)!; + values[i] = GetAndConvertValue(columns[i], current); } return columns.Count; } + private object GetAndConvertValue(ColumnMetadata column, T entity) + { + var result = column.GetValue(entity!)!; + if (converters != null) + { + foreach (var converter in converters) + { + if (converter.TryConvertValue(result, out var temp)) + { + result = temp; + break; + } + } + } + + return result; + } + public bool Read() => _enumerator.MoveNext(); public Type GetFieldType(int i) => columns[i].ClrType; diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs index 19f4835..9c1f597 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs @@ -1,3 +1,5 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; + namespace PhenX.EntityFrameworkCore.BulkInsert.Options; /// @@ -43,6 +45,11 @@ public class BulkInsertOptions /// public TimeSpan CopyTimeout { get; set; } = TimeSpan.FromMinutes(10); + /// + /// The value converters. + /// + public List? Converters { get; set; } + internal int GetCopyTimeoutInSeconds() { return Math.Max(0, (int)CopyTimeout.TotalSeconds); diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs index 8111c1d..ed0bb76 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs @@ -28,7 +28,10 @@ protected override string GetConnectionString() protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseMySql(ServerVersion.AutoDetect(GetConnectionString())) + .UseMySql(ServerVersion.AutoDetect(GetConnectionString()), o => + { + o.UseNetTopologySuite(); + }) .UseBulkInsertMySql(); } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs index 22f2ab7..9597463 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs @@ -25,7 +25,10 @@ public class TestDbContainerPostgreSql : TestDbContainer protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseNpgsql() + .UseNpgsql(o => + { + o.UseNetTopologySuite(); + }) .UseBulkInsertPostgreSql(); } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs index cddcf06..415e555 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs @@ -22,7 +22,10 @@ public class TestDbContainerSqlServer : TestDbContainer protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseSqlServer() + .UseSqlServer(o => + { + o.UseNetTopologySuite(); + }) .UseBulkInsertSqlServer(); } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index b6e12bf..85924e2 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -21,7 +21,10 @@ protected override string GetConnectionString() protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseSqlite() + .UseSqlite(o => + { + o.UseNetTopologySuite(); + }) .UseBulkInsertSqlite(); } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs index 9c67ab8..600a9b5 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs @@ -6,9 +6,10 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; public class TestDbContext : TestDbContextBase { public DbSet TestEntities { get; set; } = null!; + public DbSet TestEntitiesWithGeo { get; set; } = null!; public DbSet TestEntitiesWithJson { get; set; } = null!; - public DbSet TestEntitiesWithGuidIds { get; set; } = null!; - public DbSet TestEntitiesWithConverters { get; set; } = null!; + public DbSet TestEntitiesWithGuidId { get; set; } = null!; + public DbSet TestEntitiesWithConverter { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestEntityWithGeo.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestEntityWithGeo.cs new file mode 100644 index 0000000..667645f --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestEntityWithGeo.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using NetTopologySuite.Geometries; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +[Table("test_entity_geo")] +public class TestEntityWithGeo +{ + [Key] + public int Id { get; set; } + + public Geometry GeoObject { get; set; } = null!; + + [Column("test_run")] + public Guid TestRun { get; set; } +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj index 009db83..3ef8eb2 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj @@ -13,7 +13,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs index bb01dd1..204c887 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs @@ -1,3 +1,5 @@ +using NetTopologySuite.Geometries; + using PhenX.EntityFrameworkCore.BulkInsert.Enums; using PhenX.EntityFrameworkCore.BulkInsert.Extensions; using PhenX.EntityFrameworkCore.BulkInsert.MySql; @@ -8,6 +10,8 @@ using Xunit; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; public abstract class BasicTestsBase(TestDbContainer dbContainer) : IClassFixture, IAsyncLifetime @@ -91,7 +95,7 @@ public async Task InsertsEntities_AndReturn() Assert.Contains(insertedEntities, e => e.Name == $"{_run}_Entity2"); } - [SkippableFact] + [Fact] public async Task InsertsEntities_WithJson() { // Arrange @@ -111,6 +115,29 @@ public async Task InsertsEntities_WithJson() Assert.Contains(insertedEntities, e => e.Json[0] == 2); } + [Fact] + public async Task InsertsEntities_WithGeo() + { + // Arrange + var geo1 = new Point(1, 2) { SRID = 4326 }; + var geo2 = new Point(3, 4) { SRID = 4326 }; + + var entities = new List + { + new TestEntityWithGeo { TestRun = _run, GeoObject = geo1 }, + new TestEntityWithGeo { TestRun = _run, GeoObject = geo2 } + }; + + // Act + await _context.ExecuteBulkInsertAsync(entities); + + // Assert + var insertedEntities = _context.TestEntitiesWithGeo.Where(x => x.TestRun == _run).ToList(); + Assert.Equal(2, insertedEntities.Count); + Assert.Contains(insertedEntities, e => e.GeoObject == geo1); + Assert.Contains(insertedEntities, e => e.GeoObject == geo2); + } + [SkippableFact] public async Task InsertsEntities_AndReturn_AsyncEnumerable() { @@ -217,7 +244,7 @@ await _context.ExecuteBulkInsertAsync(entities, }); // Assert - var insertedEntities = _context.TestEntitiesWithGuidIds.Where(x => x.TestRun == _run).ToList(); + var insertedEntities = _context.TestEntitiesWithGuidId.Where(x => x.TestRun == _run).ToList(); Assert.Equal(2, insertedEntities.Count); Assert.Contains(insertedEntities, e => e.Name == $"Updated_{_run}_Entity1"); Assert.Contains(insertedEntities, e => e.Name == $"Updated_{_run}_Entity2"); @@ -470,7 +497,7 @@ public async Task InsertEntities_AndReturn_WithEntityWithValueConverters() // Act await _context.ExecuteBulkInsertAsync(entities); - var inserted = _context.TestEntitiesWithConverters.Where(x => x.TestRun == _run).ToList(); + var inserted = _context.TestEntitiesWithConverter.Where(x => x.TestRun == _run).ToList(); // Assert Assert.Equal(2, inserted.Count); From c19e456b0d2affb3ea0cb0b80005f1d2ecf78607 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:25:02 +0200 Subject: [PATCH 03/14] Fix providers --- .../MySqlGeometryConverter.cs | 2 +- .../IPostgresTypeProvider.cs | 19 ++++ ...FrameworkCore.BulkInsert.PostgreSql.csproj | 1 + .../PostgreSqlBulkInsertOptions.cs | 14 +++ .../PostgreSqlBulkInsertProvider.cs | 27 +++-- .../PostgreSqlGeometryConverter.cs | 42 +++++++ ...yFrameworkCore.BulkInsert.SqlServer.csproj | 2 + .../SqlServerBulkInsertProvider.cs | 1 + .../SqlServerGeometryConverter.cs | 105 ++++++++++++++++++ .../SqliteBulkInsertProvider.cs | 2 +- ...lueConverter.cs => IBulkValueConverter.cs} | 2 +- .../EnumerableDataReader.cs | 24 +--- .../Metadata/ColumnMetadata.cs | 19 +++- .../Options/BulkInsertOptions.cs | 2 +- .../DbContainer/TestDbContainer.cs | 2 - .../DbContainer/TestDbContainerMySql.cs | 2 +- .../DbContainer/TestDbContainerPostgreSql.cs | 3 +- .../DbContainer/TestDbContainerSqlServer.cs | 3 +- .../DbContainer/TestDbContainerSqlite.cs | 2 +- .../Tests/Basic/BasicTestsBase.cs | 6 +- 20 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/IPostgresTypeProvider.cs create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertOptions.cs create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlGeometryConverter.cs create mode 100644 src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerGeometryConverter.cs rename src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/{IValueConverter.cs => IBulkValueConverter.cs} (92%) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs index fbfa323..8ff526b 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlGeometryConverter.cs @@ -6,7 +6,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.MySql; -internal sealed class MySqlGeometryConverter : IValueConverter +internal sealed class MySqlGeometryConverter : IBulkValueConverter { public static readonly MySqlGeometryConverter Instance = new(); diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/IPostgresTypeProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/IPostgresTypeProvider.cs new file mode 100644 index 0000000..83e4fd4 --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/IPostgresTypeProvider.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore.Metadata; + +using NpgsqlTypes; + +namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; + +/// +/// Provides the type to write. +/// +public interface IPostgresTypeProvider +{ + /// + /// Gets the type of a value before written to the output. + /// + /// The source property. + /// The result type. + /// Indicates if an object should be written. + bool TryGetType(IProperty property, out NpgsqlDbType result); +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql.csproj b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql.csproj index 7c40821..4c56c76 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql.csproj +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql.csproj @@ -5,6 +5,7 @@ + diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertOptions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertOptions.cs new file mode 100644 index 0000000..6a21b3e --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertOptions.cs @@ -0,0 +1,14 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Options; + +namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; + +/// +/// Options specific to SQL Server bulk insert. +/// +public class PostgreSqlBulkInsertOptions : BulkInsertOptions +{ + /// + /// A list of type providers. + /// + public List? TypeProviders { get; set; } +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs index e571558..c8d7f6d 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs @@ -11,12 +11,11 @@ using NpgsqlTypes; using PhenX.EntityFrameworkCore.BulkInsert.Metadata; -using PhenX.EntityFrameworkCore.BulkInsert.Options; namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; [UsedImplicitly] -internal class PostgreSqlBulkInsertProvider(ILogger? logger) : BulkInsertProviderBase(logger) +internal class PostgreSqlBulkInsertProvider(ILogger? logger) : BulkInsertProviderBase(logger) { //language=sql /// @@ -32,9 +31,11 @@ private static string GetBinaryImportCommand(IReadOnlyList prope } /// - protected override BulkInsertOptions CreateDefaultOptions() => new() + protected override PostgreSqlBulkInsertOptions CreateDefaultOptions() => new() { BatchSize = 50_000, + Converters = [PostgreSqlGeometryConverter.Instance], + TypeProviders = [PostgreSqlGeometryConverter.Instance], }; /// @@ -45,7 +46,7 @@ protected override async Task BulkInsert( IEnumerable entities, string tableName, IReadOnlyList columns, - BulkInsertOptions options, + PostgreSqlBulkInsertOptions options, CancellationToken ctk) { var connection = (NpgsqlConnection)context.Database.GetDbConnection(); @@ -57,7 +58,7 @@ protected override async Task BulkInsert( : await connection.BeginBinaryImportAsync(command, ctk); // The type mapping can be null for obvious types like string. - var columnTypes = columns.Select(GetPostgreSqlType).ToArray(); + var columnTypes = columns.Select(c => GetPostgreSqlType(c, options)).ToArray(); foreach (var entity in entities) { @@ -74,7 +75,7 @@ protected override async Task BulkInsert( var columnIndex = 0; foreach (var column in columns) { - var value = column.GetValue(entity); + var value = column.GetValue(entity, options.Converters); // Get the actual type, so that the writer can do the conversation to the target type automatically. var type = columnTypes[columnIndex]; @@ -122,8 +123,20 @@ protected override async Task BulkInsert( } } - private static NpgsqlDbType? GetPostgreSqlType(ColumnMetadata column) + private static NpgsqlDbType? GetPostgreSqlType(ColumnMetadata column, PostgreSqlBulkInsertOptions options) { + var typeProviders = options.TypeProviders; + if (typeProviders is { Count: > 0 }) + { + foreach (var typeProvider in typeProviders) + { + if (typeProvider.TryGetType(column.Property, out var type)) + { + return type; + } + } + } + var mapping = column.Property.GetRelationalTypeMapping() as NpgsqlTypeMapping; return mapping?.NpgsqlDbType; diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlGeometryConverter.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlGeometryConverter.cs new file mode 100644 index 0000000..8ff1d51 --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlGeometryConverter.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Metadata; + +using NetTopologySuite.Geometries; + +using NpgsqlTypes; + +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; + +namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; + +internal sealed class PostgreSqlGeometryConverter : IBulkValueConverter, IPostgresTypeProvider +{ + public static readonly PostgreSqlGeometryConverter Instance = new(); + + private PostgreSqlGeometryConverter() + { + } + + public bool TryConvertValue(object source, out object result) + { + if (source is Geometry geometry) + { + result = geometry.ToBinary(); + return true; + } + + result = source; + return false; + } + + public bool TryGetType(IProperty property, out NpgsqlDbType result) + { + if (property.ClrType.IsAssignableTo(typeof(Geometry))) + { + result = NpgsqlDbType.Bytea; + return true; + } + + result = default; + return false; + } +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/PhenX.EntityFrameworkCore.BulkInsert.SqlServer.csproj b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/PhenX.EntityFrameworkCore.BulkInsert.SqlServer.csproj index 19117ce..835f8e5 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/PhenX.EntityFrameworkCore.BulkInsert.SqlServer.csproj +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/PhenX.EntityFrameworkCore.BulkInsert.SqlServer.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs index c409d55..dd9bd71 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs @@ -22,6 +22,7 @@ internal class SqlServerBulkInsertProvider(ILogger? protected override SqlServerBulkInsertOptions CreateDefaultOptions() => new() { BatchSize = 50_000, + Converters = [SqlServerGeometryConverter.Instance] }; /// diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerGeometryConverter.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerGeometryConverter.cs new file mode 100644 index 0000000..da64860 --- /dev/null +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerGeometryConverter.cs @@ -0,0 +1,105 @@ +using System.Data.SqlTypes; + +using Microsoft.SqlServer.Types; + +using NetTopologySuite.Geometries; + +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; + +namespace PhenX.EntityFrameworkCore.BulkInsert.SqlServer; + +internal sealed class SqlServerGeometryConverter : IBulkValueConverter +{ + public static readonly SqlServerGeometryConverter Instance = new(); + + private SqlServerGeometryConverter() + { + } + + public bool TryConvertValue(object source, out object result) + { + if (source is Geometry geometry) + { + var reversed = Reverse(geometry); + result = SqlGeometry.STGeomFromWKB(new SqlBytes(reversed.AsBinary()), geometry.SRID); + return true; + } + + result = source; + return false; + } + + private static Geometry Reverse(Geometry input) + { + switch (input) + { + case Point point: + return Reverse(point); + + case LineString lineString: + return Reverse(lineString); + + case Polygon polygon: + return Reverse(polygon); + + case MultiPoint multiPoint: + return Reverse(multiPoint); + + case MultiLineString multiLineString: + return Reverse(multiLineString); + + case MultiPolygon mpoly: + return Reverse(mpoly); + + case GeometryCollection gc: + return Reverse(gc); + + default: + throw new NotSupportedException($"Unsupported geometry type: {input.GeometryType}"); + } + } + + private static Point Reverse(Point input) + { + return input.Factory.CreatePoint(Swap(input.Coordinate)); + } + + private static LineString Reverse(LineString input) + { + return input.Factory.CreateLineString(Swap(input.Coordinates)); + } + + private static MultiPoint Reverse(MultiPoint input) + { + return input.Factory.CreateMultiPoint(input.Geometries.OfType().Select(Reverse).ToArray()); + } + + private static MultiLineString Reverse(MultiLineString input) + { + return input.Factory.CreateMultiLineString(input.Geometries.OfType().Select(Reverse).ToArray()); + } + + private static MultiPolygon Reverse(MultiPolygon input) + { + return input.Factory.CreateMultiPolygon(input.Geometries.OfType().Select(Reverse).ToArray()); + } + + private static GeometryCollection Reverse(GeometryCollection input) + { + return input.Factory.CreateGeometryCollection(input.Geometries.Select(Reverse).ToArray()); + } + + private static Polygon Reverse(Polygon input) + { + var factory = input.Factory; + + return input.Factory.CreatePolygon( + factory.CreateLinearRing(Swap(input.Shell.Coordinates)), + input.Holes.Select(h => factory.CreateLinearRing(Swap(h.Coordinates))).ToArray()); + } + + private static Coordinate Swap(Coordinate c) => new Coordinate(c.Y, c.X); + + private static Coordinate[] Swap(Coordinate[] coords) => coords.Select(Swap).ToArray(); + +} diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs index 0e064f4..d8cc12e 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs @@ -190,7 +190,7 @@ private static void FillValues(T[] chunk, DbParameterCollection parameters, I { foreach (var column in columns) { - var value = column.GetValue(entity); + var value = column.GetValue(entity, null); parameters[p].Value = value; p++; } diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkValueConverter.cs similarity index 92% rename from src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs rename to src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkValueConverter.cs index b4cd426..3197b64 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IValueConverter.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkValueConverter.cs @@ -3,7 +3,7 @@ /// /// Provide an interface to control how objects are written. /// -public interface IValueConverter +public interface IBulkValueConverter { /// /// Converts a value before written to the output. diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs index 57d2ee8..620c445 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs @@ -5,7 +5,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert; -internal sealed class EnumerableDataReader(IEnumerable rows, IReadOnlyList columns, List? converters) : IDataReader +internal sealed class EnumerableDataReader(IEnumerable rows, IReadOnlyList columns, List? converters) : IDataReader { private readonly IEnumerator _enumerator = rows.GetEnumerator(); private readonly Dictionary _ordinalMap = @@ -24,7 +24,7 @@ public object GetValue(int i) return DBNull.Value; } - return GetAndConvertValue(columns[i], current); + return columns[i].GetValue(current, converters)!; } public int GetValues(object[] values) @@ -37,30 +37,12 @@ public int GetValues(object[] values) for (var i = 0; i < columns.Count; i++) { - values[i] = GetAndConvertValue(columns[i], current); + values[i] = columns[i].GetValue(current, converters)!; } return columns.Count; } - private object GetAndConvertValue(ColumnMetadata column, T entity) - { - var result = column.GetValue(entity!)!; - if (converters != null) - { - foreach (var converter in converters) - { - if (converter.TryConvertValue(result, out var temp)) - { - result = temp; - break; - } - } - } - - return result; - } - public bool Read() => _enumerator.MoveNext(); public Type GetFieldType(int i) => columns[i].ClrType; diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ColumnMetadata.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ColumnMetadata.cs index 6e06524..5ccd173 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ColumnMetadata.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ColumnMetadata.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; +using PhenX.EntityFrameworkCore.BulkInsert.Abstractions; using PhenX.EntityFrameworkCore.BulkInsert.Dialect; namespace PhenX.EntityFrameworkCore.BulkInsert.Metadata; @@ -25,9 +26,23 @@ internal sealed class ColumnMetadata(IProperty property, SqlDialectBuilder dial public bool IsGenerated { get; } = property.ValueGenerated == ValueGenerated.OnAdd; - public object? GetValue(object entity) + public object? GetValue(object entity, List? converters) { - return _getter(entity!); + var result = _getter(entity!); + + if (converters != null && result != null) + { + foreach (var converter in converters) + { + if (converter.TryConvertValue(result, out var temp)) + { + result = temp; + break; + } + } + } + + return result; } private static PropertyAccessor.Getter BuildGetter(IProperty property) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs index 9c1f597..d58f12f 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs @@ -48,7 +48,7 @@ public class BulkInsertOptions /// /// The value converters. /// - public List? Converters { get; set; } + public List? Converters { get; set; } internal int GetCopyTimeoutInSeconds() { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs index 3a50d54..6699e21 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs @@ -48,8 +48,6 @@ public async Task CreateContextAsync() } }; - dbContext.Database.SetConnectionString(GetConnectionString()); - await EnsureConnectedAsync(dbContext); try { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs index ed0bb76..d0243c1 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs @@ -28,7 +28,7 @@ protected override string GetConnectionString() protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseMySql(ServerVersion.AutoDetect(GetConnectionString()), o => + .UseMySql(GetConnectionString(), ServerVersion.AutoDetect(GetConnectionString()), o => { o.UseNetTopologySuite(); }) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs index 9597463..89650e2 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs @@ -15,6 +15,7 @@ public class TestDbContainerPostgreSql : TestDbContainer protected override IDatabaseContainer? GetDbContainer() { return new PostgreSqlBuilder() + .WithImage("postgis/postgis") // Geo GeoSpatial support. .WithReuse(true) .WithDatabase("testdb") .WithUsername("testuser") @@ -25,7 +26,7 @@ public class TestDbContainerPostgreSql : TestDbContainer protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseNpgsql(o => + .UseNpgsql(GetConnectionString(), o => { o.UseNetTopologySuite(); }) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs index 415e555..0e3a914 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs @@ -15,6 +15,7 @@ public class TestDbContainerSqlServer : TestDbContainer protected override IDatabaseContainer? GetDbContainer() { return new MsSqlBuilder() + .WithImage("vibs2006/sql_server_fts") // Geo Geospatial support .WithReuse(true) .Build(); } @@ -22,7 +23,7 @@ public class TestDbContainerSqlServer : TestDbContainer protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseSqlServer(o => + .UseSqlServer(GetConnectionString(), o => { o.UseNetTopologySuite(); }) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index 85924e2..dd4dd71 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -21,7 +21,7 @@ protected override string GetConnectionString() protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseSqlite(o => + .UseSqlite(GetConnectionString(), o => { o.UseNetTopologySuite(); }) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs index 204c887..3d50580 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs @@ -10,8 +10,6 @@ using Xunit; -using static Microsoft.EntityFrameworkCore.DbLoggerCategory; - namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; public abstract class BasicTestsBase(TestDbContainer dbContainer) : IClassFixture, IAsyncLifetime @@ -115,9 +113,11 @@ public async Task InsertsEntities_WithJson() Assert.Contains(insertedEntities, e => e.Json[0] == 2); } - [Fact] + [SkippableFact] public async Task InsertsEntities_WithGeo() { + Skip.If(_context.IsProvider(ProviderType.Sqlite)); + // Arrange var geo1 = new Point(1, 2) { SRID = 4326 }; var geo2 = new Point(3, 4) { SRID = 4326 }; From 22b44a01ecb2bf6b8e5e250d7d72b8b09024bb18 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:26:42 +0200 Subject: [PATCH 04/14] Remove nettopoloy for sqlite for now. --- .../DbContainer/TestDbContainerSqlite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index dd4dd71..eb7ffe2 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -23,7 +23,7 @@ protected override void Configure(DbContextOptionsBuilder optionsBuilder) optionsBuilder .UseSqlite(GetConnectionString(), o => { - o.UseNetTopologySuite(); + // o.UseNetTopologySuite(); }) .UseBulkInsertSqlite(); } From 720923f3943a002cfaff628d94171e166fd0bed5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:31:05 +0200 Subject: [PATCH 05/14] Try to install sqlite spatial. --- .github/workflows/dotnet-test.yml | 10 ++++++++++ .github/workflows/release.yml | 5 +++++ .../DbContainer/TestDbContainerSqlite.cs | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-test.yml b/.github/workflows/dotnet-test.yml index c50945b..210dbb2 100644 --- a/.github/workflows/dotnet-test.yml +++ b/.github/workflows/dotnet-test.yml @@ -16,17 +16,27 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + - name: Setup .NET 9.0 uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x + + - name: Install SQLite and SpatiaLite + run: | + sudo apt-get update + sudo apt-get install -y sqlite3 libsqlite3-mod-spatialite + - name: Restore dependencies run: dotnet restore + - name: Build run: dotnet build --no-restore + - name: Test run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e42a992..d866160 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,11 @@ jobs: with: dotnet-version: 9.0.x + - name: Install SQLite and SpatiaLite + run: | + sudo apt-get update + sudo apt-get install -y sqlite3 libsqlite3-mod-spatialite + - name: Restore dependencies run: dotnet restore diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index eb7ffe2..dd4dd71 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -23,7 +23,7 @@ protected override void Configure(DbContextOptionsBuilder optionsBuilder) optionsBuilder .UseSqlite(GetConnectionString(), o => { - // o.UseNetTopologySuite(); + o.UseNetTopologySuite(); }) .UseBulkInsertSqlite(); } From 8121c7c98f2c35f6ea788875c40ee9b10b140138 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:41:21 +0200 Subject: [PATCH 06/14] Seperate tests for geo stuff. --- .../DbContainer/TestDbContainerSqlite.cs | 5 +- .../DbContext/TestDbContext.cs | 1 - .../DbContext/TestDbContextGeo.cs | 8 +++ ...ntityFrameworkCore.BulkInsert.Tests.csproj | 1 + .../Tests/Basic/BasicTestsBase.cs | 25 -------- .../Tests/Geo/GeoTestsBase.cs | 57 +++++++++++++++++++ .../Tests/Geo/GeoTestsMySql.cs | 11 ++++ .../Tests/Geo/GeoTestsPostgreSql.cs | 11 ++++ .../Tests/Geo/GeoTestsSqlServer.cs | 11 ++++ 9 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContextGeo.cs create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs create mode 100644 tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index dd4dd71..381faea 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -21,10 +21,7 @@ protected override string GetConnectionString() protected override void Configure(DbContextOptionsBuilder optionsBuilder) { optionsBuilder - .UseSqlite(GetConnectionString(), o => - { - o.UseNetTopologySuite(); - }) + .UseSqlite(GetConnectionString()) .UseBulkInsertSqlite(); } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs index 600a9b5..a15504a 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContext.cs @@ -6,7 +6,6 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; public class TestDbContext : TestDbContextBase { public DbSet TestEntities { get; set; } = null!; - public DbSet TestEntitiesWithGeo { get; set; } = null!; public DbSet TestEntitiesWithJson { get; set; } = null!; public DbSet TestEntitiesWithGuidId { get; set; } = null!; public DbSet TestEntitiesWithConverter { get; set; } = null!; diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContextGeo.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContextGeo.cs new file mode 100644 index 0000000..6e2f810 --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContext/TestDbContextGeo.cs @@ -0,0 +1,8 @@ +using Microsoft.EntityFrameworkCore; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +public class TestDbContextGeo : TestDbContextBase +{ + public DbSet TestEntitiesWithGeo { get; set; } = null!; +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj index 3ef8eb2..edcc83a 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj @@ -16,6 +16,7 @@ + diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs index 3d50580..07455a7 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs @@ -113,31 +113,6 @@ public async Task InsertsEntities_WithJson() Assert.Contains(insertedEntities, e => e.Json[0] == 2); } - [SkippableFact] - public async Task InsertsEntities_WithGeo() - { - Skip.If(_context.IsProvider(ProviderType.Sqlite)); - - // Arrange - var geo1 = new Point(1, 2) { SRID = 4326 }; - var geo2 = new Point(3, 4) { SRID = 4326 }; - - var entities = new List - { - new TestEntityWithGeo { TestRun = _run, GeoObject = geo1 }, - new TestEntityWithGeo { TestRun = _run, GeoObject = geo2 } - }; - - // Act - await _context.ExecuteBulkInsertAsync(entities); - - // Assert - var insertedEntities = _context.TestEntitiesWithGeo.Where(x => x.TestRun == _run).ToList(); - Assert.Equal(2, insertedEntities.Count); - Assert.Contains(insertedEntities, e => e.GeoObject == geo1); - Assert.Contains(insertedEntities, e => e.GeoObject == geo2); - } - [SkippableFact] public async Task InsertsEntities_AndReturn_AsyncEnumerable() { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs new file mode 100644 index 0000000..6d5a604 --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs @@ -0,0 +1,57 @@ +using NetTopologySuite.Geometries; + +using PhenX.EntityFrameworkCore.BulkInsert.Enums; +using PhenX.EntityFrameworkCore.BulkInsert.Extensions; +using PhenX.EntityFrameworkCore.BulkInsert.MySql; +using PhenX.EntityFrameworkCore.BulkInsert.Options; +using PhenX.EntityFrameworkCore.BulkInsert.SqlServer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; + +public abstract class GeoTestsBase(TestDbContainer dbContainer) : IClassFixture, IAsyncLifetime + where TDbContext : TestDbContextGeo, new() + where TFixture : TestDbContainer +{ + private readonly Guid _run = Guid.NewGuid(); + private TDbContext _context = null!; + + public async Task InitializeAsync() + { + _context = await DbContainer.CreateContextAsync(); + } + + public Task DisposeAsync() + { + _context.Dispose(); + return Task.CompletedTask; + } + + protected TestDbContainer DbContainer { get; } = dbContainer; + + [Fact] + public async Task InsertsEntities_WithGeo() + { + // Arrange + var geo1 = new Point(1, 2) { SRID = 4326 }; + var geo2 = new Point(3, 4) { SRID = 4326 }; + + var entities = new List + { + new TestEntityWithGeo { TestRun = _run, GeoObject = geo1 }, + new TestEntityWithGeo { TestRun = _run, GeoObject = geo2 } + }; + + // Act + await _context.ExecuteBulkInsertAsync(entities); + + // Assert + var insertedEntities = _context.TestEntitiesWithGeo.Where(x => x.TestRun == _run).ToList(); + Assert.Equal(2, insertedEntities.Count); + Assert.Contains(insertedEntities, e => e.GeoObject == geo1); + Assert.Contains(insertedEntities, e => e.GeoObject == geo2); + } +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs new file mode 100644 index 0000000..ff6a1ed --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs @@ -0,0 +1,11 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; + +[Trait("Category", "MySql")] +public class GeoTestsMySql(TestDbContainerMySql dbContainer) : GeoTestsBase, TestDbContextGeo>(dbContainer) +{ +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs new file mode 100644 index 0000000..d09c954 --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs @@ -0,0 +1,11 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; + +[Trait("Category", "PostgreSql")] +public class GeoTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : GeoTestsBase, TestDbContextGeo>(dbContainer) +{ +} diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs new file mode 100644 index 0000000..1e67505 --- /dev/null +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs @@ -0,0 +1,11 @@ +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; +using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; + +namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; + +[Trait("Category", "SqlServer")] +public class GeoTestsSqlServer(TestDbContainerSqlServer dbContainer) : GeoTestsBase, TestDbContextGeo>(dbContainer) +{ +} From d24443ceb85ec0fb40e6dc10d20ebe392438eb25 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:42:19 +0200 Subject: [PATCH 07/14] Remove spatial. --- .github/workflows/dotnet-test.yml | 7 +------ .github/workflows/release.yml | 5 ----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/dotnet-test.yml b/.github/workflows/dotnet-test.yml index 210dbb2..862a6a9 100644 --- a/.github/workflows/dotnet-test.yml +++ b/.github/workflows/dotnet-test.yml @@ -27,16 +27,11 @@ jobs: with: dotnet-version: 9.0.x - - name: Install SQLite and SpatiaLite - run: | - sudo apt-get update - sudo apt-get install -y sqlite3 libsqlite3-mod-spatialite - - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore - + - name: Test run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d866160..e42a992 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,11 +35,6 @@ jobs: with: dotnet-version: 9.0.x - - name: Install SQLite and SpatiaLite - run: | - sudo apt-get update - sudo apt-get install -y sqlite3 libsqlite3-mod-spatialite - - name: Restore dependencies run: dotnet restore From 8d9228eb57097f4725b3ad4ad781188d64131c04 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:43:57 +0200 Subject: [PATCH 08/14] Another test --- .../PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj index edcc83a..3ef8eb2 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj @@ -16,7 +16,6 @@ - From a50c0b7051e1a51d5112b9cec50e6c5f59204b48 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:54:47 +0200 Subject: [PATCH 09/14] Another test --- .../DbContainer/TestDbContainerMySql.cs | 3 ++- .../DbContainer/TestDbContainerPostgreSql.cs | 3 ++- .../DbContainer/TestDbContainerSqlServer.cs | 3 ++- .../DbContainer/TestDbContainerSqlite.cs | 2 +- .../Tests/Basic/BasicTestsMySql.cs | 9 ++++++++- .../Tests/Basic/BasicTestsPostgreSql.cs | 9 ++++++++- .../Tests/Basic/BasicTestsSqlServer.cs | 9 ++++++++- .../Tests/Basic/BasicTestsSqlite.cs | 6 +++++- .../Tests/Geo/GeoTestsBase.cs | 4 ---- .../Tests/Geo/GeoTestsMySql.cs | 9 ++++++++- .../Tests/Geo/GeoTestsPostgreSql.cs | 9 ++++++++- .../Tests/Geo/GeoTestsSqlServer.cs | 9 ++++++++- 12 files changed, 60 insertions(+), 15 deletions(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs index d0243c1..91fa377 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs @@ -9,7 +9,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public class TestDbContainerMySql : TestDbContainer +public abstract class TestDbContainerMySql(string reuseId) : TestDbContainer where TDbContext : TestDbContextBase, new() { protected override IDatabaseContainer? GetDbContainer() @@ -17,6 +17,7 @@ public class TestDbContainerMySql : TestDbContainer return new MySqlBuilder() .WithCommand("--log-bin-trust-function-creators=1", "--local-infile=1", "--innodb-print-all-deadlocks=ON") .WithReuse(true) + .WithLabel("reuse-id", reuseId) .Build(); } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs index 89650e2..088991e 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs @@ -9,7 +9,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public class TestDbContainerPostgreSql : TestDbContainer +public abstract class TestDbContainerPostgreSql(string reuseId) : TestDbContainer where TDbContext : TestDbContextBase, new() { protected override IDatabaseContainer? GetDbContainer() @@ -20,6 +20,7 @@ public class TestDbContainerPostgreSql : TestDbContainer .WithDatabase("testdb") .WithUsername("testuser") .WithPassword("testpassword") + .WithLabel("reuse-id", reuseId) .Build(); } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs index 0e3a914..c09a008 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs @@ -9,7 +9,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public class TestDbContainerSqlServer : TestDbContainer +public abstract class TestDbContainerSqlServer(string reuseId) : TestDbContainer where TDbContext : TestDbContextBase, new() { protected override IDatabaseContainer? GetDbContainer() @@ -17,6 +17,7 @@ public class TestDbContainerSqlServer : TestDbContainer return new MsSqlBuilder() .WithImage("vibs2006/sql_server_fts") // Geo Geospatial support .WithReuse(true) + .WithLabel("reuse-id", reuseId) .Build(); } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index 381faea..7fb103a 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -7,7 +7,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public class TestDbContainerSqlite : TestDbContainer +public abstract class TestDbContainerSqlite : TestDbContainer where TDbContext : TestDbContextBase, new() { protected override IDatabaseContainer? GetDbContainer() => null; diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs index 34716be..6f49898 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs @@ -5,7 +5,14 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; +public class BasicTestsMySqlFixture : TestDbContainerSqlServer +{ + public BasicTestsMySqlFixture() : base("basic-mysql") + { + } +} + [Trait("Category", "MySql")] -public class BasicTestsMySql(TestDbContainerMySql dbContainer) : BasicTestsBase, TestDbContextMySql>(dbContainer) +public class BasicTestsMySql(BasicTestsMySqlFixture dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs index b0ce8d3..b049577 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs @@ -5,7 +5,14 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; +public class BasicTestsPostgreSqlFixture : TestDbContainerSqlServer +{ + public BasicTestsPostgreSqlFixture() : base("basic-postgresql") + { + } +} + [Trait("Category", "PostgreSql")] -public class BasicTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : BasicTestsBase, TestDbContextPostgreSql>(dbContainer) +public class BasicTestsPostgreSql(BasicTestsPostgreSqlFixture dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs index f07ac40..51ff794 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs @@ -5,7 +5,14 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; +public class BasicTestsSqlServerFixture : TestDbContainerSqlServer +{ + public BasicTestsSqlServerFixture() : base("basic-sql-server") + { + } +} + [Trait("Category", "SqlServer")] -public class BasicTestsSqlServer(TestDbContainerSqlServer dbContainer) : BasicTestsBase, TestDbContextSqlServer>(dbContainer) +public class BasicTestsSqlServer(BasicTestsSqlServerFixture dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs index 2c728fd..2a7d030 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs @@ -5,8 +5,12 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; +public class BasicTestsSqliteFixture : TestDbContainerSqlite +{ +} + [Trait("Category", "Sqlite")] -public class BasicTestsSqlite(TestDbContainerSqlite dbContainer) : BasicTestsBase, TestDbContextSqlite>(dbContainer) +public class BasicTestsSqlite(BasicTestsSqliteFixture dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs index 6d5a604..89298f4 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs @@ -1,10 +1,6 @@ using NetTopologySuite.Geometries; -using PhenX.EntityFrameworkCore.BulkInsert.Enums; using PhenX.EntityFrameworkCore.BulkInsert.Extensions; -using PhenX.EntityFrameworkCore.BulkInsert.MySql; -using PhenX.EntityFrameworkCore.BulkInsert.Options; -using PhenX.EntityFrameworkCore.BulkInsert.SqlServer; using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs index ff6a1ed..5bc5417 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs @@ -5,7 +5,14 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; +public class GeoTestsMySqlFixture : TestDbContainerSqlServer +{ + public GeoTestsMySqlFixture() : base("geo-mysql") + { + } +} + [Trait("Category", "MySql")] -public class GeoTestsMySql(TestDbContainerMySql dbContainer) : GeoTestsBase, TestDbContextGeo>(dbContainer) +public class GeoTestsMySql(GeoTestsMySqlFixture dbContainer) : GeoTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs index d09c954..fd477cb 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs @@ -5,7 +5,14 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; +public class GeoTestsPostgreSqlFixture : TestDbContainerSqlServer +{ + public GeoTestsPostgreSqlFixture() : base("geo-postgresql") + { + } +} + [Trait("Category", "PostgreSql")] -public class GeoTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : GeoTestsBase, TestDbContextGeo>(dbContainer) +public class GeoTestsPostgreSql(GeoTestsPostgreSqlFixture dbContainer) : GeoTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs index 1e67505..a1116c7 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs @@ -5,7 +5,14 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; +public class GeoTestsSqlServerFixture : TestDbContainerSqlServer +{ + public GeoTestsSqlServerFixture() : base("geo-sqlserver") + { + } +} + [Trait("Category", "SqlServer")] -public class GeoTestsSqlServer(TestDbContainerSqlServer dbContainer) : GeoTestsBase, TestDbContextGeo>(dbContainer) +public class GeoTestsSqlServer(GeoTestsSqlServerFixture dbContainer) : GeoTestsBase(dbContainer) { } From e819906e74478ddd3b3d8841714f705cf909a879 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 17:58:58 +0200 Subject: [PATCH 10/14] Fix fixtures. --- .../Tests/Basic/BasicTestsMySql.cs | 2 +- .../Tests/Basic/BasicTestsPostgreSql.cs | 2 +- .../Tests/Basic/BasicTestsSqlServer.cs | 2 +- .../Tests/Geo/GeoTestsMySql.cs | 2 +- .../Tests/Geo/GeoTestsPostgreSql.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs index 6f49898..f3d8a5e 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs @@ -5,7 +5,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public class BasicTestsMySqlFixture : TestDbContainerSqlServer +public class BasicTestsMySqlFixture : TestDbContainerMySql { public BasicTestsMySqlFixture() : base("basic-mysql") { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs index b049577..fcca2e6 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs @@ -5,7 +5,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public class BasicTestsPostgreSqlFixture : TestDbContainerSqlServer +public class BasicTestsPostgreSqlFixture : TestDbContainerPostgreSql { public BasicTestsPostgreSqlFixture() : base("basic-postgresql") { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs index 51ff794..75ddf50 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs @@ -7,7 +7,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; public class BasicTestsSqlServerFixture : TestDbContainerSqlServer { - public BasicTestsSqlServerFixture() : base("basic-sql-server") + public BasicTestsSqlServerFixture() : base("basic-sqlserver") { } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs index 5bc5417..2ed1a1d 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs @@ -5,7 +5,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; -public class GeoTestsMySqlFixture : TestDbContainerSqlServer +public class GeoTestsMySqlFixture : TestDbContainerMySql { public GeoTestsMySqlFixture() : base("geo-mysql") { diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs index fd477cb..e576e79 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs @@ -5,7 +5,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; -public class GeoTestsPostgreSqlFixture : TestDbContainerSqlServer +public class GeoTestsPostgreSqlFixture : TestDbContainerPostgreSql { public GeoTestsPostgreSqlFixture() : base("geo-postgresql") { From 04f21316f2908eebd3b809b558e35a5baa5787d5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 18:18:11 +0200 Subject: [PATCH 11/14] Fix provider. --- .../Metadata/MetadataProvider.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProvider.cs index cfc94be..c33ad2e 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/MetadataProvider.cs @@ -7,7 +7,7 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Metadata; internal sealed class MetadataProvider { - private Dictionary? _tables; + private Dictionary> _tablesPerContext = new(); public TableMetadata GetTableInfo(DbContext context) { @@ -23,26 +23,25 @@ public TableMetadata GetTableInfo(DbContext context) private Dictionary GetTables(DbContext context) { - if (_tables != null) + lock (_tablesPerContext) { - return _tables; - } - - lock (this) - { - if (_tables != null) + var type = context.GetType(); + if (_tablesPerContext.TryGetValue(context.GetType(), out var tables)) { - return _tables; + return tables; } var provider = context.GetService(); - _tables = + tables = context.Model.GetEntityTypes() .ToDictionary( x => x.ClrType, x => new TableMetadata(x, provider.SqlDialect)); - return _tables; + + _tablesPerContext[type] = tables; + + return tables; } } } From 00c1cefd4ad5b09ef0a0f09ad4e4b026db194bda Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 20:29:51 +0200 Subject: [PATCH 12/14] Easier fixture. --- .../DbContainer/TestDbContainer.cs | 42 ++++++++++++++----- .../DbContainer/TestDbContainerMySql.cs | 33 +++++++++++---- .../DbContainer/TestDbContainerPostgreSql.cs | 25 ++++++++--- .../DbContainer/TestDbContainerSqlServer.cs | 25 ++++++++--- .../DbContainer/TestDbContainerSqlite.cs | 21 ++++++---- .../Tests/Basic/BasicTestsBase.cs | 39 ++++++++--------- .../Tests/Basic/BasicTestsMySql.cs | 10 +---- .../Tests/Basic/BasicTestsPostgreSql.cs | 10 +---- .../Tests/Basic/BasicTestsSqlServer.cs | 10 +---- .../Tests/Basic/BasicTestsSqlite.cs | 8 +--- .../Tests/Geo/GeoTestsBase.cs | 9 ++-- .../Tests/Geo/GeoTestsMySql.cs | 10 +---- .../Tests/Geo/GeoTestsPostgreSql.cs | 12 ++---- .../Tests/Geo/GeoTestsSqlServer.cs | 12 ++---- 14 files changed, 143 insertions(+), 123 deletions(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs index 6699e21..ac63439 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainer.cs @@ -1,3 +1,5 @@ +using System.Data.Common; + using DotNet.Testcontainers.Containers; using Microsoft.EntityFrameworkCore; @@ -9,10 +11,10 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public abstract class TestDbContainer : IAsyncLifetime - where TDbContext : TestDbContextBase, new() +public abstract class TestDbContainer : IAsyncLifetime { - private static readonly TimeSpan WaitTime = TimeSpan.FromSeconds(30); + private readonly TimeSpan _waitTime = TimeSpan.FromSeconds(30); + private readonly HashSet _connected = []; protected readonly IDatabaseContainer? DbContainer; protected TestDbContainer() @@ -22,12 +24,23 @@ protected TestDbContainer() protected abstract IDatabaseContainer? GetDbContainer(); - protected virtual string GetConnectionString() + protected virtual string GetConnectionString(string databaseName) { - return DbContainer?.GetConnectionString() ?? string.Empty; + if (DbContainer == null) + { + return string.Empty; + } + + var builder = new DbConnectionStringBuilder() + { + ConnectionString = DbContainer.GetConnectionString() + }; + + builder["database"] = databaseName; + return builder.ToString(); } - protected abstract void Configure(DbContextOptionsBuilder optionsBuilder); + protected abstract void Configure(DbContextOptionsBuilder optionsBuilder, string databaseName); public async Task InitializeAsync() { @@ -37,18 +50,23 @@ public async Task InitializeAsync() } } - public async Task CreateContextAsync() + public async Task CreateContextAsync(string databaseName) + where TDbContext : TestDbContextBase, new() { var dbContext = new TDbContext { ConfigureOptions = (builder) => { builder.UseLoggerFactory(NullLoggerFactory.Instance); - Configure(builder); + Configure(builder, databaseName); } }; - await EnsureConnectedAsync(dbContext); + if (_connected.Add(databaseName)) + { + await EnsureConnectedAsync(dbContext, databaseName); + } + try { await dbContext.Database.EnsureCreatedAsync(); @@ -61,9 +79,11 @@ public async Task CreateContextAsync() return dbContext; } - protected virtual async Task EnsureConnectedAsync(TDbContext context) + protected virtual async Task EnsureConnectedAsync(TDbContext context, string databaseName) + where TDbContext : TestDbContextBase { - using var cts = new CancellationTokenSource(WaitTime); + using var cts = new CancellationTokenSource(_waitTime); + while (!await context.Database.CanConnectAsync(cts.Token)) { await Task.Delay(100, cts.Token); diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs index 91fa377..904c616 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerMySql.cs @@ -3,36 +3,53 @@ using Microsoft.EntityFrameworkCore; using PhenX.EntityFrameworkCore.BulkInsert.MySql; -using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; using Testcontainers.MySql; +using Xunit; + namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public abstract class TestDbContainerMySql(string reuseId) : TestDbContainer - where TDbContext : TestDbContextBase, new() +[CollectionDefinition(Name)] +public class TestDbContainerMySqlCollection : ICollectionFixture +{ + public const string Name = "MySql"; +} + +public class TestDbContainerMySql() : TestDbContainer { protected override IDatabaseContainer? GetDbContainer() { return new MySqlBuilder() .WithCommand("--log-bin-trust-function-creators=1", "--local-infile=1", "--innodb-print-all-deadlocks=ON") .WithReuse(true) - .WithLabel("reuse-id", reuseId) + .WithUsername("root") + .WithPassword("root") .Build(); } - protected override string GetConnectionString() + protected override string GetConnectionString(string databaseName) { - return $"{base.GetConnectionString()};AllowLoadLocalInfile=true;"; + return $"{base.GetConnectionString(databaseName)};AllowLoadLocalInfile=true;"; } - protected override void Configure(DbContextOptionsBuilder optionsBuilder) + protected override void Configure(DbContextOptionsBuilder optionsBuilder, string databaseName) { + var connectionString = GetConnectionString(databaseName); + optionsBuilder - .UseMySql(GetConnectionString(), ServerVersion.AutoDetect(GetConnectionString()), o => + .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), o => { o.UseNetTopologySuite(); }) .UseBulkInsertMySql(); } + + protected override async Task EnsureConnectedAsync(TDbContext context, string databaseName) + { + var container = (MySqlContainer)DbContainer!; + + await container.ExecScriptAsync($"CREATE DATABASE `{databaseName}`"); + await base.EnsureConnectedAsync(context, databaseName); + } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs index 088991e..b03a7cc 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerPostgreSql.cs @@ -3,14 +3,20 @@ using Microsoft.EntityFrameworkCore; using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; -using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; using Testcontainers.PostgreSql; +using Xunit; + namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public abstract class TestDbContainerPostgreSql(string reuseId) : TestDbContainer - where TDbContext : TestDbContextBase, new() +[CollectionDefinition(Name)] +public class TestDbContainerPostgreSqlCollection : ICollectionFixture +{ + public const string Name = "PostgreSql"; +} + +public class TestDbContainerPostgreSql : TestDbContainer { protected override IDatabaseContainer? GetDbContainer() { @@ -20,17 +26,24 @@ public abstract class TestDbContainerPostgreSql(string reuseId) : Te .WithDatabase("testdb") .WithUsername("testuser") .WithPassword("testpassword") - .WithLabel("reuse-id", reuseId) .Build(); } - protected override void Configure(DbContextOptionsBuilder optionsBuilder) + protected override void Configure(DbContextOptionsBuilder optionsBuilder, string databaseName) { optionsBuilder - .UseNpgsql(GetConnectionString(), o => + .UseNpgsql(GetConnectionString(databaseName), o => { o.UseNetTopologySuite(); }) .UseBulkInsertPostgreSql(); } + + protected override async Task EnsureConnectedAsync(TDbContext context, string databaseName) + { + var container = (PostgreSqlContainer)DbContainer!; + + await container.ExecScriptAsync($"CREATE DATABASE \"{databaseName}\""); + await base.EnsureConnectedAsync(context, databaseName); + } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs index c09a008..a11f7ac 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlServer.cs @@ -3,31 +3,44 @@ using Microsoft.EntityFrameworkCore; using PhenX.EntityFrameworkCore.BulkInsert.SqlServer; -using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; using Testcontainers.MsSql; +using Xunit; + namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public abstract class TestDbContainerSqlServer(string reuseId) : TestDbContainer - where TDbContext : TestDbContextBase, new() +[CollectionDefinition(Name)] +public class TestDbContainerSqlServerCollection : ICollectionFixture +{ + public const string Name = "SqlServer"; +} + +public class TestDbContainerSqlServer : TestDbContainer { protected override IDatabaseContainer? GetDbContainer() { return new MsSqlBuilder() .WithImage("vibs2006/sql_server_fts") // Geo Geospatial support .WithReuse(true) - .WithLabel("reuse-id", reuseId) .Build(); } - protected override void Configure(DbContextOptionsBuilder optionsBuilder) + protected override void Configure(DbContextOptionsBuilder optionsBuilder, string databaseName) { optionsBuilder - .UseSqlServer(GetConnectionString(), o => + .UseSqlServer(GetConnectionString(databaseName), o => { o.UseNetTopologySuite(); }) .UseBulkInsertSqlServer(); } + + protected override async Task EnsureConnectedAsync(TDbContext context, string databaseName) + { + var container = (MsSqlContainer)DbContainer!; + + await container.ExecScriptAsync($"CREATE DATABASE [{databaseName}]"); + await base.EnsureConnectedAsync(context, databaseName); + } } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs index 7fb103a..c0245c5 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/DbContainer/TestDbContainerSqlite.cs @@ -3,29 +3,34 @@ using Microsoft.EntityFrameworkCore; using PhenX.EntityFrameworkCore.BulkInsert.Sqlite; -using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext; + +using Xunit; namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer; -public abstract class TestDbContainerSqlite : TestDbContainer - where TDbContext : TestDbContextBase, new() +[CollectionDefinition(Name)] +public class TestDbContainerSqliteCollection : ICollectionFixture +{ + public const string Name = "Sqlite"; +} + +public class TestDbContainerSqlite : TestDbContainer { protected override IDatabaseContainer? GetDbContainer() => null; - protected override string GetConnectionString() + protected override string GetConnectionString(string databaseName) { - // return "Data Source=:memory:;Mode=Memory;Cache=Shared"; return $"Data Source={Guid.NewGuid()}.db"; } - protected override void Configure(DbContextOptionsBuilder optionsBuilder) + protected override void Configure(DbContextOptionsBuilder optionsBuilder, string databaseName) { optionsBuilder - .UseSqlite(GetConnectionString()) + .UseSqlite(GetConnectionString(databaseName)) .UseBulkInsertSqlite(); } - protected override Task EnsureConnectedAsync(TDbContext context) + protected override Task EnsureConnectedAsync(TDbContext context, string databaseName) { return Task.CompletedTask; } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs index 07455a7..dba9848 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs @@ -1,5 +1,3 @@ -using NetTopologySuite.Geometries; - using PhenX.EntityFrameworkCore.BulkInsert.Enums; using PhenX.EntityFrameworkCore.BulkInsert.Extensions; using PhenX.EntityFrameworkCore.BulkInsert.MySql; @@ -12,16 +10,15 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public abstract class BasicTestsBase(TestDbContainer dbContainer) : IClassFixture, IAsyncLifetime +public abstract class BasicTestsBase(TestDbContainer dbContainer) : IAsyncLifetime where TDbContext : TestDbContext, new() - where TFixture : TestDbContainer { private readonly Guid _run = Guid.NewGuid(); private TDbContext _context = null!; public async Task InitializeAsync() { - _context = await DbContainer.CreateContextAsync(); + _context = await dbContainer.CreateContextAsync("basic"); } public Task DisposeAsync() @@ -30,8 +27,6 @@ public Task DisposeAsync() return Task.CompletedTask; } - protected TestDbContainer DbContainer { get; } = dbContainer; - [Fact] public async Task InsertsEntities() { @@ -53,7 +48,7 @@ public async Task InsertsEntities() } [Fact] - public void InsertsEntities_Sync() + public void InsertEntities_Sync() { // Arrange var entities = new List @@ -73,7 +68,7 @@ public void InsertsEntities_Sync() } [SkippableFact] - public async Task InsertsEntities_AndReturn() + public async Task InsertEntities_AndReturn() { Skip.If(_context.IsProvider(ProviderType.MySql)); @@ -94,7 +89,7 @@ public async Task InsertsEntities_AndReturn() } [Fact] - public async Task InsertsEntities_WithJson() + public async Task InsertEntities_WithJson() { // Arrange var entities = new List @@ -114,7 +109,7 @@ public async Task InsertsEntities_WithJson() } [SkippableFact] - public async Task InsertsEntities_AndReturn_AsyncEnumerable() + public async Task InsertEntities_AndReturn_AsyncEnumerable() { Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase)); @@ -140,7 +135,7 @@ public async Task InsertsEntities_AndReturn_AsyncEnumerable() } [SkippableFact] - public void InsertsEntities_AndReturn_Sync() + public void InsertEntities_AndReturn_Sync() { Skip.If(_context.IsProvider(ProviderType.MySql)); @@ -161,7 +156,7 @@ public void InsertsEntities_AndReturn_Sync() } [SkippableFact] - public async Task InsertsEntities_MultipleTimes() + public async Task InsertEntities_MultipleTimes() { Skip.If(_context.IsProvider(ProviderType.PostgreSql)); Skip.If(_context.IsProvider(ProviderType.SqlServer)); @@ -195,7 +190,7 @@ await _context.ExecuteBulkInsertAsync(entities, } [SkippableFact] - public async Task InsertsEntities_MultipleTimes_WithGuidId() + public async Task InsertEntities_MultipleTimes_WithGuidId() { // Arrange var entities = new List @@ -226,7 +221,7 @@ await _context.ExecuteBulkInsertAsync(entities, } [SkippableFact] - public async Task InsertsEntities_MultipleTimes_With_Conflict_On_Id() + public async Task InsertEntities_MultipleTimes_With_Conflict_On_Id() { // Arrange var entities = new List @@ -259,7 +254,7 @@ await _context.ExecuteBulkInsertAsync(insertedEntities0, } [Fact] - public async Task InsertsEntities_MoveRows() + public async Task InsertEntities_MoveRows() { // Arrange var entities = new List @@ -282,7 +277,7 @@ await _context.ExecuteBulkInsertAsync(entities, o => } [SkippableFact] - public async Task InsertsEntities_WithConflict_SingleColumn() + public async Task InsertEntities_WithConflict_SingleColumn() { Skip.If(_context.IsProvider(ProviderType.MySql)); @@ -321,7 +316,7 @@ await _context.ExecuteBulkInsertAsync(entities, o => } [SkippableFact] - public async Task InsertsEntities_WithConflict_DoNothing() + public async Task InsertEntities_WithConflict_DoNothing() { Skip.If(_context.IsProvider(ProviderType.MySql)); @@ -351,7 +346,7 @@ await _context.ExecuteBulkInsertAsync(entities, o => } [SkippableFact] - public async Task InsertsEntities_WithConflict_Condition() + public async Task InsertEntities_WithConflict_Condition() { Skip.If(_context.IsProvider(ProviderType.MySql)); @@ -382,7 +377,7 @@ await _context.ExecuteBulkInsertAsync(entities, o => } [SkippableFact] - public async Task InsertsEntities_WithConflict_MultipleColumns() + public async Task InsertEntities_WithConflict_MultipleColumns() { Skip.If(_context.IsProvider(ProviderType.MySql)); @@ -418,7 +413,7 @@ await _context.ExecuteBulkInsertAsync(entities, o => } [Fact] - public async Task InsertsEntities_DoesNothing_WhenEntitiesAreEmpty() + public async Task InsertEntities_DoesNothing_WhenEntitiesAreEmpty() { // Arrange var entities = new List(); @@ -432,7 +427,7 @@ public async Task InsertsEntities_DoesNothing_WhenEntitiesAreEmpty() } [Fact] - public async Task InsertsEntities_Many() + public async Task InsertEntities_Many() { // Arrange const int count = 156055; diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs index f3d8a5e..9251653 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsMySql.cs @@ -5,14 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public class BasicTestsMySqlFixture : TestDbContainerMySql -{ - public BasicTestsMySqlFixture() : base("basic-mysql") - { - } -} - [Trait("Category", "MySql")] -public class BasicTestsMySql(BasicTestsMySqlFixture dbContainer) : BasicTestsBase(dbContainer) +[Collection(TestDbContainerMySqlCollection.Name)] +public class BasicTestsMySql(TestDbContainerMySql dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs index fcca2e6..7b369bf 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsPostgreSql.cs @@ -5,14 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public class BasicTestsPostgreSqlFixture : TestDbContainerPostgreSql -{ - public BasicTestsPostgreSqlFixture() : base("basic-postgresql") - { - } -} - [Trait("Category", "PostgreSql")] -public class BasicTestsPostgreSql(BasicTestsPostgreSqlFixture dbContainer) : BasicTestsBase(dbContainer) +[Collection(TestDbContainerPostgreSqlCollection.Name)] +public class BasicTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs index 75ddf50..c811fb5 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlServer.cs @@ -5,14 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public class BasicTestsSqlServerFixture : TestDbContainerSqlServer -{ - public BasicTestsSqlServerFixture() : base("basic-sqlserver") - { - } -} - [Trait("Category", "SqlServer")] -public class BasicTestsSqlServer(BasicTestsSqlServerFixture dbContainer) : BasicTestsBase(dbContainer) +[Collection(TestDbContainerSqlServerCollection.Name)] +public class BasicTestsSqlServer(TestDbContainerSqlServer dbContainer) : BasicTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs index 2a7d030..eee2394 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsSqlite.cs @@ -5,12 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic; -public class BasicTestsSqliteFixture : TestDbContainerSqlite -{ -} - [Trait("Category", "Sqlite")] -public class BasicTestsSqlite(BasicTestsSqliteFixture dbContainer) : BasicTestsBase(dbContainer) +[Collection(TestDbContainerSqliteCollection.Name)] +public class BasicTestsSqlite(TestDbContainerSqlite dbContainer) : BasicTestsBase(dbContainer) { } - diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs index 89298f4..890d0ba 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsBase.cs @@ -8,16 +8,15 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; -public abstract class GeoTestsBase(TestDbContainer dbContainer) : IClassFixture, IAsyncLifetime +public abstract class GeoTestsBase(TestDbContainer dbContainer) : IAsyncLifetime where TDbContext : TestDbContextGeo, new() - where TFixture : TestDbContainer { private readonly Guid _run = Guid.NewGuid(); private TDbContext _context = null!; public async Task InitializeAsync() { - _context = await DbContainer.CreateContextAsync(); + _context = await dbContainer.CreateContextAsync("geo"); } public Task DisposeAsync() @@ -26,10 +25,8 @@ public Task DisposeAsync() return Task.CompletedTask; } - protected TestDbContainer DbContainer { get; } = dbContainer; - [Fact] - public async Task InsertsEntities_WithGeo() + public async Task InsertEntities_WithGeo() { // Arrange var geo1 = new Point(1, 2) { SRID = 4326 }; diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs index 2ed1a1d..f1cb551 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsMySql.cs @@ -5,14 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; -public class GeoTestsMySqlFixture : TestDbContainerMySql -{ - public GeoTestsMySqlFixture() : base("geo-mysql") - { - } -} - [Trait("Category", "MySql")] -public class GeoTestsMySql(GeoTestsMySqlFixture dbContainer) : GeoTestsBase(dbContainer) +[Collection(TestDbContainerMySqlCollection.Name)] +public class GeoTestsMySql(TestDbContainerMySql dbContainer) : GeoTestsBase(dbContainer) { } diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs index e576e79..d53a2d7 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsPostgreSql.cs @@ -5,14 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; -public class GeoTestsPostgreSqlFixture : TestDbContainerPostgreSql -{ - public GeoTestsPostgreSqlFixture() : base("geo-postgresql") - { - } -} - [Trait("Category", "PostgreSql")] -public class GeoTestsPostgreSql(GeoTestsPostgreSqlFixture dbContainer) : GeoTestsBase(dbContainer) +[Collection(TestDbContainerPostgreSqlCollection.Name)] +public class GeoTestsPostgreSql(TestDbContainerPostgreSql dbContainer) : GeoTestsBase(dbContainer) { -} +} \ No newline at end of file diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs index a1116c7..008305e 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Geo/GeoTestsSqlServer.cs @@ -5,14 +5,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Geo; -public class GeoTestsSqlServerFixture : TestDbContainerSqlServer -{ - public GeoTestsSqlServerFixture() : base("geo-sqlserver") - { - } -} - [Trait("Category", "SqlServer")] -public class GeoTestsSqlServer(GeoTestsSqlServerFixture dbContainer) : GeoTestsBase(dbContainer) +[Collection(TestDbContainerSqlServerCollection.Name)] +public class GeoTestsSqlServer(TestDbContainerSqlServer dbContainer) : GeoTestsBase(dbContainer) { -} +} \ No newline at end of file From 991902b9b9522d1fea2c940f4622fe04f71273a5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 20:33:56 +0200 Subject: [PATCH 13/14] Test --- .../PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj index 3ef8eb2..8c1fb6b 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj @@ -13,7 +13,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - From a55457a1faad8e6aeff9e51538313c4f2c2ea2fe Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 24 May 2025 20:44:10 +0200 Subject: [PATCH 14/14] Fix dependencies. --- .../PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj index 8c1fb6b..9f5facd 100644 --- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj +++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/PhenX.EntityFrameworkCore.BulkInsert.Tests.csproj @@ -13,10 +13,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + - - + + + + all