如何让重载的 == 运算符与 LINQ 和 EF Core 一起使用?

问题描述 投票:0回答:2

基本上,我有一个使用 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,但这并不那么干净。谢谢

c# linq entity-framework-core operator-overloading equals
2个回答
6
投票

要理解为什么使用不正确的相等比较器,您必须了解

IEnumerable<...>
IQueryable<...>
之间的区别。

IE可数

实现

IEnumerable<...>
的对象是表示一系列相似对象的对象。它包含获取序列第一项的所有内容,一旦获得序列中的一项,只要有下一项,就可以获取下一项。

您可以通过调用

GetEnumerator()
并重复调用
MoveNext()
来开始显式枚举。更常见的是使用
foreach
或 LINQ 终止语句(如
ToList()
ToDictionary()
FirstOrDefault()
Count()
Any()
)开始隐式枚举。这组 LINQ 方法在内部使用
foreach
GetEnumerator()
MoveNext()
/
Current

I可查询

实现

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 名学生。


0
投票
<h1>şlşşşş[enter image description here][1]

在此输入图片描述

© www.soinside.com 2019 - 2024. All rights reserved.