Коллекция Nhibernate iesicollection содержит возвраты false

Nhibernate заставляет вас использовать набор Iesi вместо интерфейса net 4 ISet. В следующем фрагменте кода я проверяю, содержит ли набор iesi элемент:

    public virtual void Remove(Substance substance)
    {
        var test = _substances.First() == substance;

        if (!_substances.Contains(substance)) return;

        _substances.Remove(substance);
        substance.SubstanceGroup = null;
    }

Переменная _substances ссылается на HashedSet. Я добавил тестовую переменную просто для проверки кода в качестве временной меры. У меня метод Equals переопределен следующим образом:

    public override int GetHashCode()
    {
        return Equals(Id, default(TId)) ? base.GetHashCode() : Id.GetHashCode();
    }

Это приводит к тому, что элемент возвращает идентификатор (Guid) в виде хэша. Если я проверю в отладчике, я получу следующий результат:

test
true
_substances.Contains(substance)
false
_substances.First().GetHashCode()
-2974953
substance.GetHashCode()
-2974953

Как может быть, что точно такой же объект не обнаруживается в коллекции с помощью метода contains этой коллекции?? Я даже могу сделать это в отладчике:

_substances.Contains(_substances.First())
false

Очевидно, что _substances.Remove(substance) тоже не работает. После некоторых дополнительных исследований я обнаружил, что NH заменяет коллекцию своим собственным набором Persistent Generic. Проблема возникает при использовании этого набора. Если я извлекаю элемент из этого набора и вызываю «Содержит» для того же набора, он всегда возвращает false. Я переопределил GetHashCode и Equals, даже установил return true в методе Equals.


person halcwb    schedule 25.08.2011    source источник


Ответы (1)


Что-то не так с вашей реализацией Equals и GetHashCode, потому что я уверяю вас, что коллекция Iesi ISet работает правильно. Причина, по которой он заменяется PersistentGenericSet, заключается в том, что ISet — это просто интерфейс, коллекция должна быть заменена конкретным типом. Без дополнительного кода трудно понять, в чем проблема, поэтому ниже я вставил лучшую реализацию равенства. Одна проблема, которую я вижу в вашей, заключается в том, что хэш-код изменится после назначения идентификатора, моя версия обрабатывает это путем кэширования хэш-кода.

public class Substance
{
    private int? _cachedHashCode;

    public Substance()
    {
        Id = Guid.Empty;
    }

    public Substance(Guid id)
    {
        Id = id;
    }

    public Guid Id { get; set; }

    public bool IsTransient
    {
        get { return Id == Guid.Empty; }
    }

    public bool Equals(Substance other)
    {
        if (IsTransient ^ other.IsTransient)
        {
            return false;
        }
        if (IsTransient && other.IsTransient)
        {
            return ReferenceEquals(this, other);
        }
        return other.Id.Equals(Id);
    }

    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
        {
            return false;
        }
        var other = (Substance)obj;
        return Equals(other);
    }

    public override int GetHashCode()
    {
        if (!_cachedHashCode.HasValue)
        {
            _cachedHashCode = IsTransient ? base.GetHashCode() : Id.GetHashCode();
        }
        return _cachedHashCode.Value;
    }
}

public class Mixture
{
    public Mixture()
    {
        Substances = new HashedSet<Substance>();
    }

    public ISet<Substance> Substances { get; set; }
}

public class Tests
{
    [Test]
    public void set_contains_transient_substance()
    {
        var mixture = new Mixture();
        var s1 = new Substance();
        mixture.Substances.Add(s1);
        Assert.IsTrue(mixture.Substances.Contains(s1));
    }

    [Test]
    public void set_contains_persistent_substance()
    {
        var id = Guid.NewGuid();
        var mixture = new Mixture();

        var s1 = new Substance(id);
        mixture.Substances.Add(s1);

        var s2 = new Substance(id);
        // these were created with the same id so hash code is not cached
        // and id equality is used
        Assert.IsTrue(mixture.Substances.Contains(s2));
    }

    [Test]
    public void remove_substance()
    {
        var id = Guid.NewGuid();
        var mixture = new Mixture();

        var s1 = new Substance(id);
        mixture.Substances.Add(s1);

        var s2 = new Substance(id);
        mixture.Substances.Remove(s2);
        Assert.IsTrue(mixture.Substances.Count() == 0);
    }

    [Test]
    public void hash_code_is_cached()
    {
        var s1 = new Substance(Guid.NewGuid());
        var s2 = new Substance(Guid.NewGuid());

        var mixture = new Mixture();
        mixture.Substances.Add(s1);

        Assert.IsFalse(mixture.Substances.Contains(s2));
        // assign s1 id to s2, s2 hashcode is cached so they are not equal
        s2.Id = s1.Id;
        Assert.IsFalse(mixture.Substances.Contains(s2));
    }

}
person Jamie Ide    schedule 25.08.2011
comment
Это помогло, большое спасибо. В своем базовом классе я реализовал кэширование хэш-кода. - person halcwb; 26.08.2011
comment
Однако одно важное предостережение: ваше решение подразумевает, что идентификатор устанавливается приложением, а не NH. К сожалению, вы упускаете из виду некоторые важные функции NH, делая это таким образом. - person halcwb; 28.08.2011