查询中 GroupBy 方法的自定义比较
Custom compare for GroupBy method in a query
我想将具有特定值的对象(在本例中 (int?)null
)放在不同的组中。
所以这个:
Id NullableInt
A 1
B 2
C 1
D null
E null
F 1
G null
应该这样结束:
Key Ids
1 A, C, F
2 B
null D
null E
null G
我正在使用 .GroupBy
和自定义比较器来尝试实现这一点。
问题是获取错误
LINQ to Entities does not recognize the method, and this method cannot be translated into a store expression
当我单独测试 LINQ 表达式时它可以工作,所以我假设它在 Entity Framework 中不受支持或工作方式不同,但我找不到任何关于它的信息。
我的代码(简化版):
var result = db.Table
...
.GroupBy(
t => t.NullableInt,
new NullNotEqualComprare())
...
.ToList();
显然我想在数据库本身做尽可能多的事情。
比较器代码:
private class NullNotEqualComparer : IEqualityComparer<int?>
{
public bool Equals(int? x, int? y)
{
if (x == null || y == null)
{
return false;
}
return x.Value == y.Value;
}
public int GetHashCode(int? obj)
{
return obj.GetHashCode();
}
}
我是不是做错了什么,如果不支持,我该如何解决这个问题?
根据 MSDN https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/supported-and-unsupported-linq-methods-linq-to-entities ,linq-to-entities GroupBy 不支持比较器。
根据您的要求,您可以尝试2个功能。第一个块过滤所有空键,然后第二个块按非空键分组。像这样
var nullKeyList = db.Table.Where(x => x.NullableInt == null).ToList();
var valueKeyGroup = db.Table.Where(x => x.NullableInt != null)
.GroupBy(t => t.NullableInt).ToList();
哎呀,你忘了准确写下你组的要求了。我无法从你的比较器中扣除它,因为比较器不正确。
根据您的比较器,如果 x 等于 null,则 x 不等于 x
IEqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
bool b = comparer(x, x);
因此 x 不会与 x 在同一组中。
后面再说比较器,先解释一下异常
IQueryable 不能使用 IEqualityComparer
一个IQueryable<...>
有一个Expression
和一个Provider
。 Expression 以通用形式表示必须执行的查询;提供者知道哪个进程将执行查询(通常是数据库管理系统)以及使用哪种语言与该进程通信(通常 SQL)。
只要连接 return IQueryable<...>
的 LINQ 语句,只有 Expression
会发生变化。未联系数据库,未执行查询。只有当您通过调用 GetEnumerator()
开始枚举时(直接调用,或在另一个函数中深入调用,如 ToList()
或 foreach
),表达式才会发送给 Provider,Provider 将尝试将表达式翻译成 SQL,然后执行查询。 returned 数据显示为 IEnumerator<...>
,您可以使用它逐一访问 returned 项目。
问题是,提供商不知道您的 NullNotEqualComparer
,因此无法将其翻译成 SQL。事实上,有几种 LINQ 方法不受 LINQ-to-Entities 支持。请参阅 [支持和不支持的方法 (LINQ to Entities)]
1
因此您必须尝试将比较放在 GroupBy 的 keySelector 中。
间奏曲:你的 NullNotEqualComparer
你的相等比较器不是一个好的比较器。不满足x等于x:
的要求
IEqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
bool b = comparer.Equals(x, x);
int? y = x;
bool c = comparer.Equals(x, y);
int? z = null;
bool d = comparer.Equals(x, z);
你期待什么,结果如何?
几乎总是,正确的相等比较器以相同的四行开始:
public bool equals(MyClass x, MyClass y)
{
if (x == null) return y == null; // true if both null, false if x null, y not null
if (y == null) return false; // false, because x != null and y == null
// the following two lines are just for efficiency:
if (object.ReferenceEquals(x, y) return true;
if (x.GetType() != y.GetType()) return false;
// here starts the real comparison:
...
}
在极少数情况下,您希望不同类型成为相同的对象。在那种情况下,您将不会检查类型。
GetHashCode 用于快速检查两个对象是否不同。如果你要比较一千个对象的相等性,你可以很容易地发现其中 990 个是不同的,那么你只需要完全检查最后 10 个元素。
想想一个有 20 个属性的 class。要完全相等,您需要检查所有 20 个属性。如果您明智地选择 GetHashCode,则可能没有必要检查所有 20 个属性。
例如,如果您想查找住在同一地址的所有人,则必须检查国家/地区、城市、邮编、街道、门牌号……
从输入序列中消除大多数人的一种快速方法是仅检查邮政编码:如果两个人的邮政编码不同,他们将不会住在同一地址。
因此,对您的 GetHashCode 的唯一要求是:如果 Equals(x, y)
,则 GetHashCode(x) == GetHashCode(y)
。请注意:不是相反:可能有不同的 x 和 y,它们具有相同的 HashCode。这个很容易看出:GetHashCodereturn是一个Int32,所以肯定有几个Int64对象共享他们的HashCode。
EqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
int y = comparer.GetHashcode(x); // <== Exception!
回到你的问题
在我看来,您创建了这个相等比较器,因为您希望为 table 中的所有项目创建一个单独的组,这些项目 t.NullableInt 的值等于 null。
Id NullableInt
A 1
B 2
C 1
D null
E null
F 1
G null
你想要三组:
- 键 1,Id 为 A、C、F 的元素
- 键 2,ID 为 B 的元素
- 键为空,Id 为 D、E、G 的元素
如果这是你想要的,你可以使用 default comparer for class Nullable<T>
:
- x 和 y 的 HasValue 均为假:return真
- x 和 y 的 HasValue 均为真:return x.Value == y.Value
- 在所有其他情况下:return 错误。
假设您有一种方法可以将 dt.Table 的行转换为 IQueryable:
IQueryable<MyClass> tableRows = db.Table.ToMyClass();
var result = tableRows.GroupBy(row => row.NullableInt);
最后我进行了一些破解,但它非常简单并且效果很好。我想我会把它写在这里,因为它可能对其他人有用。
在我的问题中,NullableInt
实际上是另一个 table 的 ID,所以我知道它总是大于零。
因此我可以这样做:
var result = db.Table
...
.GroupBy(t => t.NullableInt ?? int.MinValue + t.Id)
...
.ToList();
我想将具有特定值的对象(在本例中 (int?)null
)放在不同的组中。
所以这个:
Id NullableInt
A 1
B 2
C 1
D null
E null
F 1
G null
应该这样结束:
Key Ids
1 A, C, F
2 B
null D
null E
null G
我正在使用 .GroupBy
和自定义比较器来尝试实现这一点。
问题是获取错误
LINQ to Entities does not recognize the method, and this method cannot be translated into a store expression
当我单独测试 LINQ 表达式时它可以工作,所以我假设它在 Entity Framework 中不受支持或工作方式不同,但我找不到任何关于它的信息。
我的代码(简化版):
var result = db.Table
...
.GroupBy(
t => t.NullableInt,
new NullNotEqualComprare())
...
.ToList();
显然我想在数据库本身做尽可能多的事情。
比较器代码:
private class NullNotEqualComparer : IEqualityComparer<int?>
{
public bool Equals(int? x, int? y)
{
if (x == null || y == null)
{
return false;
}
return x.Value == y.Value;
}
public int GetHashCode(int? obj)
{
return obj.GetHashCode();
}
}
我是不是做错了什么,如果不支持,我该如何解决这个问题?
根据 MSDN https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/supported-and-unsupported-linq-methods-linq-to-entities ,linq-to-entities GroupBy 不支持比较器。
根据您的要求,您可以尝试2个功能。第一个块过滤所有空键,然后第二个块按非空键分组。像这样
var nullKeyList = db.Table.Where(x => x.NullableInt == null).ToList(); var valueKeyGroup = db.Table.Where(x => x.NullableInt != null) .GroupBy(t => t.NullableInt).ToList();
哎呀,你忘了准确写下你组的要求了。我无法从你的比较器中扣除它,因为比较器不正确。
根据您的比较器,如果 x 等于 null,则 x 不等于 x
IEqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
bool b = comparer(x, x);
因此 x 不会与 x 在同一组中。
后面再说比较器,先解释一下异常
IQueryable 不能使用 IEqualityComparer
一个IQueryable<...>
有一个Expression
和一个Provider
。 Expression 以通用形式表示必须执行的查询;提供者知道哪个进程将执行查询(通常是数据库管理系统)以及使用哪种语言与该进程通信(通常 SQL)。
只要连接 return IQueryable<...>
的 LINQ 语句,只有 Expression
会发生变化。未联系数据库,未执行查询。只有当您通过调用 GetEnumerator()
开始枚举时(直接调用,或在另一个函数中深入调用,如 ToList()
或 foreach
),表达式才会发送给 Provider,Provider 将尝试将表达式翻译成 SQL,然后执行查询。 returned 数据显示为 IEnumerator<...>
,您可以使用它逐一访问 returned 项目。
问题是,提供商不知道您的 NullNotEqualComparer
,因此无法将其翻译成 SQL。事实上,有几种 LINQ 方法不受 LINQ-to-Entities 支持。请参阅 [支持和不支持的方法 (LINQ to Entities)]
1
因此您必须尝试将比较放在 GroupBy 的 keySelector 中。
间奏曲:你的 NullNotEqualComparer
你的相等比较器不是一个好的比较器。不满足x等于x:
的要求IEqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
bool b = comparer.Equals(x, x);
int? y = x;
bool c = comparer.Equals(x, y);
int? z = null;
bool d = comparer.Equals(x, z);
你期待什么,结果如何?
几乎总是,正确的相等比较器以相同的四行开始:
public bool equals(MyClass x, MyClass y)
{
if (x == null) return y == null; // true if both null, false if x null, y not null
if (y == null) return false; // false, because x != null and y == null
// the following two lines are just for efficiency:
if (object.ReferenceEquals(x, y) return true;
if (x.GetType() != y.GetType()) return false;
// here starts the real comparison:
...
}
在极少数情况下,您希望不同类型成为相同的对象。在那种情况下,您将不会检查类型。
GetHashCode 用于快速检查两个对象是否不同。如果你要比较一千个对象的相等性,你可以很容易地发现其中 990 个是不同的,那么你只需要完全检查最后 10 个元素。
想想一个有 20 个属性的 class。要完全相等,您需要检查所有 20 个属性。如果您明智地选择 GetHashCode,则可能没有必要检查所有 20 个属性。
例如,如果您想查找住在同一地址的所有人,则必须检查国家/地区、城市、邮编、街道、门牌号……
从输入序列中消除大多数人的一种快速方法是仅检查邮政编码:如果两个人的邮政编码不同,他们将不会住在同一地址。
因此,对您的 GetHashCode 的唯一要求是:如果 Equals(x, y)
,则 GetHashCode(x) == GetHashCode(y)
。请注意:不是相反:可能有不同的 x 和 y,它们具有相同的 HashCode。这个很容易看出:GetHashCodereturn是一个Int32,所以肯定有几个Int64对象共享他们的HashCode。
EqualityComparer<int?> comparer = new NullNotEqualComparer();
int? x = null;
int y = comparer.GetHashcode(x); // <== Exception!
回到你的问题
在我看来,您创建了这个相等比较器,因为您希望为 table 中的所有项目创建一个单独的组,这些项目 t.NullableInt 的值等于 null。
Id NullableInt
A 1
B 2
C 1
D null
E null
F 1
G null
你想要三组:
- 键 1,Id 为 A、C、F 的元素
- 键 2,ID 为 B 的元素
- 键为空,Id 为 D、E、G 的元素
如果这是你想要的,你可以使用 default comparer for class Nullable<T>
:
- x 和 y 的 HasValue 均为假:return真
- x 和 y 的 HasValue 均为真:return x.Value == y.Value
- 在所有其他情况下:return 错误。
假设您有一种方法可以将 dt.Table 的行转换为 IQueryable:
IQueryable<MyClass> tableRows = db.Table.ToMyClass();
var result = tableRows.GroupBy(row => row.NullableInt);
最后我进行了一些破解,但它非常简单并且效果很好。我想我会把它写在这里,因为它可能对其他人有用。
在我的问题中,NullableInt
实际上是另一个 table 的 ID,所以我知道它总是大于零。
因此我可以这样做:
var result = db.Table
...
.GroupBy(t => t.NullableInt ?? int.MinValue + t.Id)
...
.ToList();