gpt4 book ai didi

c# - 这个请求是由 EF Core buggy 生成的还是我的代码?

转载 作者:行者123 更新时间:2023-12-03 22:58:52 25 4
gpt4 key购买 nike

介绍
我将 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)
//...
}


功能
预期记录数
来自数据库的记录
使用LINQ时的记录


查询1
3
3
3

查询2
3
2
3

查询 3
3
3
3


为什么第二个数据库查询只返回2条记录?
我知道第一个和第三个查询比较 DateTimeDateTime而第二个比较 DateTimeDateTimeOffset .因此,我想知道为什么 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'
地雷
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();
}
}
}

最佳答案

第二个和其他两个 LINQ 查询之间的区别(因此不同的翻译 - CAST ... as datetimeoffset)是其他人使用 DateTime比较,而这使用 DateTimeOffset比较。因为当start类型是 DateTimeOffset , 表达方式

foo.Moment.Date >= start
通过 CLR 类型系统规则(适用于 C# 生成的表达式树)实际上是
((DateTimeOffset)foo.Moment.Date) >= start
foo.Moment.Date <= end 相同.
现在来了 DateTime的区别至 DateTimeOffset两个执行上下文之间的转换 - 客户端(CLR)和服务器(在本例中为 SqlServer):
CLR

This method is equivalent to the DateTimeOffset constructor. The offset of the resulting DateTimeOffset object depends on the value of the DateTime.Kind property of the dateTime parameter:

  • If the value of the DateTime.Kind property is DateTimeKind.Utc, the date and time of the DateTimeOffset object is set equal to dateTime, and its Offset property is set equal to 0.

  • If the value of the DateTime.Kind property is DateTimeKind.Local or DateTimeKind.Unspecified, the date and time of the DateTimeOffset object is set equal to dateTime, and its Offset property is set equal to the offset of the local system's current time zone.


SqlServer

For conversion to datetimeoffset(n), date is copied, and the time is set to 00:00.0000000 +00:00


现在您应该清楚地看到差异了。 CLR Date属性返回 DateTimeInspecified kind,然后转换回 DateTimeOffset使用您本地系统的当前时区偏移量 - 根据您初始化变量的方式,很可能是 +2。而在 SQL 查询中,列值转换为 0 偏移量。从 DateTimeOffset比较使用所有成员(包括偏移量),对于某些数据值,您可以获得不同的结果。
当然,在 LINQ to Objects 上下文中运行相同的代码时,它只是使用 CLR 规则在本地编译和执行,因此没有区别。
简而言之,永远不要依赖非 LINQ to Objects 查询中的转换。使用第一个查询中的显式运算符/方法,或使用第三个查询中的正确数据类型变量,以确保您比较相同的数据类型而没有隐藏和非确定性转换。

关于c# - 这个请求是由 EF Core buggy 生成的还是我的代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67800947/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com