Skip to content

Commit 86fcdf7

Browse files
CopilotasmyasnikovKirillKurdyukov
authored
Fix DateTime.UtcNow generating invalid SQL in ExecuteUpdate (#568)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: asmyasnikov <[email protected]> Co-authored-by: KirillKurdyukov <[email protected]>
1 parent b295c02 commit 86fcdf7

File tree

4 files changed

+105
-18
lines changed

4 files changed

+105
-18
lines changed

src/EFCore.Ydb/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
- Fixed bug: DateTime.UtcNow generating invalid SQL in ExecuteUpdate ([#555](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/555)).
2+
13
## v0.2.0
24

35
- Fixed bug: Missing/removed __EFMigrationsHistory caused INSERT errors (LockMigration); migrations now handle table recreation and locking properly ([#544](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/544)).

src/EFCore.Ydb/src/Query/Internal/Translators/YdbDateTimeMemberTranslator.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010

1111
namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;
1212

13-
public class YdbDateTimeMemberTranslator(
13+
public sealed class YdbDateTimeMemberTranslator(
1414
IRelationalTypeMappingSource typeMappingSource,
1515
YdbSqlExpressionFactory sqlExpressionFactory)
1616
: IMemberTranslator
1717
{
18-
public virtual SqlExpression? Translate(
18+
public SqlExpression? Translate(
1919
SqlExpression? instance,
2020
MemberInfo member,
2121
Type returnType,
@@ -58,8 +58,14 @@ public class YdbDateTimeMemberTranslator(
5858
// nameof(DateTime.Now) => ???,
5959
// nameof(DateTime.Today) => ???
6060

61-
nameof(DateTime.UtcNow) => UtcNow(),
62-
61+
nameof(DateTime.UtcNow) => sqlExpressionFactory.Function(
62+
"CurrentUtcTimestamp",
63+
[],
64+
nullable: false,
65+
argumentsPropagateNullability: ArrayUtil.TrueArrays[0],
66+
returnType,
67+
typeMappingSource.FindMapping(returnType)
68+
),
6369
nameof(DateTime.Year) => DatePart(instance!, "GetYear"),
6470
nameof(DateTime.Month) => DatePart(instance!, "GetMonth"),
6571
nameof(DateTime.Day) => DatePart(instance!, "GetDayOfMonth"),
@@ -75,18 +81,6 @@ public class YdbDateTimeMemberTranslator(
7581
nameof(DateTime.Ticks) => null,
7682
_ => null
7783
};
78-
79-
SqlExpression UtcNow()
80-
{
81-
return sqlExpressionFactory.Function(
82-
"CurrentUtc" + returnType.Name == "DateOnly" ? "Date" : returnType.Name,
83-
[],
84-
nullable: false,
85-
argumentsPropagateNullability: ArrayUtil.TrueArrays[0],
86-
returnType,
87-
typeMappingSource.FindMapping(returnType)
88-
);
89-
}
9084
}
9185

9286
private SqlExpression DatePart(SqlExpression instance, string partName)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using EntityFrameworkCore.Ydb.Extensions;
2+
using EntityFrameworkCore.Ydb.FunctionalTests.TestUtilities;
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.TestUtilities;
5+
using Xunit;
6+
7+
namespace EntityFrameworkCore.Ydb.FunctionalTests.BulkUpdates;
8+
9+
public class DateTimeExecuteUpdateYdbTest
10+
{
11+
[Fact]
12+
public async Task ExecuteUpdate_with_DateTime_UtcNow()
13+
{
14+
await using var testStore = YdbTestStoreFactory.Instance.Create("DateTimeExecuteUpdateYdbTest");
15+
using var sqlLoggerFactory = YdbTestStoreFactory.Instance.CreateListLoggerFactory(_ => false);
16+
await using var context = new TestContext(sqlLoggerFactory);
17+
await testStore.CleanAsync(context);
18+
await context.Database.EnsureCreatedAsync();
19+
20+
context.Entities.Add(new TestEntity { Id = 1, Name = "Test", UpdatedAt = DateTime.UtcNow });
21+
await context.SaveChangesAsync();
22+
23+
await context.Entities
24+
.Where(e => e.Id == 1)
25+
.ExecuteUpdateAsync(s => s.SetProperty(e => e.UpdatedAt, DateTime.UtcNow));
26+
27+
((TestSqlLoggerFactory)sqlLoggerFactory).AssertBaseline([
28+
"""
29+
UPDATE `TestEntities`
30+
SET `UpdatedAt` = CurrentUtcTimestamp()
31+
WHERE `Id` = 1
32+
"""
33+
], false);
34+
}
35+
36+
[Fact]
37+
public async Task ExecuteUpdate_with_constant_DateTime()
38+
{
39+
await using var testStore = YdbTestStoreFactory.Instance.Create("DateTimeExecuteUpdateYdbTest");
40+
using var sqlLoggerFactory = YdbTestStoreFactory.Instance.CreateListLoggerFactory(_ => false);
41+
await using var context = new TestContext(sqlLoggerFactory);
42+
await testStore.CleanAsync(context);
43+
await context.Database.EnsureCreatedAsync();
44+
45+
context.Entities.Add(new TestEntity { Id = 1, Name = "Test", UpdatedAt = DateTime.UtcNow });
46+
await context.SaveChangesAsync();
47+
48+
var testDate = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
49+
50+
await context.Entities
51+
.Where(e => e.Id == 1)
52+
.ExecuteUpdateAsync(s => s.SetProperty(e => e.UpdatedAt, testDate));
53+
((TestSqlLoggerFactory)sqlLoggerFactory).AssertBaseline([
54+
"""
55+
$__testDate_0='?' (DbType = Object)
56+
57+
UPDATE `TestEntities`
58+
SET `UpdatedAt` = @__testDate_0
59+
WHERE `Id` = 1
60+
"""
61+
], false);
62+
63+
var entity = await context.Entities.AsNoTracking().FirstOrDefaultAsync(e => e.Id == 1);
64+
Assert.NotNull(entity);
65+
Assert.Equal(testDate, entity.UpdatedAt);
66+
}
67+
68+
public class TestEntity
69+
{
70+
public int Id { get; set; }
71+
public string Name { get; set; } = "";
72+
public DateTime UpdatedAt { get; set; }
73+
}
74+
75+
public class TestContext(ListLoggerFactory sqlLoggerFactory) : DbContext
76+
{
77+
public DbSet<TestEntity> Entities => Set<TestEntity>();
78+
79+
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
80+
modelBuilder.Entity<TestEntity>(b =>
81+
{
82+
b.ToTable("TestEntities");
83+
b.HasKey(e => e.Id);
84+
b.Property(e => e.UpdatedAt).HasColumnType("Timestamp");
85+
});
86+
87+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
88+
.UseYdb("Host=localhost;Port=2136")
89+
.UseLoggerFactory(sqlLoggerFactory)
90+
.EnableServiceProviderCaching(false);
91+
}
92+
}

src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/InsertWithDefaultsTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
8383
.UseYdb("Host=localhost;Port=2136", builder => builder
8484
.DisableRetryOnFailure()
8585
.MigrationsAssembly(typeof(TestEntityMigration).Assembly.FullName))
86-
.EnableServiceProviderCaching(false)
87-
.LogTo(Console.WriteLine);
86+
.EnableServiceProviderCaching(false);
8887
}
8988

9089
public class TestEntity

0 commit comments

Comments
 (0)