Skip to content

Commit e17ba44

Browse files
Geo support (#34)
* Temp * Geometry support. * Fix providers * Remove nettopoloy for sqlite for now. * Try to install sqlite spatial. * Seperate tests for geo stuff. * Remove spatial. * Another test * Another test * Fix fixtures. * Fix provider. * Easier fixture. * Test * Fix dependencies.
1 parent 1f21ee4 commit e17ba44

36 files changed

Lines changed: 565 additions & 96 deletions

.github/workflows/dotnet-test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,22 @@ jobs:
1616

1717
steps:
1818
- uses: actions/checkout@v4
19+
1920
- name: Setup .NET 8.0
2021
uses: actions/setup-dotnet@v4
2122
with:
2223
dotnet-version: 8.0.x
24+
2325
- name: Setup .NET 9.0
2426
uses: actions/setup-dotnet@v4
2527
with:
2628
dotnet-version: 9.0.x
29+
2730
- name: Restore dependencies
2831
run: dotnet restore
32+
2933
- name: Build
3034
run: dotnet build --no-restore
35+
3136
- name: Test
3237
run: dotnet test --no-build --verbosity normal

src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ internal class MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider> logger)
2222
protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
2323

2424
/// <inheritdoc />
25-
protected override MySqlBulkInsertOptions CreateDefaultOptions() => new();
25+
protected override MySqlBulkInsertOptions CreateDefaultOptions() => new()
26+
{
27+
Converters = [MySqlGeometryConverter.Instance]
28+
};
2629

2730
/// <inheritdoc />
2831
protected override IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
@@ -71,11 +74,11 @@ CancellationToken ctk
7174
if (sync)
7275
{
7376
// ReSharper disable once MethodHasAsyncOverloadWithCancellation
74-
bulkCopy.WriteToServer(new EnumerableDataReader<T>(entities, properties));
77+
bulkCopy.WriteToServer(new EnumerableDataReader<T>(entities, properties, options.Converters));
7578
}
7679
else
7780
{
78-
await bulkCopy.WriteToServerAsync(new EnumerableDataReader<T>(entities, properties), ctk);
81+
await bulkCopy.WriteToServerAsync(new EnumerableDataReader<T>(entities, properties, options.Converters), ctk);
7982
}
8083
}
8184
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using MySqlConnector;
2+
3+
using NetTopologySuite.Geometries;
4+
5+
using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
6+
7+
namespace PhenX.EntityFrameworkCore.BulkInsert.MySql;
8+
9+
internal sealed class MySqlGeometryConverter : IBulkValueConverter
10+
{
11+
public static readonly MySqlGeometryConverter Instance = new();
12+
13+
private MySqlGeometryConverter()
14+
{
15+
}
16+
17+
public bool TryConvertValue(object source, out object result)
18+
{
19+
if (source is Geometry geometry)
20+
{
21+
result = MySqlGeometry.FromWkb(geometry.SRID, geometry.ToBinary());
22+
return true;
23+
}
24+
25+
result = source;
26+
return false;
27+
}
28+
}

src/PhenX.EntityFrameworkCore.BulkInsert.MySql/PhenX.EntityFrameworkCore.BulkInsert.MySql.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</ItemGroup>
1010

1111
<ItemGroup>
12+
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
1213
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.3" />
1314
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.0-preview.3.efcore.9.0.0" />
1415
</ItemGroup>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.EntityFrameworkCore.Metadata;
2+
3+
using NpgsqlTypes;
4+
5+
namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;
6+
7+
/// <summary>
8+
/// Provides the type to write.
9+
/// </summary>
10+
public interface IPostgresTypeProvider
11+
{
12+
/// <summary>
13+
/// Gets the type of a value before written to the output.
14+
/// </summary>
15+
/// <param name="property">The source property.</param>
16+
/// <param name="result">The result type.</param>
17+
/// <returns>Indicates if an object should be written.</returns>
18+
bool TryGetType(IProperty property, out NpgsqlDbType result);
19+
}

src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</ItemGroup>
66

77
<ItemGroup>
8+
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
89
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.11" />
910
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.4" />
1011
</ItemGroup>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using PhenX.EntityFrameworkCore.BulkInsert.Options;
2+
3+
namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;
4+
5+
/// <summary>
6+
/// Options specific to SQL Server bulk insert.
7+
/// </summary>
8+
public class PostgreSqlBulkInsertOptions : BulkInsertOptions
9+
{
10+
/// <summary>
11+
/// A list of type providers.
12+
/// </summary>
13+
public List<IPostgresTypeProvider>? TypeProviders { get; set; }
14+
}

src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@
1111
using NpgsqlTypes;
1212

1313
using PhenX.EntityFrameworkCore.BulkInsert.Metadata;
14-
using PhenX.EntityFrameworkCore.BulkInsert.Options;
1514

