为POCO实现IEquatable

时间:2012-03-20 06:27:09

标签: entity-framework-4 iequatable

我注意到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字段?

3 个答案:

答案 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;
  }
}
相关问题