Skip to content

Commit 429f643

Browse files
More tests.
1 parent e703fb7 commit 429f643

7 files changed

Lines changed: 100 additions & 58 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider>? logger = null)
1616

1717
//language=sql
1818
/// <inheritdoc />
19-
protected override string CreateTableCopySql => "CREATE TEMPORARY TABLE {0} SELECT * FROM {1} WHERE 1 = 0;";
19+
protected override string CreateTableCopySql => "CREATE TEMPORARY TABLE {0} SELECT * FROM {1} WHERE 1 = 0;";
2020

2121
//language=sql
2222
/// <inheritdoc />

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Linq.Expressions;
12
using System.Text;
23

34
using Microsoft.EntityFrameworkCore;

src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,12 @@ private async Task<List<TResult>> CopyFromTempTableWithoutKeysAsync<T, TResult>(
108108
{
109109
var (schemaName, tableName, _) = GetTableInfo(context, typeof(T));
110110
var quotedTableName = QuoteTableName(schemaName, tableName);
111-
var movedProperties = context.GetProperties(typeof(T), false);
111+
var movedProperties = context.GetProperties(typeof(T), options.CopyGeneratedColumns);
112112
var returnedProperties = returnData ? context.GetProperties(typeof(T)) : [];
113113

114114
if (returnData && !SqlDialect.SupportsReturning)
115115
{
116-
var moveQuery = SqlDialect.BuildMoveDataSql<T>(context, tempTableName, quotedTableName, movedProperties, [], options, onConflict);
117-
118-
// Just copy the values first.
119-
await ExecuteAsync(sync, context, moveQuery, cancellationToken);
120-
121-
// Then query them.
122-
var selectQuery = SqlDialect.BuildSelectSql<T>(context, tempTableName, returnedProperties);
123-
124-
return await QueryAsync(sync, context, selectQuery, cancellationToken);
116+
throw new NotSupportedException("Provider does not support returning entities.");
125117
}
126118

127119
var query = SqlDialect.BuildMoveDataSql<T>(context, tempTableName, quotedTableName, movedProperties, returnedProperties, options, onConflict);
@@ -246,7 +238,7 @@ public async Task BulkInsert<T>(
246238
: GetQuotedTableName(context, typeof(T));
247239

248240
var properties = context
249-
.GetProperties(typeof(T), false)
241+
.GetProperties(typeof(T), options.CopyGeneratedColumns)
250242
.Select(p => new PropertyAccessor(p))
251243
.ToArray();
252244

src/PhenX.EntityFrameworkCore.BulkInsert/Dialect/SqlDialectBuilder.cs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.EntityFrameworkCore;
55
using Microsoft.EntityFrameworkCore.Metadata;
6+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
67

78
using PhenX.EntityFrameworkCore.BulkInsert.Options;
89

@@ -178,33 +179,6 @@ protected virtual void AppendConflictCondition<T>(StringBuilder sql, OnConflictO
178179
sql.AppendLine($"WHERE {onConflictTyped.Condition}");
179180
}
180181

181-
/// <summary>
182-
/// Builds the SQL for selecting data from one table.
183-
/// </summary>
184-
/// <param name="context">The DbContext</param>
185-
/// <param name="source">Source table name</param>
186-
/// <param name="insertedProperties">Properties to be copied</param>
187-
/// <typeparam name="T">Entity type</typeparam>
188-
/// <returns>The SQL query</returns>
189-
public virtual string BuildSelectSql<T>(DbContext context, string source,
190-
IProperty[] insertedProperties)
191-
{
192-
var insertedColumns = insertedProperties.Select(p => Quote(p.GetColumnName()));
193-
var insertedColumnList = string.Join(", ", insertedColumns);
194-
195-
var q = new StringBuilder();
196-
197-
q.AppendLine($"""
198-
SELECT {insertedColumnList}
199-
FROM {source}
200-
WHERE TRUE
201-
""");
202-
203-
q.AppendLine(";");
204-
205-
return q.ToString();
206-
}
207-
208182
/// <summary>
209183
/// Get the name of the excluded column for the ON CONFLICT clause.
210184
/// </summary>

src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ public class BulkInsertOptions
3232
/// </list>
3333
/// </summary>
3434
public int? BatchSize { get; set; }
35+
36+
/// <summary>
37+
/// Indicates if also generated columns should be copied. This is useful for upsert operations.
38+
/// </summary>
39+
public bool CopyGeneratedColumns { get; set; }
3540
}

tests/PhenX.EntityFrameworkCore.BulkInsert.Benchmark/LibComparatorSqlServer.cs

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

66
using Microsoft.EntityFrameworkCore;
77

8+
using PhenX.EntityFrameworkCore.BulkInsert.Sqlite;
89
using PhenX.EntityFrameworkCore.BulkInsert.SqlServer;
910

1011
using Testcontainers.MsSql;

tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,8 @@ namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.Tests.Basic;
99

1010
public abstract class BasicTestsBase : IAsyncLifetime
1111
{
12-
private static int Id = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
1312
private readonly string _prefix = Guid.NewGuid().ToString();
1413

15-
public static int GetId()
16-
{
17-
return Interlocked.Increment(ref Id) + 1;
18-
}
19-
2014
protected BasicTestsBase(TestDbContainer<TestDbContext> dbContainer)
2115
{
2216
DbContainer = dbContainer;
@@ -30,12 +24,12 @@ public async Task InsertsEntitiesSuccessfully()
3024
// Arrange
3125
var entities = new List<TestEntity>
3226
{
33-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity1" },
34-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity2" }
27+
new TestEntity { Name = $"{_prefix}_Entity1" },
28+
new TestEntity { Name = $"{_prefix}_Entity2" }
3529
};
3630

3731
// Act
38-
await DbContainer.DbContext.ExecuteBulkInsertReturnEntitiesAsync(entities);
32+
await DbContainer.DbContext.ExecuteBulkInsertAsync(entities);
3933

4034
// Assert
4135
var insertedEntities = DbContainer.DbContext.TestEntities.ToList();
@@ -50,12 +44,12 @@ public void InsertsEntitiesSuccessfully_Sync()
5044
// Arrange
5145
var entities = new List<TestEntity>
5246
{
53-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity1" },
54-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity2" }
47+
new TestEntity { Name = $"{_prefix}_Entity1" },
48+
new TestEntity { Name = $"{_prefix}_Entity2" }
5549
};
5650

5751
// Act
58-
DbContainer.DbContext.ExecuteBulkInsertReturnEntities(entities);
52+
DbContainer.DbContext.ExecuteBulkInsert(entities);
5953

6054
// Assert
6155
var insertedEntities = DbContainer.DbContext.TestEntities.ToList();
@@ -64,25 +58,67 @@ public void InsertsEntitiesSuccessfully_Sync()
6458
Assert.Contains(insertedEntities, e => e.Name == $"{_prefix}_Entity2");
6559
}
6660

61+
[SkippableFact]
62+
public async Task InsertsEntitiesAndReturn()
63+
{
64+
Skip.If(DbContainer.DbContext.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
65+
66+
// Arrange
67+
var entities = new List<TestEntity>
68+
{
69+
new TestEntity { Name = $"{_prefix}_Entity1" },
70+
new TestEntity { Name = $"{_prefix}_Entity2" }
71+
};
72+
73+
// Act
74+
var insertedEntities = await DbContainer.DbContext.ExecuteBulkInsertReturnEntitiesAsync(entities);
75+
76+
// Assert
77+
Assert.Equal(2, insertedEntities.Count);
78+
Assert.Contains(insertedEntities, e => e.Name == $"{_prefix}_Entity1");
79+
Assert.Contains(insertedEntities, e => e.Name == $"{_prefix}_Entity2");
80+
}
81+
82+
[SkippableFact]
83+
public void InsertsEntitiesAndReturn_Sync()
84+
{
85+
Skip.If(DbContainer.DbContext.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
86+
87+
// Arrange
88+
var entities = new List<TestEntity>
89+
{
90+
new TestEntity { Name = $"{_prefix}_Entity1" },
91+
new TestEntity { Name = $"{_prefix}_Entity2" }
92+
};
93+
94+
// Act
95+
var insertedEntities = DbContainer.DbContext.ExecuteBulkInsertReturnEntities(entities);
96+
97+
// Assert
98+
Assert.Equal(2, insertedEntities.Count);
99+
Assert.Contains(insertedEntities, e => e.Name == $"{_prefix}_Entity1");
100+
Assert.Contains(insertedEntities, e => e.Name == $"{_prefix}_Entity2");
101+
}
102+
67103
[Fact]
68104
public async Task InsertsEntities_MultipleTimes()
69105
{
70106
// Arrange
71107
var entities = new List<TestEntity>
72108
{
73-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity1" },
74-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity2" }
109+
new TestEntity { Name = $"{_prefix}_Entity1" },
110+
new TestEntity { Name = $"{_prefix}_Entity2" }
75111
};
76112

77113
// Act
78-
await DbContainer.DbContext.ExecuteBulkInsertReturnEntitiesAsync(entities);
114+
await DbContainer.DbContext.ExecuteBulkInsertAsync(entities);
79115

80116
foreach (var entity in entities)
81117
{
82118
entity.NumericEnumValue = NumericEnum.Second;
83119
}
84120

85-
await DbContainer.DbContext.ExecuteBulkInsertReturnEntitiesAsync(entities,
121+
await DbContainer.DbContext.ExecuteBulkInsertAsync(entities,
86122
onConflict: new OnConflictOptions<TestEntity>
87123
{
88124
Update = e => e,
@@ -95,18 +131,51 @@ await DbContainer.DbContext.ExecuteBulkInsertReturnEntitiesAsync(entities,
95131
Assert.Contains(insertedEntities, e => e.NumericEnumValue == NumericEnum.Second);
96132
}
97133

134+
[Fact]
135+
public async Task InsertsEntities_MultipleTimes_With_Conflict_On_Id()
136+
{
137+
// Arrange
138+
var entities = new List<TestEntity>
139+
{
140+
new TestEntity { Name = $"{_prefix}_Entity1" },
141+
new TestEntity { Name = $"{_prefix}_Entity2" }
142+
};
143+
144+
// Act
145+
await DbContainer.DbContext.ExecuteBulkInsertAsync(entities);
146+
147+
var insertedEntities0 = DbContainer.DbContext.TestEntities.ToList();
148+
foreach (var entity in insertedEntities0)
149+
{
150+
entity.Name = $"Updated_{entity.Name}";
151+
}
152+
153+
await DbContainer.DbContext.ExecuteBulkInsertAsync(insertedEntities0,
154+
o => o.CopyGeneratedColumns = true,
155+
onConflict: new OnConflictOptions<TestEntity>
156+
{
157+
Update = e => e,
158+
});
159+
160+
// Assert
161+
var insertedEntities1 = DbContainer.DbContext.TestEntities.ToList();
162+
Assert.Equal(2, insertedEntities1.Count);
163+
Assert.Contains(insertedEntities1, e => e.Name == $"Updated_{_prefix}_Entity1");
164+
Assert.Contains(insertedEntities1, e => e.Name == $"Updated_{_prefix}_Entity2");
165+
}
166+
98167
[Fact]
99168
public async Task InsertsEntitiesMoveRowsSuccessfully()
100169
{
101170
// Arrange
102171
var entities = new List<TestEntity>
103172
{
104-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity1" },
105-
new TestEntity { Id = GetId(), Name = $"{_prefix}_Entity2" }
173+
new TestEntity { Name = $"{_prefix}_Entity1" },
174+
new TestEntity { Name = $"{_prefix}_Entity2" }
106175
};
107176

108177
// Act
109-
await DbContainer.DbContext.ExecuteBulkInsertReturnEntitiesAsync(entities, o =>
178+
await DbContainer.DbContext.ExecuteBulkInsertAsync(entities, o =>
110179
{
111180
o.MoveRows = true;
112181
});

0 commit comments

Comments
 (0)