我注意到EF的DbSet.Add()非常慢。一个小小的谷歌搜索出现了一个SO答案,承诺性能提升180倍:
https://stackoverflow.com/a/7052504/141172
但是,我不明白如何按照答案中的建议实施IEquatable<T>
。
According to MSDN,如果我实施IEquatable<T>
,我还应该覆盖Equals()
和GetHashCode()
。
与许多POCO一样,我的对象是 mutable 。在提交到数据库(SaveChanges()
)之前,新对象的Id为0. 对象保存后,Id作为实现IEquatable,Equals()的理想基础和GetHashCode()。
在哈希代码中包含任何可变属性是不明智的,因为according to MSDN
如果两个对象比较相等,则每个对象的GetHashCode方法 对象必须返回相同的值
我应该将IEquatable<T>
作为逐个属性的比较(例如this.FirstName == other.FirstName
)而不是重写Equals()和GetHashCode()吗?
鉴于我的POCO用于EntityFramework上下文,是否应该特别注意Id字段?
答案 0 :(得分:3)
我遇到了你的问题,寻找同一个问题的解决方案。这是我正在尝试的解决方案,看看它是否符合您的需求:
首先,我的所有POCO都来自这个抽象类:
public abstract class BasePOCO <T> : IEquatable<T> where T : class
{
private readonly Guid _guid = Guid.NewGuid();
#region IEquatable<T> Members
public abstract bool Equals(T other);
#endregion
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != typeof (T))
{
return false;
}
return Equals((T)obj);
}
public override int GetHashCode()
{
return _guid.GetHashCode();
}
}
我创建了一个只在GetHashCode()覆盖中使用的只读Guid字段。这将确保我将派生的POCO放入字典或使用散列的其他东西,如果我在过渡期间调用.SaveChanges()并且ID字段由基类这是我不确定是完全正确的一部分,还是比Base.GetHashCode()更好?。我抽象了Equals(T other)方法,以确保实现类必须以某种有意义的方式实现它,最有可能使用ID字段。我在这个基类中放置了Equals(object obj)覆盖,因为它对于所有派生类也可能是相同的。
这将是抽象类的实现:
public class Species : BasePOCO<Species>
{
public int ID { get; set; }
public string LegacyCode { get; set; }
public string Name { get; set; }
public override bool Equals(Species other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return ID != 0 &&
ID == other.ID &&
LegacyCode == other.LegacyCode &&
Name == other.Name;
}
}
ID属性被设置为数据库中的主键,EF知道这一点。新创建的对象上的ID为0,然后在.SaveChanges()上设置为唯一的正整数。所以在重写的Equals(Species other)方法中,null对象显然不相等,显然是相同的引用,那么我们只需要检查ID == 0.如果是,我们会说两个相同类型的对象ID均为0的两者都不相等。否则,如果它们的属性完全相同,我们会说它们是相同的。
我认为这涵盖了所有相关情况,但如果我不正确,请发信息。希望这会有所帮助。
===编辑1
我在想我的GetHashCode()是不对的,我看了关于这个主题的这个https://stackoverflow.com/a/371348/213169答案。上面的实现违反了返回Equals()== true的对象必须具有相同哈希码的约束。
这是我的第二次尝试:
public abstract class BasePOCO <T> : IEquatable<T> where T : class
{
#region IEquatable<T> Members
public abstract bool Equals(T other);
#endregion
public abstract override bool Equals(object obj);
public abstract override int GetHashCode();
}
实施:
public class Species : BasePOCO<Species>
{
public int ID { get; set; }
public string LegacyCode { get; set; }
public string Name { get; set; }
public override bool Equals(Species other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return ID != 0 &&
ID == other.ID &&
LegacyCode == other.LegacyCode &&
Name == other.Name;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return Equals(obj as Species);
}
public override int GetHashCode()
{
unchecked
{
return ((LegacyCode != null ? LegacyCode.GetHashCode() : 0) * 397) ^
(Name != null ? Name.GetHashCode() : 0);
}
}
public static bool operator ==(Species left, Species right)
{
return Equals(left, right);
}
public static bool operator !=(Species left, Species right)
{
return !Equals(left, right);
}
}
所以我摆脱了基类中的Guid并将GetHashCode移到了实现中。我使用Resharper的GetHashCode实现以及ID以外的所有属性,因为ID可能会改变(不想要孤儿)。这将满足上述相关答案中对平等的约束。
答案 1 :(得分:1)
与许多POCO一样,我的对象是可变的
但是,作为主键的字段不应该是可变的。根据定义,或者你以后无论如何都处于一个痛苦的数据库世界。
仅在primay键的字段上生成HashCode。
Equals()必须返回true IFF,参与对象具有相同的哈希码
BZZZ - 错误。
Hashcodes是双倍的。 2个对象可能具有不同的值和smae哈希码。一个hsahsode是一个int(32位)。一个字符串可以是2GB长。您不能将每个可能的字符串映射到单独的哈希码。
如果两个对象具有相同的哈希码,则它们可能不同。如果两个对象相同,则它们不能具有不同的哈希码。
在哪里可以看出Equals必须为具有相同哈希码的对象返回true?
另外,PCO与否,映射到数据库并在关系中使用的对象必须具有稳定的主键(可用于运行哈希码计算)。没有此STIL的对象应该具有主键(根据SQL Server要求),使用序列/人工主键在此处工作。再次,使用它来运行HashCode计算。
答案 2 :(得分:0)
第一件事:抱歉我的蹩脚英语:)
正如TomTom所说,他们不应该只是因为他们还没有收到PK / Id ......
在我们的EF:CF系统中,我们使用生成的否定ID(在基类ctor中分配,或者,如果在ObjectMaterialized事件中使用ProxyTracking,则为每个新POCO)。它非常简单的想法:
public static class IdKeeper
{
private static int m_Current = int.MinValue;
private static Next()
{
return ++m_Current;
}
}
MinValue和increment应该很重要,因为EF会在对db进行更改之前按PK对POCO进行排序,当你使用“-1,-2,-3”时,POCO会被保存翻转,这在某些情况下(不是根据什么样的)可能不理想。
public abstract class IdBase
{
public virtual int Id { get; set; }
protected IdBase()
{
Id = IdKeeper.Next();
}
}
如果POCO是从DB实现的,那么他的Id将被覆盖实际PK以及调用SaveChanges()时。作为奖励,每一个“尚未保存”的POCO ID都是独一无二的(有一天应该会派上用场;)
将两个POCO与IEquatable(why does dbset work so slow)进行比较是很容易的:
public class Person
: IdBase, IEquatable<Person>
{
public virtual string FirstName { get; set; }
public bool Equals(Person other)
{
return Id == other.Id;
}
}