如何让重载的 == 运算符与 LINQ 和 EF Core 一起使用?
How to get an overloaded == operator to work with LINQ and EF Core?
基本上,我有一个使用 EF Core 的项目。为了在比较两个对象(class 协议)是否相等时缩短我的 lambda,我重写了我的 Equals 方法并重载了 == 和 != 运算符。然而,LINQ 似乎并不关心它,仍然使用引用来判断是否相等。谢谢
正如我之前所说,我已经覆盖了 Equals 方法并重载了 == 和 != 运算符。没有运气。我也试过实现 IEquatable 接口。也不走运。
我正在使用:
EF 核心 2.2.4
//协议class
[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
实现 IEnumerable<...>
的对象是表示一系列相似对象的对象。它包含获取序列第一项的所有内容,一旦您获得序列中的一项,只要有下一项,就可以获取下一项。
您可以通过调用 GetEnumerator()
显式开始枚举并重复调用 MoveNext()
。更常见的是通过使用 foreach
或 LINQ 终止语句(如 ToList()
、ToDictionary()
、FirstOrDefault()
、Count()
或 Any()
开始隐式枚举。这组 LINQ 方法在内部使用 foreach
或 GetEnumerator()
和 MoveNext()
/ Current
.
可查询
实现IQueryable<...>
的对象也表示可枚举序列。然而不同的是,这个序列通常不是由您的进程持有,而是由不同的进程持有,比如数据库管理系统。
IQueryable 不会(必须)保留要枚举的所有内容。相反,它包含一个 Expression
和一个 Provider
。 Expression
是关于必须查询的内容的通用描述。 Provider
知道哪个进程将执行查询(通常是数据库管理系统)以及如何与该进程通信(通常是 SQL-like)。
IQueryable<..>
还实现了 IEnumerable<..>
,因此您可以开始枚举序列,就好像它是标准 IEnumerable 一样。一旦您通过(内部)调用 GetEnumerator()
开始枚举 IQueryable<...>
,Expression
将发送到 Provider
,后者将 Expression
转换为 SQL 并执行查询。结果显示为枚举器,可以使用 MoveNext()
/ Current
.
枚举
这意味着,如果要枚举 IQueryable<...>
,Expression
必须翻译成 Provider
支持的语言。由于编译器并不真正知道谁将执行查询,如果您的 Expression 包含方法或 类 而您的 Provider 不知道如何转换为 SQL,编译器也不会抱怨。在这种情况下,您会收到 run-time 错误。
很容易看出,SQL不知道你自己定义的Equals
方法。事实上,甚至有几个标准的 LINQ 函数不受支持。参见 Supported and Unsupported LINQ Methods (LINQ to Entities)。
那我想使用不支持的功能怎么办呢?
您可以做的事情之一是将数据移动到本地进程,然后调用不受支持的函数。
这可以使用 ToList
来完成,但如果您只使用一个或几个获取的项目,这将浪费处理能力。
One of the slower parts of a database query is the transport of the selected data to your local process. Hence it is wise to limit the data to the data that you actually plan to use.
更聪明的解决方案是使用 AsEnumerable
。这将“每页”获取选定的数据。它将获取第一页,一旦您枚举了获取的页面(使用 MoveNext),它将获取下一页。
因此,如果您只使用了一些获取的项目,您将获取一些未使用的项目,但至少您不会获取所有这些项目。
例子
假设您有一个局部函数,它接受一个 Student
作为输入,returns 一个布尔值
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 个学生。
基本上,我有一个使用 EF Core 的项目。为了在比较两个对象(class 协议)是否相等时缩短我的 lambda,我重写了我的 Equals 方法并重载了 == 和 != 运算符。然而,LINQ 似乎并不关心它,仍然使用引用来判断是否相等。谢谢
正如我之前所说,我已经覆盖了 Equals 方法并重载了 == 和 != 运算符。没有运气。我也试过实现 IEquatable 接口。也不走运。
我正在使用: EF 核心 2.2.4
//协议class
[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
实现 IEnumerable<...>
的对象是表示一系列相似对象的对象。它包含获取序列第一项的所有内容,一旦您获得序列中的一项,只要有下一项,就可以获取下一项。
您可以通过调用 GetEnumerator()
显式开始枚举并重复调用 MoveNext()
。更常见的是通过使用 foreach
或 LINQ 终止语句(如 ToList()
、ToDictionary()
、FirstOrDefault()
、Count()
或 Any()
开始隐式枚举。这组 LINQ 方法在内部使用 foreach
或 GetEnumerator()
和 MoveNext()
/ Current
.
可查询
实现IQueryable<...>
的对象也表示可枚举序列。然而不同的是,这个序列通常不是由您的进程持有,而是由不同的进程持有,比如数据库管理系统。
IQueryable 不会(必须)保留要枚举的所有内容。相反,它包含一个 Expression
和一个 Provider
。 Expression
是关于必须查询的内容的通用描述。 Provider
知道哪个进程将执行查询(通常是数据库管理系统)以及如何与该进程通信(通常是 SQL-like)。
IQueryable<..>
还实现了 IEnumerable<..>
,因此您可以开始枚举序列,就好像它是标准 IEnumerable 一样。一旦您通过(内部)调用 GetEnumerator()
开始枚举 IQueryable<...>
,Expression
将发送到 Provider
,后者将 Expression
转换为 SQL 并执行查询。结果显示为枚举器,可以使用 MoveNext()
/ Current
.
这意味着,如果要枚举 IQueryable<...>
,Expression
必须翻译成 Provider
支持的语言。由于编译器并不真正知道谁将执行查询,如果您的 Expression 包含方法或 类 而您的 Provider 不知道如何转换为 SQL,编译器也不会抱怨。在这种情况下,您会收到 run-time 错误。
很容易看出,SQL不知道你自己定义的Equals
方法。事实上,甚至有几个标准的 LINQ 函数不受支持。参见 Supported and Unsupported LINQ Methods (LINQ to Entities)。
那我想使用不支持的功能怎么办呢?
您可以做的事情之一是将数据移动到本地进程,然后调用不受支持的函数。
这可以使用 ToList
来完成,但如果您只使用一个或几个获取的项目,这将浪费处理能力。
One of the slower parts of a database query is the transport of the selected data to your local process. Hence it is wise to limit the data to the data that you actually plan to use.
更聪明的解决方案是使用 AsEnumerable
。这将“每页”获取选定的数据。它将获取第一页,一旦您枚举了获取的页面(使用 MoveNext),它将获取下一页。
因此,如果您只使用了一些获取的项目,您将获取一些未使用的项目,但至少您不会获取所有这些项目。
例子
假设您有一个局部函数,它接受一个 Student
作为输入,returns 一个布尔值
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 个学生。