Mic*_*viš 0 c# linq equals operator-overloading entity-framework-core
基本上,我有一个使用 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);
}
Run Code Online (Sandbox Code Playgroud)
//问题
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
}
Run Code Online (Sandbox Code Playgroud)
我希望 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 并不(必然)包含要枚举的所有内容。相反,它包含 anExpression和 a Provider。这Expression是关于必须查询的内容的一般描述。知道Provider哪个进程将执行查询(通常是数据库管理系统)以及如何与该进程通信(通常是类似 SQL 的东西)。
AnIQueryable<..>还实现了IEnumerable<..>,因此您可以开始枚举序列,就像它是标准 IEnumerable 一样。一旦您开始IQueryable<...>通过调用(内部)枚举 an GetEnumerator(), 就会Expression将 发送到Provider,后者将 转换Expression为 SQL 并执行查询。结果以枚举器的形式呈现,可以使用MoveNext()/进行枚举Current。
这意味着,如果您想枚举 an IQueryable<...>,则Expression必须将 翻译成支持的语言Provider。由于编译器并不真正知道谁将执行查询,因此如果您的表达式包含您的提供程序不知道如何转换为 SQL 的方法或类,编译器不会抱怨。在这种情况下,您将收到运行时错误。
很容易看出,SQL 不知道你自己定义的Equals方法。事实上,甚至有几个标准的 LINQ 函数不受支持。请参阅支持和不支持的 LINQ 方法 (LINQ to Entities)。
那么如果我想使用不支持的功能该怎么办呢?
您可以做的事情之一是将数据移动到本地进程,然后调用不受支持的函数。
这可以使用 来完成ToList,但如果您只使用一个或几个获取的项目,这将浪费处理能力。
数据库查询较慢的部分之一是将所选数据传输到本地进程。因此,明智的做法是将数据限制为您实际计划使用的数据。
更明智的解决方案是使用AsEnumerable. 这将获取“每页”选定的数据。它将获取第一页,一旦您枚举了获取的页面(使用 MoveNext),它将获取下一页。
因此,如果您只使用部分已获取的项目,您将获取一些未使用的项目,但至少不会获取所有项目。
例子
假设您有一个本地函数,它接受 aStudent作为输入并返回一个布尔值
bool HasSpecialAbility(Student student);
Run Code Online (Sandbox Code Playgroud)
要求:给我三个住在纽约市的具有特殊能力的学生。
唉,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();
Run Code Online (Sandbox Code Playgroud)
好吧,您可能已经获取了 100 名学生的页面,而您只需要 3 个学生,但至少您还没有获取全部 25000 名学生。
| 归档时间: |
|
| 查看次数: |
995 次 |
| 最近记录: |