EF Core在这种情况下生成的SQL语句相当低效,有什么解决方法吗?

4

考虑这样的简单实体:

public class Person
{
    public int Id { get; set; }
    public string MainEmail { get; set; }
}

public class Person_Email
{
    public int Id { get; set; }
    public int Id_Person { get; set; }
    public string Email { get; set; }
} 

这个查询:

db.Person.Where(p => p.Person_Email.Any(c => c.Email == "myemail@email.com") || p.MainEmail == "myemail@email.com").FirstOrDefault();

被翻译成:

SELECT TOP(1) [p].[Id]
FROM [Person] AS [p]

WHERE (EXISTS (
    SELECT 1
    FROM [Person_Email] AS [p1]
    WHERE ([p].[Id] = [p1].[Id_Person]) AND ([p1].[Email] = N'myemail@email.com')) 
    OR ([p].[MainEmail] = N'myemail@email.com')
    )

这个很慢,在我的数据库中分析器显示有4347316次读取,用时为2568秒。

我会把SQL写成:

SELECT TOP(1) [p].[Id]
FROM [Person] AS [p]

join [Person_Email] as pe on p.Id = pe.Id_Person
where p.MainEmail = N'myemail@email.com' or pe.Email = N'myemail@email.com'

在这种情况下,分析器显示读取次数为17448,持续时间为300。
我想知道是否有一种不同的方式来优化编写LINQ查询的方法,或者我们只能等待ef core团队对其进行改进(我尝试过ef core 5.0预览版8,但没有任何改变)。

如果你有正确的索引,第一个查询就不应该很慢。 - Gert Arnold
编写 LINQ 以获得所需的 SQL:https://www.dotnettricks.com/learn/linq/sql-joins-with-csharp-linq - Seabizkit
@GertArnold,那是我看过的第一件事,但是我在Person.MainEmail和Person_Email.Email上都有一个NONCLUSTERED INDEX,以及在Person_Email.Id_Person上有一个CLUSTERED INDEX。我还需要添加什么?此外,该应用程序已经在SQL Azure上长时间运行,并且其ML从未建议在那里添加另一个索引。 - Pietro
1个回答

1
这个怎么样?
var person = (from a in db.Person
              from b in db.Person_Email.Where(c => c.Id_Person == a.Id).DefaultIfEmpty()
              where a.MainEmail == "myemail@email.com" || b.Email == "myemail@email.com"
              select a).FirstOrDefault();

或者

var personemail = db.Person_Email.Where(c => c.Email == "myemail@email.com").FirstOrDefault();

var person = db.Person.Where(c => (personemail == null || c.Id == personemail.Id_Person) || c.MainEmail == "myemail@email.com").FirstOrDefault();

enter image description here


获取第一条记录。或者相当于Single()。有什么问题吗? - Asherguru
添加了截图。速度提升3倍。 - Asherguru
实际上,第一个LINQ查询接近于OP的要求。只需使用导航属性自动内连接替换手动左外连接from b in db.Person_Email.Where(c => c.Id_Person == a.Id).DefaultIfEmpty(),您就可以获得所需的转换。它是否更有效是另一回事 - 在我看来,现在的CBO应该将EXISTS视为JOIN,但这是OP想要的。 - Ivan Stoev
谢谢@Asherguru,但这并不能改善情况。我建议使用Profiler,Stopwatch在这种情况下效果不佳。 - Pietro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接