【问题标题】:Implement IEquatable for POCO为 POCO 实施 IEquatable
【发布时间】:2012-03-20 06:27:08
【问题描述】:

我注意到 EF 的 DbSet.Add() 非常慢。用谷歌搜索一下,得到了一个承诺高达 180 倍性能提升的 SO 答案:

https://stackoverflow.com/a/7052504/141172

但是,我不完全了解如何按照答案中的建议实现IEquatable<T>

According to MSDN,如果我实现IEquatable<T>,我也应该覆盖Equals()GetHashCode()

与许多 POCO 一样,我的对象是可变的。在提交到数据库 (SaveChanges()) 之前,新对象的 Id 为 0。对象被保存后,Id 作为实现 IEquatable、Equals() 和 GetHashCode 的理想基础()。

在哈希码中包含任何可变属性是不明智的,因为according to MSDN

如果两个对象比较相等,则每个对象的 GetHashCode 方法 对象必须返回相同的值

我是否应该将IEquatable<T> 实现为逐个属性的比较(例如this.FirstName == other.FirstName),而不是覆盖Equals() 和GetHashCode()?

鉴于我的 POCO 在 EntityFramework 上下文中使用,是否应特别注意 Id 字段?

【问题讨论】:

  • 为什么你的对象的 id 为 0?为什么不像 JomTois 在他的代码示例中显示的那样直接将 Guid 分配给 ID 字段/属性。这是您提到自己的 IEquatable 的理想基础。 TomTom 还指出了为每个客户端分配 id Range 或使用 -1、-2 和 -3 作为临时 id 的方法,这些“解决方案”似乎使事情变得复杂。在构建时,您会生成一个新的 Guid 并开始营业。还是我错过了什么?
  • @YoupTube:整数不能毫无价值。此外,整数是 32 位的,而 GUID 是 128 位的。这意味着 SQL Server 可以使用整数在内存中容纳 4 倍的 ID(执行连接时性能的关键)。
  • 啊,我明白了。但是您是否拥有大量数据、流量、连接并且需要超高性能?这总是一个权衡......我知道。但是使用 guid 应该不是什么大问题。然后您就可以忘记因使用整数而遇到的所有复杂问题。
  • @YoupTube:当然,在某些情况下 GUID 是一个合理的答案。然而,解决方案往往比最初想象的要大得多,所以我宁愿在高效架构方面犯错。就我而言,每天只有几十个访客。然而,流量所作用的 数据 与可用 RAM 的大小相比是巨大的(它是一个提供对大数据的访问的业务门户)。

标签: entity-framework-4 iequatable


【解决方案1】:

我在寻找相同问题的解决方案时遇到了您的问题。这是我正在尝试的解决方案,看看它是否满足您的需求:

首先,我所有的 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)方法中,空对象显然不相等,同样的引用显然是,那么我们只需要检查ID是否== 0,如果是,我们就说两个相同类型的对象两者都具有 0 的 ID 不相等。否则,如果它们的属性都相同,我们会说它们是相等的。

我认为这涵盖了所有相关情况,但如果我不正确,请补充。希望这会有所帮助。

=== 编辑 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 可能会更改(不想要孤儿)。这将满足上面链接答案中对相等性的约束。

【讨论】:

  • 这是一个有趣的方法。我会尽可能尝试并分享我的反馈。在您解决此问题时,如果您有任何其他见解,我将不胜感激。
【解决方案2】:

与许多 POCO 一样,我的对象是可变的

但是 tehy 在作为主键的字段上不应该是可变的。根据定义,或者您以后无论如何都处于痛苦数据库的世界中。

仅在主键的字段上生成 HashCode。

Equals() 必须返回 true IFF 参与对象具有相同的哈希码

BZZZ - 错误。

哈希码是双重的。 2 个对象可能具有不同的值和 smae 哈希码。 hsahsode 是一个 int(32 位)。一个字符串可以是 2gb 长。您不能将每个可能的字符串都映射到单独的哈希码。

如果两个对象具有相同的哈希码,它们可能是不同的。如果两个对象相同,则它们不能有不同的哈希码。

您从哪里得到 Equals 必须为具有相同哈希码的对象返回 true 的想法?

此外,无论是否为 PCO,映射到数据库并在关系中使用的对象必须具有稳定的主键(可用于运行哈希码计算)。没有这个 STIL 的对象应该有主键(根据 SQL Server 要求),在这里使用序列/人工主键有效。同样,使用它来运行 HashCode 计算。

【讨论】:

  • 你是对的,Equals 和 GetHashCode 之间的关系是相反的“如果两个对象比较相等,则每个对象的 GetHashCode 方法必须返回相同的值”msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx。关于关键:一旦将对象插入数据库,身份和平等就很简单了。就我而言,我正在实例化新对象并且尚未调用SaveChanges(),因此所有 Id 均为 0。我将根据您的 cmets 修改我的问题,但我还没有看到支持新对象的解决方案没有PK 已分配。
  • 使用客户端生成的主键。 GUID#s,您在客户端生成的序列。否则,通常的参考机制是痛苦的。或 - 去自己的 ID。我自己的 ORM 一年前使用负数 (-1, -2, -3) 作为临时键,它在插入时被替换。 G无论如何,提交后哈希码都不能合法地重用(对象需要刷新);)问题解决了。
  • +1 表示相同的哈希码并不意味着您的对象是相同的。
  • Eric 错过了 IFF 中的最后一个“F”......但他是对的......会有很多样本 GetHashCode 相同但 Equals 不会
  • 如何在运行大量客户端的情况下生成客户端 ID?几乎可以保证键冲突。 GUID 是 128 位的,并且比 32 位 int 作为主键的效率要低得多。
【解决方案3】:

第一件事:对不起我蹩脚的英语:)

正如 TomTom 所说,它们不应该仅仅因为它们还没有收到 PK/Id 就可变......

在我们的 EF:CF 系统中,我们为每个新 POCO 使用生成的负 id(在基类 ctor 中分​​配,或者,如果您使用 ProxyTracking,在 ObjectMaterialized 事件中分配)。它的想法很简单:

public static class IdKeeper
{
  private static int m_Current = int.MinValue;
  private static Next()
  {
    return ++m_Current;
  }
}

MinValue 和增量应该很重要,因为 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 实现的,那么当您调用 SaveChanges() 时,他的 Id 将被实际 PK 覆盖。作为奖励,每个“尚未保存”的 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;
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多