基本上,我有一个使用 EF Core 的项目。为了在比较两个对象(协议类)是否相等时缩短 lambda,我重写了 Equals 方法并重载了 == 和 != 运算符。然而,LINQ似乎并不关心它,仍然使用引用来确定相等性。谢谢
正如我之前所说,我重写了 Equals 方法并重载了 == 和 != 运算符。没有运气。我还尝试过实现 IEquatable 接口。也没有运气。
我正在使用: EF Core 2.2.4
//协议类
[Key]
public int ProtocolId {get;set;}
public string Repnr {get;set;}
public string Service {get;set;}
public override bool Equals(object obj)
{
if (obj is Protocol other)
{
return this.Equals(other);
}
return false;
}
public override int GetHashCode()
{
return $"{Repnr}-{Service}".GetHashCode();
}
public bool Equals(Protocol other)
{
return this?.Repnr == other?.Repnr && this?.Service == other?.Service;
}
public static bool operator ==(Protocol lhs, Protocol rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(Protocol lhs, Protocol rhs)
{
return !lhs.Equals(rhs);
}
//问题
using (var db = new DbContext())
{
var item1 = new Protocol() { Repnr = "1666", Service = "180" };
db.Protocols.Add(item1 );
db.SaveChanges();
var item2 = new Protocol() { Repnr = "1666", Service = "180" };
var result1 = db.Protocols.FirstOrDefault(a => a == item2);
var result2 = db.Protocols.FirstOrDefault(a => a.Equals(item2));
//both result1 and result2 are null
}
我希望 result1 和 result2 都是 item1。然而,它们都是空的。我知道我可以只执行 a.Repnr == b.Repnr && a.Service == b.Service,但这并不那么干净。谢谢
要理解为什么使用不正确的相等比较器,您必须了解
IEnumerable<...>
和 IQueryable<...>
之间的区别。
实现
IEnumerable<...>
的对象是表示一系列相似对象的对象。它包含获取序列第一项的所有内容,一旦获得序列中的一项,只要有下一项,就可以获取下一项。
您可以通过调用
GetEnumerator()
并重复调用 MoveNext()
来开始显式枚举。更常见的是使用 foreach
或 LINQ 终止语句(如 ToList()
、ToDictionary()
、FirstOrDefault()
、Count()
或 Any()
)开始隐式枚举。这组 LINQ 方法在内部使用 foreach
或 GetEnumerator()
和 MoveNext()
/ Current
。
实现
IQueryable<...>
的对象也表示可枚举序列。然而,不同之处在于,这个序列通常不是由您的进程持有,而是由不同的进程持有,例如数据库管理系统。
IQueryable 并不(必然)包含要枚举的所有内容。相反,它包含一个
Expression
和一个 Provider
。 Expression
是关于必须查询的内容的通用描述。 Provider
知道哪个进程将执行查询(通常是数据库管理系统)以及如何与该进程通信(通常是类似 SQL 的东西)。
IQueryable<..>
还实现了 IEnumerable<..>
,因此您可以开始枚举序列,就像它是标准 IEnumerable 一样。一旦您通过(内部)调用 IQueryable<...>
开始枚举 GetEnumerator()
,Expression
就会发送到 Provider
,后者将 Expression
转换为 SQL 并执行查询。结果以枚举器的形式呈现,可以使用 MoveNext()
/ Current
进行枚举。
这意味着,如果您想枚举
IQueryable<...>
,则必须将 Expression
翻译成 Provider
支持的语言。由于编译器并不真正知道谁将执行查询,因此如果您的表达式包含您的提供程序不知道如何转换为 SQL 的方法或类,编译器不会抱怨。在这种情况下,您会收到运行时错误。
很容易看出,SQL 不知道您自己定义的
Equals
方法。事实上,甚至有几个标准的 LINQ 函数不受支持。请参阅支持和不支持的 LINQ 方法 (LINQ to Entities)。
那么如果我想使用不支持的功能该怎么办?
您可以做的事情之一是将数据移动到本地进程,然后调用不受支持的函数。
这可以使用
ToList
来完成,但如果您只使用一个或几个获取的项目,这将浪费处理能力。
数据库查询速度较慢的部分之一是将所选数据传输到本地进程。因此,明智的做法是将数据限制为您实际计划使用的数据。
更明智的解决方案是使用
AsEnumerable
。这将获取“每页”选定的数据。它将获取第一页,一旦您枚举了获取的页面(使用 MoveNext),它将获取下一页。
因此,如果您只使用了一些已获取的项目,那么您将获取一些未使用的项目,但至少您不会获取所有项目。
示例
假设您有一个本地函数,它接受
Student
作为输入并返回布尔值
bool HasSpecialAbility(Student student);
要求:给我三个居住在纽约市且具有特殊能力的学生。
唉,
HasSpecialAbility
是本地函数,无法翻译成Sql。在调用之前,您必须让学生进入您的本地流程。
var result = dbContext.Students
// limit the transported data as much as you can:
.Where(student => student.CityCode == "NYC")
// transport to local process per page:
.AsEnumerable()
// now you can call HasSpecialAbility:
.Where(student => HasSpecialAbility(student))
.Take(3)
.ToList();
好吧,您可能已经获取了 100 名学生的页面,而您只需要 3 个学生,但至少您还没有获取全部 25000 名学生。
<h1>şlşşşş[enter image description here][1]