Skip to content

Commit c19e456

Browse files
Fix providers
1 parent 6c54b6b commit c19e456

20 files changed

Lines changed: 237 additions & 43 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace PhenX.EntityFrameworkCore.BulkInsert.MySql;
88

9-
internal sealed class MySqlGeometryConverter : IValueConverter
9+
internal sealed class MySqlGeometryConverter : IBulkValueConverter
1010
{
1111
public static readonly MySqlGeometryConverter Instance = new();
1212

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>

src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ internal class SqlServerBulkInsertProvider(ILogger<SqlServerBulkInsertProvider>?
2222
protected override SqlServerBulkInsertOptions CreateDefaultOptions() => new()
2323
{
2424
BatchSize = 50_000,
25+
Converters = [SqlServerGeometryConverter.Instance]
2526
};
2627

2728
/// <inheritdoc />
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Data.SqlTypes;
2+
3+
using Microsoft.SqlServer.Types;
4+
5+
using NetTopologySuite.Geometries;
6+
7+
using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
8+
9+
namespace PhenX.EntityFrameworkCore.BulkInsert.SqlServer;
10+
11+
internal sealed class SqlServerGeometryConverter : IBulkValueConverter
12+
{
13+
public static readonly SqlServerGeometryConverter Instance = new();
14+
15+
private SqlServerGeometryConverter()
16+
{
17+
}
18+
19+
public bool TryConvertValue(object source, out object result)
20+
{
21+
if (source is Geometry geometry)
22+
{
23+
var reversed = Reverse(geometry);
24+
result = SqlGeometry.STGeomFromWKB(new SqlBytes(reversed.AsBinary()), geometry.SRID);
25+
return true;
26+
}
27+
28+
result = source;
29+
return false;
30+
}
31+
32+
private static Geometry Reverse(Geometry input)
33+
{
34+
switch (input)
35+
{
36+
case Point point:
37+
return Reverse(point);
38+
39+
case LineString lineString:
40+
return Reverse(lineString);
41+
42+
case Polygon polygon:
43+
return Reverse(polygon);
44+
45+
case MultiPoint multiPoint:
46+
return Reverse(multiPoint);
47+
48+
case MultiLineString multiLineString:
49+
return Reverse(multiLineString);
50+
51+
case MultiPolygon mpoly:
52+
return Reverse(mpoly);
53+
54+
case GeometryCollection gc:
55+
return Reverse(gc);
56+
57+
default:
58+
throw new NotSupportedException($"Unsupported geometry type: {input.GeometryType}");
59+
}
60+
}
61+
62+
private static Point Reverse(Point input)
63+
{
64+
return input.Factory.CreatePoint(Swap(input.Coordinate));
65+
}
66+
67+
private static LineString Reverse(LineString input)
68+
{
69+
return input.Factory.CreateLineString(Swap(input.Coordinates));
70+
}
71+
72+
private static MultiPoint Reverse(MultiPoint input)
73+
{
74+
return input.Factory.CreateMultiPoint(input.Geometries.OfType<Point>().Select(Reverse).ToArray());
75+
}
76+
77+
private static MultiLineString Reverse(MultiLineString input)
78+
{
79+
return input.Factory.CreateMultiLineString(input.Geometries.OfType<LineString>().Select(Reverse).ToArray());
80+
}
81+
82+
private static MultiPolygon Reverse(MultiPolygon input)
83+
{
84+
return input.Factory.CreateMultiPolygon(input.Geometries.OfType<Polygon>().Select(Reverse).ToArray());
85+
}
86+
87+
private static GeometryCollection Reverse(GeometryCollection input)
88+
{
89+
return input.Factory.CreateGeometryCollection(input.Geometries.Select(Reverse).ToArray());
90+
}
91+
92+
private static Polygon Reverse(Polygon input)
93+
{
94+
var factory = input.Factory;
95+
96+
return input.Factory.CreatePolygon(
97+
factory.CreateLinearRing(Swap(input.Shell.Coordinates)),
98+
input.Holes.Select(h => factory.CreateLinearRing(Swap(h.Coordinates))).ToArray());
99+
}
100+
101+
private static Coordinate Swap(Coordinate c) => new Coordinate(c.Y, c.X);
102+
103+
private static Coordinate[] Swap(Coordinate[] coords) => coords.Select(Swap).ToArray();
104+
105+
}

src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ private static void FillValues<T>(T[] chunk, DbParameterCollection parameters, I
190190
{
191191
foreach (var column in columns)
192192
{
193-
var value = column.GetValue(entity);
193+
var value = column.GetValue(entity, null);
194194
parameters[p].Value = value;
195195
p++;
196196
}

0 commit comments

Comments
 (0)