Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ CancellationToken ctk
sourceOrdinal++;
}

var dataReader = new EnumerableDataReader<T>(entities, properties, options.Converters);
var dataReader = new EnumerableDataReader<T>(entities, properties, options);

if (sync)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using MySqlConnector;
using MySqlConnector;

using NetTopologySuite.Geometries;

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert.MySql;

Expand All @@ -14,11 +15,11 @@ private MySqlGeometryConverter()
{
}

public bool TryConvertValue(object source, out object result)
public bool TryConvertValue(object source, BulkInsertOptions options, out object result)
{
if (source is Geometry geometry)
{
result = MySqlGeometry.FromWkb(geometry.SRID, geometry.ToBinary());
result = MySqlGeometry.FromWkb(options.SRID, geometry.ToBinary());
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ protected override async Task BulkInsert<T>(
? connection.BeginBinaryImport(command)
: await connection.BeginBinaryImportAsync(command, ctk);

var bulkValueConverters = options.Converters;

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

Expand All @@ -76,7 +74,7 @@ protected override async Task BulkInsert<T>(

for (var columnIndex = 0; columnIndex < columns.Count; columnIndex++)
{
var value = columns[columnIndex].GetValue(entity, bulkValueConverters);
var value = columns[columnIndex].GetValue(entity, options);

// Get the actual type, so that the writer can do the conversation to the target type automatically.
var type = columnTypes[columnIndex];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata;

using NetTopologySuite.Geometries;

using NpgsqlTypes;

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;

Expand All @@ -16,10 +17,16 @@ private PostgreSqlGeometryConverter()
{
}

public bool TryConvertValue(object source, out object result)
public bool TryConvertValue(object source, BulkInsertOptions options, out object result)
{
if (source is Geometry geometry)
{
if (geometry.SRID != options.SRID)
{
geometry = geometry.Copy();
geometry.SRID = options.SRID;
}

result = geometry.ToBinary();
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected override async Task BulkInsert<T>(
bulkCopy.ColumnMappings.Add(column.PropertyName, column.ColumnName);
}

var dataReader = new EnumerableDataReader<T>(entities, columns, options.Converters);
var dataReader = new EnumerableDataReader<T>(entities, columns, options);

if (sync)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Data.SqlTypes;
using System.Data.SqlTypes;

using Microsoft.SqlServer.Types;

using NetTopologySuite.Geometries;

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert.SqlServer;

Expand All @@ -16,12 +17,12 @@ private SqlServerGeometryConverter()
{
}

public bool TryConvertValue(object source, out object result)
public bool TryConvertValue(object source, BulkInsertOptions options, out object result)
{
if (source is Geometry geometry)
{
var reversed = Reverse(geometry);
result = SqlGeometry.STGeomFromWKB(new SqlBytes(reversed.AsBinary()), geometry.SRID);
result = SqlGeometry.STGeomFromWKB(new SqlBytes(reversed.AsBinary()), options.SRID);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,37 +144,48 @@ CancellationToken ctk
var columnList = tableInfo.GetColumns(options.CopyGeneratedColumns);
var columnTypes = columnList.Select(GetSqliteType).ToArray();

await using var insertCommand =
GetInsertCommand(
context,
tableName,
columnList,
columnTypes,
sb,
batchSize);

foreach (var chunk in entities.Chunk(batchSize))
DbCommand? insertCommand = null;
try
{
// Full chunks
if (chunk.Length == batchSize)
foreach (var chunk in entities.Chunk(batchSize))
{
FillValues(chunk, insertCommand.Parameters, columns);
await ExecuteCommand(sync, insertCommand, ctk);
// Full chunks
if (chunk.Length == batchSize)
{
insertCommand ??=
GetInsertCommand(
context,
tableName,
columnList,
columnTypes,
sb,
batchSize);

FillValues(chunk, insertCommand.Parameters, columns, options);
await ExecuteCommand(sync, insertCommand, ctk);
}
// Last chunk
else
{
await using var partialInsertCommand =
GetInsertCommand(
context,
tableName,
columnList,
columnTypes,
sb,
chunk.Length);

FillValues(chunk, partialInsertCommand.Parameters, columns, options);
await ExecuteCommand(sync, partialInsertCommand, ctk);
}
}
// Last chunk
else
}
finally
{
if (insertCommand != null)
{
await using var partialInsertCommand =
GetInsertCommand(
context,
tableName,
columnList,
columnTypes,
sb,
chunk.Length);

FillValues(chunk, partialInsertCommand.Parameters, columns);
await ExecuteCommand(sync, partialInsertCommand, ctk);
await insertCommand.DisposeAsync();
}
}
}
Expand All @@ -192,7 +203,11 @@ private static async Task ExecuteCommand(bool sync, DbCommand insertCommand, Can
}
}

private static void FillValues<T>(T[] chunk, DbParameterCollection parameters, IReadOnlyList<ColumnMetadata> columns) where T : class
private static void FillValues<T>(
T[] chunk,
DbParameterCollection parameters,
IReadOnlyList<ColumnMetadata> columns,
BulkInsertOptions options) where T : class
{
var p = 0;

Expand All @@ -203,7 +218,7 @@ private static void FillValues<T>(T[] chunk, DbParameterCollection parameters, I
for (var columnIndex = 0; columnIndex < columns.Count; columnIndex++)
{
var column = columns[columnIndex];
var value = column.GetValue(entity, null);
var value = column.GetValue(entity, options);
parameters[p].Value = value;
p++;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert.Abstractions;

/// <summary>
/// Provide an interface to control how objects are written.
Expand All @@ -10,6 +12,7 @@ public interface IBulkValueConverter
/// </summary>
/// <param name="source">The source object.</param>
/// <param name="result">The result type.</param>
/// <param name="options">The options.</param>
/// <returns>Indicates if an object should be written.</returns>
bool TryConvertValue(object source, out object result);
bool TryConvertValue(object source, BulkInsertOptions options, out object result);
}
10 changes: 7 additions & 3 deletions src/PhenX.EntityFrameworkCore.BulkInsert/EnumerableDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
using PhenX.EntityFrameworkCore.BulkInsert.Metadata;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert;

internal sealed class EnumerableDataReader<T>(IEnumerable<T> rows, IReadOnlyList<ColumnMetadata> columns, List<IBulkValueConverter>? converters) : IDataReader
internal sealed class EnumerableDataReader<T>(
IEnumerable<T> rows,
IReadOnlyList<ColumnMetadata> columns,
BulkInsertOptions options) : IDataReader
{
private readonly IEnumerator<T> _enumerator = rows.GetEnumerator();
private readonly Dictionary<string, int> _ordinalMap =
Expand All @@ -24,7 +28,7 @@ public object GetValue(int i)
return DBNull.Value;
}

return columns[i].GetValue(current, converters)!;
return columns[i].GetValue(current, options)!;
}

public int GetValues(object[] values)
Expand All @@ -37,7 +41,7 @@ public int GetValues(object[] values)

for (var i = 0; i < columns.Count; i++)
{
values[i] = columns[i].GetValue(current, converters)!;
values[i] = columns[i].GetValue(current, options)!;
}

return columns.Count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
using PhenX.EntityFrameworkCore.BulkInsert.Dialect;
using PhenX.EntityFrameworkCore.BulkInsert.Options;

namespace PhenX.EntityFrameworkCore.BulkInsert.Metadata;

Expand All @@ -24,15 +25,15 @@ internal sealed class ColumnMetadata(IProperty property, SqlDialectBuilder dial

public bool IsGenerated { get; } = property.ValueGenerated == ValueGenerated.OnAdd;

public object? GetValue(object entity, List<IBulkValueConverter>? converters)
public object? GetValue(object entity, BulkInsertOptions options)
{
var result = _getter(entity);

if (converters != null && result != null)
if (options.Converters != null && result != null)
{
foreach (var converter in converters)
foreach (var converter in options.Converters)
{
if (converter.TryConvertValue(result, out var temp))
if (converter.TryConvertValue(result, options, out var temp))
{
result = temp;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class BulkInsertOptions
/// </summary>
public List<IBulkValueConverter>? Converters { get; set; }

/// <summary>
/// Sets the ID of the Spatial Reference System used by the Geometries to be inserted.
/// </summary>
public int SRID { get; set; } = 4326;

internal int GetCopyTimeoutInSeconds()
{
return Math.Max(0, (int)CopyTimeout.TotalSeconds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,29 @@ public async Task InsertEntities_WithGeo(InsertStrategy strategy)
insertedEntities.Should().BeEquivalentTo(entities,
o => o.RespectingRuntimeTypes().Excluding((TestEntityWithGeo e) => e.Id));
}

[SkippableTheory]
[CombinatorialData]
public async Task InsertEntities_WithGeo_And_Default_SRID(InsertStrategy strategy)
{
// Arrange
var geo1 = new Point(1, 2);
var geo2 = new Point(3, 4);

var entities = new List<TestEntityWithGeo>
{
new TestEntityWithGeo { GeoObject = geo1 },
new TestEntityWithGeo { GeoObject = geo2 }
};

// Act
var insertedEntities = await _context.InsertWithStrategyAsync(strategy, entities);

geo1.SRID = 4326;
geo2.SRID = 4326;

// Assert
insertedEntities.Should().BeEquivalentTo(entities,
o => o.RespectingRuntimeTypes().Excluding((TestEntityWithGeo e) => e.Id));
}
}