【发布时间】:2021-08-20 08:47:18
【问题描述】:
简介
我将 EF Core 与 .NET 5.0 和 SQL Server Express 结合使用。基本上我想知道它是否生成了一个错误的 SQL 查询,或者我的代码是否有错误(可能:D)。我在问题的底部提供了一个 mre,但希望问题从我收集的数据中变得明显(我已经问过一个类似的问题,但觉得它需要彻底检修)
设置
我有一个记录和一个DbContext,如下所示。它被简化为重要的属性Moment,必须为DateTimeOffset 类型(公司准则)。
private class Foo
{
public int ID { get; set; }
public DateTimeOffset Moment { get; set; }
}
private class Context : DbContext
{
public Context(DbContextOptions<Context> options) : base(options) {}
public DbSet<Foo> Foos { get; set; }
}
生成的数据库中相应列的数据类型为datetimeoffset(7),看起来不错。
我用这些数据初始化了数据库(连续 5 天,每天在各自时区的午夜):
context.Foos.Add(new Foo() { Moment = DateTimeOffset.Parse("2021-04-21 00:00 +02:00"), });
context.Foos.Add(new Foo() { Moment = DateTimeOffset.Parse("2021-04-22 00:00 +02:00"), });
context.Foos.Add(new Foo() { Moment = DateTimeOffset.Parse("2021-04-23 00:00 +02:00"), });
context.Foos.Add(new Foo() { Moment = DateTimeOffset.Parse("2021-04-24 00:00 +02:00"), });
context.Foos.Add(new Foo() { Moment = DateTimeOffset.Parse("2021-04-25 00:00 +02:00"), });
现在我想查询所有记录Moment >= start && Moment <= end,同时忽略这些参数的时间:
var start = DateTimeOffset.Parse("2021-04-22 00:00 +02:00");
var end = DateTimeOffset.Parse("2021-04-24 00:00 +02:00");
我希望得到 3 条记录,并提出了 3 个与我看起来相同的查询,但是,第二个产生了不同的结果:
查询
private static async Task Query1(Context context, DateTimeOffset start, DateTimeOffset end)
{
var records = await context.Foos
.Where(foo => foo.Moment.Date >= start.Date && foo.Moment.Date <= end.Date)
//... Finds 3 records, expected
}
private static async Task Query2(Context context, DateTimeOffset start, DateTimeOffset end)
{
start = start.Date; // .Date yields DateTime -> implicit conversion to DateTimeOffset?
end = end.Date;
var records = await context.Foos
.Where(foo => foo.Moment.Date >= start && foo.Moment.Date <= end)
// ... Finds only 2 records, unexpected
}
private static async Task Query3(Context context, DateTimeOffset start, DateTimeOffset end)
{
var start2 = start.Date; // start2 and end2 are of type DateTime now
var end2 = end.Date;
var records = await context.Foos
.Where(foo => foo.Moment.Date >= start2 && foo.Moment.Date <= end2)
// ... Finds 3 records, expected
}
结果
我还为每个查询创建了一个 LINQ 版本,我从 List<Foo> 查询数据:
private static void Query1(List<Foo> foos, DateTimeOffset start, DateTimeOffset end)
{
var records = foos
.Where(foo => foo.Moment.Date >= start.Date && foo.Moment.Date <= end.Date)
//...
}
| Function | Expected number of records | Records from DB | Records when using LINQ |
|---|---|---|---|
| Query1 | 3 | 3 | 3 |
| Query2 | 3 | 2 | 3 |
| Query3 | 3 | 3 | 3 |
为什么第二个数据库查询只返回2条记录?
我知道第一个和第三个查询将DateTime 与DateTime 进行比较,而第二个查询将DateTime 与DateTimeOffset 进行比较。因此,我想知道为什么 LINQ 版本的行为不同。
跟踪查询
我跟踪了发送到 SQL Server 的实际查询,它们是不同的,但是,我真的不明白为什么它们会导致不同的结果(没有太多 SQL 经验):
-- From Query1()
exec sp_executesql N'SELECT [f].[ID]
FROM [Foos] AS [f]
WHERE (CONVERT(date, [f].[Moment]) >= @__start_Date_0) AND (CONVERT(date, [f].[Moment]) <= @__end_Date_1)',N'@__start_Date_0 datetime2(7),@__end_Date_1 datetime2(7)',@__start_Date_0='2021-04-22 00:00:00',@__end_Date_1='2021-04-24 00:00:00'
-- From Query2()
exec sp_executesql N'SELECT [f].[ID]
FROM [Foos] AS [f]
WHERE (CAST(CONVERT(date, [f].[Moment]) AS datetimeoffset) >= @__start_0) AND (CAST(CONVERT(date, [f].[Moment]) AS datetimeoffset) <= @__end_1)',N'@__start_0 datetimeoffset(7),@__end_1 datetimeoffset(7)',@__start_0='2021-04-22 00:00:00 +02:00',@__end_1='2021-04-24 00:00:00 +02:00'
-- From Query3()
exec sp_executesql N'SELECT [f].[ID]
FROM [Foos] AS [f]
WHERE (CONVERT(date, [f].[Moment]) >= @__start2_0) AND (CONVERT(date, [f].[Moment]) <= @__end2_1)',N'@__start2_0 datetime2(7),@__end2_1 datetime2(7)',@__start2_0='2021-04-22 00:00:00',@__end2_1='2021-04-24 00:00:00'
MRE
使用Microsoft.EntityFrameworkCore 和Microsoft.EntityFrameworkCore.SqlServer 版本5.0.6 测试。
namespace Playground
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
public static class Program
{
private class Foo
{
public int ID { get; set; }
public DateTimeOffset Moment { get; set; }
}
private static Context CreateContext()
{
var connectionString = $"Data Source=.\\SQLEXPRESS;Initial Catalog=FOO_DB;Integrated Security=SSPI";
var optionsBuilder = new DbContextOptionsBuilder<Context>();
optionsBuilder.UseSqlServer(connectionString).EnableSensitiveDataLogging();
var context = new Context(optionsBuilder.Options);
context.Database.EnsureCreated();
return context;
}
private class Context : DbContext
{
public Context(DbContextOptions<Context> options) : base(options) { }
public DbSet<Foo> Foos { get; set; }
}
private static async Task Query1(Context context, DateTimeOffset start, DateTimeOffset end)
{
var records = await context.Foos
.Where(foo => foo.Moment.Date >= start.Date && foo.Moment.Date <= end.Date)
.Select(foo => foo.ID)
.ToListAsync();
Console.WriteLine($"Query1 in DB found {records.Count} records");
}
private static async Task Query2(Context context, DateTimeOffset start, DateTimeOffset end)
{
start = start.Date;
end = end.Date;
var records = await context.Foos
.Where(foo => foo.Moment.Date >= start && foo.Moment.Date <= end)
.Select(foo => foo.ID)
.ToListAsync();
Console.WriteLine($"Query2 in DB found {records.Count} records");
}
private static async Task Query3(Context context, DateTimeOffset start, DateTimeOffset end)
{
var start2 = start.Date;
var end2 = end.Date;
var records = await context.Foos
.Where(foo => foo.Moment.Date >= start2 && foo.Moment.Date <= end2)
.Select(foo => foo.ID)
.ToListAsync();
Console.WriteLine($"Query3 in DB found {records.Count} records");
}
public async static Task Main()
{
var context = CreateContext();
var foos = new List<Foo>() {
new Foo() { Moment = DateTimeOffset.Parse("2021-04-21 00:00 +02:00"), },
new Foo() { Moment = DateTimeOffset.Parse("2021-04-22 00:00 +02:00"), },
new Foo() { Moment = DateTimeOffset.Parse("2021-04-23 00:00 +02:00"), },
new Foo() { Moment = DateTimeOffset.Parse("2021-04-24 00:00 +02:00"), },
new Foo() { Moment = DateTimeOffset.Parse("2021-04-25 00:00 +02:00"), },
};
if (!context.Foos.Any())
{
await context.AddRangeAsync(foos);
}
await context.SaveChangesAsync();
var start = DateTimeOffset.Parse("2021-04-22 00:00 +02:00");
var end = DateTimeOffset.Parse("2021-04-24 00:00 +02:00");
await Query1(context, start, end);
await Query2(context, start, end);
await Query3(context, start, end);
context.Dispose();
}
}
}
【问题讨论】:
-
TBH 在我看来很合适。
-
看起来如果您尝试重新定义两个参数但它不起作用。你应该得到一个错误。不允许在方法中重新定义参数。代码忽略了更改: start = start.Date;和结束=结束。日期;所以你的编译器选项设置为忽略警告而不是报告警告。
-
@jdweng 这是一个赋值,没有重新定义(警告被视为警告级别 5 的错误)。
.Date生成DateTime,然后将其转换为DateTimeOffset。 (应该提到我使用 .NET 5)你是什么意思 “代码忽略了更改:start = start.Date; and end = end.Date;”? -
除非参数定义为 out 或 ref,否则不应更改方法内的参数值。
-
@jdweng 请提供来源。重新分配变量是非常基本的事情,不会造成麻烦。
标签: c# sql-server datetime entity-framework-core datetimeoffset