diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypes.Tests.csproj b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypes.Tests.csproj
index db7467f52e..bb4f594afa 100644
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypes.Tests.csproj
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypes.Tests.csproj
@@ -1,37 +1,37 @@
- net7.0
+ net10.0
enable
false
-
-
+
+
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypesTests.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypesTests.cs
index 38a517fc82..8dc3382d1d 100644
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypesTests.cs
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/DateOnlyAndTimeOnlyTypesTests.cs
@@ -1,45 +1,90 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using System;
+using System.Linq;
using Xunit;
namespace DateOnlyAndTimeOnlyTypes.Tests;
public class DateOnlyAndTimeOnlyTypesTest
{
- private readonly AppDbContext _context;
-
- public DateOnlyAndTimeOnlyTypesTest()
+ private static AppDbContext CreateSqlServerContext()
{
- var contextOptions = new DbContextOptionsBuilder()
- .UseInMemoryDatabase("DateOnlyDemo")
- .ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
+ // No connection is opened: these tests only build the model and generate SQL.
+ var options = new DbContextOptionsBuilder()
+ .UseSqlServer("Server=.;Database=DateOnlyDemo;Trusted_Connection=True;TrustServerCertificate=True")
.Options;
- _context = new AppDbContext(contextOptions);
- _context.Database.EnsureDeleted();
- _context.Database.EnsureCreated();
+ return new AppDbContext(options);
}
[Fact]
- public void GivenDbContext_WhenHavingProperConverters_ThenRightlyStoresDateOnlyTimeOnlyValues()
+ public void GivenDbContext_WhenSavingDateOnlyTimeOnlyValues_ThenReadsThemBack()
{
- var employee = new Employee
- {
- Name = "John Doe",
- BirthDate = new DateOnly(2023, 5, 9),
- WorkStartTime = new TimeOnly(8, 30),
- WorkEndTime = new TimeOnly(4, 30)
+ var contextOptions = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase("DateOnlyDemo")
+ .ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
+ .Options;
+
+ using var context = new AppDbContext(contextOptions);
+ context.Database.EnsureDeleted();
+ context.Database.EnsureCreated();
+
+ var employee = new Employee
+ {
+ Name = "John Doe",
+ BirthDate = new DateOnly(2023, 5, 9),
+ WorkStartTime = new TimeOnly(8, 30),
+ WorkEndTime = new TimeOnly(16, 30)
};
- _context.Employees.Add(employee);
- _context.SaveChanges();
+ context.Employees.Add(employee);
+ context.SaveChanges();
- var storedEmployee = _context.Employees.Find(employee.Id);
+ var storedEmployee = context.Employees.Find(employee.Id);
Assert.NotNull(storedEmployee);
Assert.Equal(employee.BirthDate, storedEmployee!.BirthDate);
Assert.Equal(employee.WorkStartTime, storedEmployee.WorkStartTime);
Assert.Equal(employee.WorkEndTime, storedEmployee.WorkEndTime);
}
-}
\ No newline at end of file
+
+ [Fact]
+ public void GivenSqlServerProvider_WhenGeneratingSchema_ThenMapsDateOnlyToDateAndTimeOnlyToTime()
+ {
+ using var context = CreateSqlServerContext();
+
+ var script = context.Database.GenerateCreateScript();
+
+ Assert.Contains("[BirthDate] date NOT NULL", script);
+ Assert.Contains("[WorkStartTime] time NOT NULL", script);
+ Assert.Contains("[WorkEndTime] time NOT NULL", script);
+ }
+
+ [Fact]
+ public void GivenDateOnlyMember_WhenFiltering_ThenTranslatesToSql()
+ {
+ using var context = CreateSqlServerContext();
+
+ var sql = context.Employees
+ .Where(e => e.BirthDate.Year == 1990 && e.WorkStartTime.Hour < 9)
+ .ToQueryString();
+
+ Assert.Contains("DATEPART(year, [e].[BirthDate]) = 1990", sql);
+ Assert.Contains("DATEPART(hour, [e].[WorkStartTime]) < 9", sql);
+ }
+
+ [Fact]
+ public void GivenLegacyDatetime2Column_WhenUsingConverter_ThenKeepsDatetime2()
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseSqlServer("Server=.;Database=LegacyDemo;Trusted_Connection=True;TrustServerCertificate=True")
+ .Options;
+
+ using var context = new LegacyAppDbContext(options);
+
+ var script = context.Database.GenerateCreateScript();
+
+ Assert.Contains("[BirthDate] datetime2 NOT NULL", script);
+ }
+}
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/LegacyAppDbContext.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/LegacyAppDbContext.cs
new file mode 100644
index 0000000000..c23dec0025
--- /dev/null
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.Tests/LegacyAppDbContext.cs
@@ -0,0 +1,19 @@
+using System;
+using Microsoft.EntityFrameworkCore;
+
+namespace DateOnlyAndTimeOnlyTypes.Tests;
+
+// An existing database keeps BirthDate in a datetime2 column that we can't change.
+public class LegacyAppDbContext(DbContextOptions options) : DbContext(options)
+{
+ public DbSet Employees => Set();
+
+ protected override void ConfigureConventions(ModelConfigurationBuilder builder)
+ {
+ base.ConfigureConventions(builder);
+
+ builder.Properties()
+ .HaveConversion()
+ .HaveColumnType("datetime2");
+ }
+}
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/AppDbContext.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/AppDbContext.cs
index 2947be63cc..9ebaec252e 100644
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/AppDbContext.cs
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/AppDbContext.cs
@@ -1,34 +1,24 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-
-namespace DateOnlyAndTimeOnlyTypes;
-
-public class AppDbContext : DbContext
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+
+namespace DateOnlyAndTimeOnlyTypes;
+
+public class AppDbContext : DbContext
{
- public DbSet Employees => Set();
-
- protected override void OnConfiguring(DbContextOptionsBuilder builder)
- {
- if (builder.IsConfigured)
- return;
-
- var configuration = new ConfigurationBuilder()
- .AddJsonFile("appsettings.json")
- .Build();
- builder.UseSqlServer(configuration.GetConnectionString("Default"));
- }
-
- protected override void ConfigureConventions(ModelConfigurationBuilder builder)
+ public DbSet Employees => Set();
+
+ protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
- base.ConfigureConventions(builder);
+ if (builder.IsConfigured)
+ return;
+
+ var configuration = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
+ builder.UseSqlServer(configuration.GetConnectionString("Default"));
+ }
- builder.Properties()
- .HaveConversion()
- .HaveColumnType("date");
+ public AppDbContext() { }
- builder.Properties()
- .HaveConversion();
- }
-
- public AppDbContext(DbContextOptions options) : base(options) { }
-}
+ public AppDbContext(DbContextOptions options) : base(options) { }
+}
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.csproj b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.csproj
index 9fa0bf4ae3..689ce6ef29 100644
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.csproj
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes.csproj
@@ -2,34 +2,35 @@
Library
- net7.0
+ net10.0
enable
enable
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
-
-
+
+
-
-
- PreserveNewest
-
+
+
+ PreserveNewest
+
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyComparer.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyComparer.cs
deleted file mode 100644
index 791ce6240b..0000000000
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/DateOnlyComparer.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.EntityFrameworkCore.ChangeTracking;
-
-namespace DateOnlyAndTimeOnlyTypes;
-
-public class DateOnlyComparer : ValueComparer
-{
- public DateOnlyComparer() : base(
- (x, y) => x.DayNumber == y.DayNumber,
- dateOnly => dateOnly.GetHashCode())
- { }
-}
\ No newline at end of file
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/20261017201029_RemoveDateOnlyTimeOnlyConverters.Designer.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/20261017201029_RemoveDateOnlyTimeOnlyConverters.Designer.cs
new file mode 100644
index 0000000000..6bfb82f213
--- /dev/null
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/20261017201029_RemoveDateOnlyTimeOnlyConverters.Designer.cs
@@ -0,0 +1,56 @@
+//
+using System;
+using DateOnlyAndTimeOnlyTypes;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace DateOnlyAndTimeOnlyTypes.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20261017201029_RemoveDateOnlyTimeOnlyConverters")]
+ partial class RemoveDateOnlyTimeOnlyConverters
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("DateOnlyAndTimeOnlyTypes.Employee", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("BirthDate")
+ .HasColumnType("date");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("WorkEndTime")
+ .HasColumnType("time");
+
+ b.Property("WorkStartTime")
+ .HasColumnType("time");
+
+ b.HasKey("Id");
+
+ b.ToTable("Employees");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/20261017201029_RemoveDateOnlyTimeOnlyConverters.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/20261017201029_RemoveDateOnlyTimeOnlyConverters.cs
new file mode 100644
index 0000000000..7fa13b299b
--- /dev/null
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/20261017201029_RemoveDateOnlyTimeOnlyConverters.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DateOnlyAndTimeOnlyTypes.Migrations
+{
+ ///
+ public partial class RemoveDateOnlyTimeOnlyConverters : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/AppDbContextModelSnapshot.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/AppDbContextModelSnapshot.cs
index 53a1d61cb9..abac12b99c 100644
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/AppDbContextModelSnapshot.cs
+++ b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/Migrations/AppDbContextModelSnapshot.cs
@@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "7.0.0")
+ .HasAnnotation("ProductVersion", "10.0.13")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -30,17 +30,17 @@ protected override void BuildModel(ModelBuilder modelBuilder)
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
- b.Property("BirthDate")
+ b.Property("BirthDate")
.HasColumnType("date");
b.Property("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
- b.Property("WorkEndTime")
+ b.Property("WorkEndTime")
.HasColumnType("time");
- b.Property("WorkStartTime")
+ b.Property("WorkStartTime")
.HasColumnType("time");
b.HasKey("Id");
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/TimeOnlyComparer.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/TimeOnlyComparer.cs
deleted file mode 100644
index 4937209447..0000000000
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/TimeOnlyComparer.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.EntityFrameworkCore.ChangeTracking;
-
-namespace DateOnlyAndTimeOnlyTypes;
-
-public class TimeOnlyComparer : ValueComparer
-{
- public TimeOnlyComparer() : base(
- (x, y) => x.Ticks == y.Ticks,
- timeOnly => timeOnly.GetHashCode())
- { }
-}
\ No newline at end of file
diff --git a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/TimeOnlyConverter.cs b/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/TimeOnlyConverter.cs
deleted file mode 100644
index d967f50cb5..0000000000
--- a/dotnet-efcore/DateOnlyAndTimeOnlyTypes/DateOnlyAndTimeOnlyTypes/TimeOnlyConverter.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-
-namespace DateOnlyAndTimeOnlyTypes;
-
-public class TimeOnlyConverter : ValueConverter
-{
- public TimeOnlyConverter() : base(
- timeOnly => timeOnly.ToTimeSpan(),
- timeSpan => TimeOnly.FromTimeSpan(timeSpan))
- { }
-}
\ No newline at end of file