1615
namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;
1716

1817
[UsedImplicitly]
19-
internal class PostgreSqlBulkInsertProvider(ILogger<PostgreSqlBulkInsertProvider>? logger) : BulkInsertProviderBase<PostgreSqlDialectBuilder, BulkInsertOptions>(logger)
18+
internal class PostgreSqlBulkInsertProvider(ILogger<PostgreSqlBulkInsertProvider>? logger) : BulkInsertProviderBase<PostgreSqlDialectBuilder, PostgreSqlBulkInsertOptions>(logger)
2019
{
2120
//language=sql
2221
/// <inheritdoc />
@@ -32,9 +31,11 @@ private static string GetBinaryImportCommand(IReadOnlyList<ColumnMetadata> prope
3231
}
3332

3433
/// <inheritdoc />
35-
protected override BulkInsertOptions CreateDefaultOptions() => new()
34+
protected override PostgreSqlBulkInsertOptions CreateDefaultOptions() => new()
3635
{
3736
BatchSize = 50_000,
37+
Converters = [PostgreSqlGeometryConverter.Instance],
38+
TypeProviders = [PostgreSqlGeometryConverter.Instance],
3839
};
3940

4041
/// <inheritdoc />
@@ -45,7 +46,7 @@ protected override async Task BulkInsert<T>(
4546
IEnumerable<T> entities,
4647
string tableName,
4748
IReadOnlyList<ColumnMetadata> columns,
48-
BulkInsertOptions options,
49+
PostgreSqlBulkInsertOptions options,
4950
CancellationToken ctk)
5051
{
5152
var connection = (NpgsqlConnection)context.Database.GetDbConnection();
@@ -57,7 +58,7 @@ protected override async Task BulkInsert<T>(
5758
: await connection.BeginBinaryImportAsync(command, ctk);
5859

5960
// The type mapping can be null for obvious types like string.
60-
var columnTypes = columns.Select(GetPostgreSqlType).ToArray();
61+
var columnTypes = columns.Select(c => GetPostgreSqlType(c, options)).ToArray();
6162

6263
foreach (var entity in entities)
6364
{
@@ -74,7 +75,7 @@ protected override async Task BulkInsert<T>(
7475
var columnIndex = 0;
7576
foreach (var column in columns)
7677
{
77-
var value = column.GetValue(entity);
78+
var value = column.GetValue(entity, options.Converters);
7879

7980
// Get the actual type, so that the writer can do the conversation to the target type automatically.
8081
var type = columnTypes[columnIndex];
@@ -122,8 +123,20 @@ protected override async Task BulkInsert<T>(
122123
}
123124
}
124125

125-
private static NpgsqlDbType? GetPostgreSqlType(ColumnMetadata column)
126+
private static NpgsqlDbType? GetPostgreSqlType(ColumnMetadata column, PostgreSqlBulkInsertOptions options)
126127
{
128+
var typeProviders = options.TypeProviders;
129+
if (typeProviders is { Count: > 0 })
130+
{
131+
foreach (var typeProvider in typeProviders)
132+
{
133+
if (typeProvider.TryGetType(column.Property, out var type))
134+
{
135+
return type;
136+
}
137+
}
138+
}
139+
127140
var mapping = column.Property.GetRelationalTypeMapping() as NpgsqlTypeMapping;
128141

129142
return mapping?.NpgsqlDbType;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Microsoft.EntityFrameworkCore.Metadata;
2+
3+
using NetTopologySuite.Geometries;
4+
5+
using NpgsqlTypes;
6+
7+
using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
8+
9+
namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;
10+
11+
internal sealed class PostgreSqlGeometryConverter : IBulkValueConverter, IPostgresTypeProvider
12+
{
13+
public static readonly PostgreSqlGeometryConverter Instance = new();
14+
15+
private PostgreSqlGeometryConverter()
16+
{
17+
}
18+
19+
public bool TryConvertValue(object source, out object result)
20+
{
21+
if (source is Geometry geometry)
22+
{
23+
result = geometry.ToBinary();
24+
return true;
25+
}
26+
27+
result = source;
28+
return false;
29+
}
30+
31+
public bool TryGetType(IProperty property, out NpgsqlDbType result)
32+
{
33+
if (property.ClrType.IsAssignableTo(typeof(Geometry)))
34+
{
35+
result = NpgsqlDbType.Bytea;
36+
return true;
37+
}
38+
39+
result = default;
40+
return false;
41+
}
42+
}

src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/PhenX.EntityFrameworkCore.BulkInsert.SqlServer.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
</ItemGroup>
66

77
<ItemGroup>
8+
<PackageReference Include="Microsoft.SqlServer.Types" Version="160.1000.6" />
9+
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
810
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.16" />
911
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.5" />
1012
</ItemGroup>

0 commit comments

Comments
 (0)