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 @@ -246,7 +246,7 @@ protected IEnumerable<string> GetUpdates<T>(DbContext context, TableMetadata tab
{
foreach (var arg in newExpr.Arguments.Zip(newExpr.Members, (expr, member) => (expr, member)))
{
yield return $"{table.GetColumnName(arg.member.Name)} = {ToSqlExpression<T>(context, table, arg.expr, lambda)}";
yield return $"{table.GetQuotedColumnName(arg.member.Name)} = {ToSqlExpression<T>(context, table, arg.expr, lambda)}";
}

break;
Expand All @@ -255,13 +255,13 @@ protected IEnumerable<string> GetUpdates<T>(DbContext context, TableMetadata tab
{
foreach (var binding in memberInit.Bindings.OfType<MemberAssignment>())
{
yield return $"{table.GetColumnName(binding.Member.Name)} = {ToSqlExpression<T>(context, table, binding.Expression, lambda)}";
yield return $"{table.GetQuotedColumnName(binding.Member.Name)} = {ToSqlExpression<T>(context, table, binding.Expression, lambda)}";
}

break;
}
case MemberExpression memberExpr:
yield return $"{table.GetColumnName(memberExpr.Member.Name)} = {ToSqlExpression<T>(context, table, memberExpr, lambda)}";
yield return $"{table.GetQuotedColumnName(memberExpr.Member.Name)} = {ToSqlExpression<T>(context, table, memberExpr, lambda)}";
break;

case ParameterExpression parameterExpr when (parameterExpr.Type == typeof(T)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class TestDbContext : TestDbContextBase
public DbSet<TestEntityWithConverters> TestEntitiesWithConverter { get; set; } = null!;
public DbSet<TestEntityWithComplexType> TestEntitiesWithComplexType { get; set; } = null!;
public DbSet<TestEntityWithSmartEnum> TestEntitiesWithSmartEnum { get; set; } = null!;
public DbSet<TestEntityWithSpecialColumnNames> TestEntitiesWithSpecialColumnNames { get; set; } = null!;
public DbSet<Student> Students { get; set; } = null!;
public DbSet<Course> Courses { get; set; } = null!;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

using Microsoft.EntityFrameworkCore;

namespace PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext;

/// <summary>
/// Test entity with column names containing spaces and SQL reserved keywords.
/// This is used to test that column names are properly quoted in SQL statements.
/// </summary>
[PrimaryKey(nameof(Id))]
[Index(nameof(BusinessFunctionText), IsUnique = true)]
[Table("test_entity_special_columns")]
public class TestEntityWithSpecialColumnNames : TestEntityBase
{
public int Id { get; set; }

/// <summary>
/// Column name with spaces and SQL reserved keyword "Function".
/// </summary>
[Column("Business Function Text")]
[MaxLength(255)]
public string BusinessFunctionText { get; set; } = string.Empty;

/// <summary>
/// Column name with SQL reserved keyword "Order".
/// </summary>
[Column("Order Number")]
public int OrderNumber { get; set; }

/// <summary>
/// Regular column name for comparison.
/// </summary>
[Column("description")]
[MaxLength(500)]
public string Description { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using FluentAssertions;

using PhenX.EntityFrameworkCore.BulkInsert.Enums;
using PhenX.EntityFrameworkCore.BulkInsert.Extensions;
using PhenX.EntityFrameworkCore.BulkInsert.Options;
using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer;
using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext;

Expand Down Expand Up @@ -42,4 +45,87 @@ public async Task InsertSmartEnumEntities(InsertStrategy strategy)
insertedEntities.Should().BeEquivalentTo(entities,
o => o.RespectingRuntimeTypes().Excluding(e => e.Id));
}

/// <summary>
/// Tests that column names with spaces and SQL reserved keywords are properly quoted.
/// This addresses the issue where columns like "Business Function Text" were not being
/// properly escaped, causing SQL syntax errors.
/// </summary>
[SkippableTheory]
[CombinatorialData]
public async Task InsertEntities_WithSpecialColumnNames(InsertStrategy strategy)
{
// Arrange
var entities = new List<TestEntityWithSpecialColumnNames>
{
new TestEntityWithSpecialColumnNames
{
TestRun = _run,
BusinessFunctionText = $"{_run}_BusinessFunction1",
OrderNumber = 100,
Description = "Test description 1"
},
new TestEntityWithSpecialColumnNames
{
TestRun = _run,
BusinessFunctionText = $"{_run}_BusinessFunction2",
OrderNumber = 200,
Description = "Test description 2"
}
};

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

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

/// <summary>
/// Tests that merge/upsert operations work correctly with column names containing
/// spaces and SQL reserved keywords.
/// </summary>
[SkippableTheory]
[CombinatorialData]
public async Task InsertEntities_WithSpecialColumnNames_Merge(InsertStrategy strategy)
{
Skip.If(_context.IsProvider(ProviderType.MySql, ProviderType.Oracle));

// Arrange
var entities = new List<TestEntityWithSpecialColumnNames>
{
new TestEntityWithSpecialColumnNames
{
TestRun = _run,
BusinessFunctionText = $"{_run}_BusinessFunction1",
OrderNumber = 100,
Description = "Initial description"
}
};

// First insert
await _context.InsertWithStrategyAsync(strategy, entities);

// Update entity for upsert
entities[0].OrderNumber = 200;
entities[0].Description = "Updated description";

// Act - Merge with update on conflict
var insertedEntities = await _context.InsertWithStrategyAsync(strategy, entities, _ => { },
onConflict: new OnConflictOptions<TestEntityWithSpecialColumnNames>
{
Match = e => new { e.BusinessFunctionText },
Update = (inserted, excluded) => new TestEntityWithSpecialColumnNames
{
OrderNumber = excluded.OrderNumber,
Description = excluded.Description
}
});

// Assert
insertedEntities.Should().HaveCount(1);
insertedEntities[0].OrderNumber.Should().Be(200);
insertedEntities[0].Description.Should().Be("Updated description");
}
}
Loading