6

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

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

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

根据 MSDN,如果我实现IEquatable<T>,我还应该覆盖Equals()and GetHashCode()

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

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

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

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

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

4

3 回答 3

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)方法中,空对象显然不相等,同样的引用显然是,那么我们只需要检查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 可能会更改(不想要孤儿)。这将满足上面链接答案中对平等的约束。

于 2012-11-17T05:51:09.307 回答
1

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

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

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

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

BZZZ - 错误。

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

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

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

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

于 2012-03-20T06:57:30.930 回答
0

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

正如 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 和 incremen 应该很重要,因为 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 进行比较(为什么 dbset 工作这么慢)很容易:

public class Person
  : IdBase, IEquatable<Person>
{
  public virtual string FirstName { get; set; }

  public bool Equals(Person other)
  {
    return Id == other.Id;
  }
}
于 2012-04-27T14:14:59.523 回答