C# 为什么要收集<>;。Contains忽略我覆盖的等号和IEquatable<&燃气轮机;接口?
我对实体框架项目中的导航属性有问题 以下是MobileUser的C# 为什么要收集<>;。Contains忽略我覆盖的等号和IEquatable<&燃气轮机;接口?,c#,entity-framework,entity-framework-6,equals,c#-6.0,C#,Entity Framework,Entity Framework 6,Equals,C# 6.0,我对实体框架项目中的导航属性有问题 以下是MobileUser的课程: [DataContract] [Table("MobileUser")] public class MobileUser: IEquatable<MobileUser> { // constructors omitted.... /// <summary> /// The primary-key of MobileUser. /// This is not the V
课程:
[DataContract]
[Table("MobileUser")]
public class MobileUser: IEquatable<MobileUser>
{
// constructors omitted....
/// <summary>
/// The primary-key of MobileUser.
/// This is not the VwdId which is stored in a separate column
/// </summary>
[DataMember, Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[DataMember, Required, Index(IsUnique = true), MinLength(VwdIdMinLength), MaxLength(VwdIdMaxLength)]
public string VwdId { get; set; }
// other properties omitted ...
[DataMember]
public virtual ICollection<MobileDeviceInfo> DeviceInfos { get; private set; }
public bool Equals(MobileUser other)
{
return this.UserId == other?.UserId || this.VwdId == other?.VwdId;
}
public override bool Equals(object obj)
{
if(object.ReferenceEquals(this, obj))return true;
MobileUser other = obj as MobileUser;
if (other == null) return false;
return this.Equals(other);
}
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return VwdId.GetHashCode();
}
public override string ToString()
{
return "foo"; // omitted actual implementation
}
#region constants
// irrelevant
#endregion
}
如您所见,它实现了IEquatable
,并覆盖Equals
和GetHashCode
中的System.Object
我有以下测试,我希望Contains
会调用我的Equals
,但它没有。它似乎使用了对象。ReferenceEquals
,因此找不到我的设备,因为它是一个不同的引用:
var userRepo = new MobileUserRepository((ILog)null);
var deviceRepo = new MobileDeviceRepository((ILog)null);
IReadOnlyList<MobileUser> allUser = userRepo.GetAllMobileUsersWithDevices();
MobileUser user = allUser.First();
IReadOnlyList<MobileDeviceInfo> allDevices = deviceRepo.GetMobileDeviceInfos(user.VwdId, true);
MobileDeviceInfo device = allDevices.First();
bool contains = user.DeviceInfos.Contains(device);
bool anyEqual = user.DeviceInfos.Any(x => x.DeviceToken == device.DeviceToken);
Assert.IsTrue(contains); // no, it's false
那么,为什么只比较引用,而忽略我的自定义Equals
更新:
更令人困惑的是,即使我将其转换为
HashSet
:
而标准的HashSet
使用:
GenericEqualityComparer<T>
从EF源代码中,您可能会偶然发现,这似乎是连接导航属性的一部分
如果与属性兼容,则调用并返回一个HashSet
作为类型
然后,使用HashSet
,它进行调用,根据代码和描述,将HashSet
作为特例处理,并将其传递给构造函数
因此:EF为您创建的HashSet
EF没有使用等式实现,而是使用引用等式
为什么ICollection.Contains会忽略我覆盖的Equals和IEquatable接口
因为接口的实现者不需要这样做
ICollection.包含方法MSDN状态:
确定ICollection是否包含特定值
然后
备注
实现在如何确定对象的相等性方面可能有所不同;例如,List使用Comparer.Default,而Dictionary允许用户指定用于比较键的IComparer实现
旁注:看起来他们把IComparer
和iQualityComparer
搞砸了,但你明白了:)
结论:如果您不知道将使用什么比较器,请不要使用Contains,或者使用Enumerable.Contains和接受自定义比较器的重载
根据Enumerable.Contains(IEnumerable,T)
方法重载(即没有自定义比较器):
使用默认值相等比较器确定序列是否包含指定元素
这听起来像是将调用您的覆盖。但随之而来的是:
备注
如果源类型实现ICollection,则调用该实现中的Contains方法以获得结果。否则,此方法将确定源是否包含指定的元素
这与最初的陈述相冲突
真是一团糟。我只能说,我完全同意这个结论 @TimSchmelter:所以如果它只是一个散列集
,你应该能够在不涉及EF的情况下复制它,对吗?A在这里会很有帮助。@TimSchmelter比较((HashSet)user.deviceinfo).Comparer
到新的HashSet(user.deviceinfo).Comparer
,我打赌您会看到第一个是使用EF创建的自定义比较器,该比较器忽略您的iequatable
,第二个是使用默认比较器,该比较器将使用iequatable
。您可能会发现,这是埋在哈希集中的相等比较器的问题。你可能需要调试才能找到…@JonSkeet(和其他人):谢谢,你说得对。它实际上使用了一个不同的比较器,entity framework使用了一个名为自我解释的System.Data.entity.Infrastructure.ObjectReferenceEqualityComparer
。我仍然不明白这个陷阱的原因。我喜欢(具有讽刺意味的是)即使是最好的问题也是如何让选票下降的。在这个网站上提问太难了!谢谢虽然我真的不明白为什么实体框架要在这种情况下使用ObjectReferenceEqualityComparer
。这是一个令人讨厌的陷阱。@TimSchmelter我相信它正在使用它,因为它希望ICollection
具有快速缓存查找,它不能保证默认比较器对于该函数的性能良好。一开始你认为它是一个列表,所以只要使用可枚举。Contains
重载并传入EqualityComparer.Default
@ScottChamberlain:因此,根据经验法则:如果你不知道集合的实际类型以及它使用的比较器,就不要使用Contains
,否则你会陷入困境。@TimSchmelter我同意这个说法。这是。。。令人不安。几乎和我自己的代码一样难以理解:)
var userRepo = new MobileUserRepository((ILog)null);
var deviceRepo = new MobileDeviceRepository((ILog)null);
IReadOnlyList<MobileUser> allUser = userRepo.GetAllMobileUsersWithDevices();
MobileUser user = allUser.First();
IReadOnlyList<MobileDeviceInfo> allDevices = deviceRepo.GetMobileDeviceInfos(user.VwdId, true);
MobileDeviceInfo device = allDevices.First();
bool contains = user.DeviceInfos.Contains(device);
bool anyEqual = user.DeviceInfos.Any(x => x.DeviceToken == device.DeviceToken);
Assert.IsTrue(contains); // no, it's false
bool contains = new HashSet<MobileDeviceInfo>(user.DeviceInfos).Contains(device); // true
// still false
bool contains2 = ((HashSet<MobileDeviceInfo>)user.DeviceInfos).Contains(device);
// but this is true as already mentioned
bool contains3 = new HashSet<MobileDeviceInfo>(user.DeviceInfos).Contains(device);
System.Data.Entity.Infrastructure.ObjectReferenceEqualityComparer
GenericEqualityComparer<T>
bool contains = user.DeviceInfos.Contains(device, EqualityComparer<MobileDeviceInfo>.Default); // true