проблема с NHibernate отношения один ко многим Коллекция с каскадом - PullRequest
0 голосов
/ 15 августа 2011

У меня есть объект AssetGroup, имеющий отношение «один ко многим» с объектом «Актив». Существует базовый класс Entity, который переопределяет Equals и GetHashCode. Я следую примеру ch 20 родительский ребенок

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="TestNHibernate"
                   namespace="TestNHibernate.Models" auto-import="true">
  <class name="AssetGroup">
    <id name="Id" column="Id" type="guid">
      <generator class="guid"></generator>
    </id>
    <property name="Name" type="string" not-null="true"/>
    <set name="Assets" cascade="all" inverse="true" access="field.camelcase-underscore" lazy="true">
      <key column="AssetGroupID"/>
      <one-to-many class="Asset"/>
    </set>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="TestNHibernate"
                   namespace="TestNHibernate.Models" auto-import="true">
  <class name="Asset">
    <id name="Id" column="Id" type="guid">
      <generator class="guid"></generator>
    </id>
    <property name="Name" type="string" not-null="true"/>
    <many-to-one name="AssetGroup" column="AssetGroupID" cascade="all" lazy="false"/>

  </class>
</hibernate-mapping>

код, следующий за:

public class AssetGroup : Entity<Guid>
{
    public AssetGroup()
    {
        this._assets = new HashedSet<Asset>();
    }


    virtual public string Name { get; set; }

    private ISet<Asset> _assets;
    virtual public ISet<Asset> Assets
    {
        get { return _assets; }
        protected set { _assets = value; }
    }

    virtual public bool AddAsset(Asset asset)
    {
        if (asset != null && _assets.Add(asset))
        {
            asset.SetAssetGroup(this);
            return true;
        }
        return false;
    }

    virtual public bool RemoveAsset(Asset asset)
    {
        if (asset != null && _assets.Remove(asset))
        {
            asset.SetAssetGroup(null);
            return true;
        }
        return false;
    }
 }

public class AssetGroup : Entity<Guid>
{
    public AssetGroup()
    {
        this._assets = new HashedSet<Asset>();
    }

    virtual public string Name { get; set; }

    private ISet<Asset> _assets;
    virtual public ISet<Asset> Assets
    {
        get { return _assets; }
        protected set { _assets = value; }
    }

    virtual public bool AddAsset(Asset asset)
    {
        if (asset != null && _assets.Add(asset))
        {
            asset.SetAssetGroup(this);
            return true;
        }
        return false;
    }

    virtual public bool RemoveAsset(Asset asset)
    {
        if (asset != null && _assets.Remove(asset))
        {
            asset.SetAssetGroup(null);
            return true;
        }
        return false;
    }
}

Мой TestCode выглядит следующим образом:

[TestMethod]
public void Can_Use_ISession()
{
    ISession session = TestConfig.SessionFactory.GetCurrentSession();
    var ag = new AssetGroup { Name = "NHSession" };
    session.Save(ag);

    var a1 = new Asset { Name = "s1" };
    var a2 = new Asset { Name = "s2" };

    a1.SetAssetGroup(ag);
    a2.SetAssetGroup(ag);

    session.Flush();

    Assert.IsTrue(a1.Id != default(Guid)); // ok
    Assert.IsTrue(a2.Id != default(Guid)); // ok

    var enumerator = ag.Assets.GetEnumerator();
    enumerator.MoveNext();
    Assert.IsTrue(ag.Assets.Contains(enumerator.Current));  // failed

    Assert.IsTrue(ag.Assets.Contains(a1));  // failed
    Assert.IsTrue(ag.Assets.Contains(a2));  // failed 

    var agRepo2 = new NHibernateRepository<AssetGroup>(TestConfig.SessionFactory, new QueryFactory(TestConfig.Locator));
    Assert.IsTrue(agRepo2.Contains(ag)); // ok
    var ag2 = agRepo2.FirstOrDefault(x => x.Id == ag.Id);
    Assert.IsTrue(ag2.Assets.FirstOrDefault(x => x.Id == a1.Id) != null); // ok
    Assert.IsTrue(ag2.Assets.FirstOrDefault(x => x.Id == a2.Id) != null); // ok

    var aa1 = session.Get<Asset>(a1.Id);
    var aa2 = session.Get<Asset>(a2.Id);
    Assert.IsTrue(ag2.Assets.Contains(aa1));  // failed
    Assert.IsTrue(ag2.Assets.Contains(aa2));  // failed

}

Базовый класс My Entity находится здесь:

public abstract class Entity<Tid> : IEquatable<Entity<Tid>>
{
    [HiddenInput(DisplayValue = false)]
    public virtual Tid Id { get; protected set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return base.Equals(obj);
        return Equals(obj as Entity<Tid>);
    }

    public static bool IsTransient(Entity<Tid> obj)
    {
        return obj != null && Equals(obj.Id, default(Tid));
    }

    private Type GetUnproxiedType()
    {
        return GetType();
    }

    public virtual bool Equals(Entity<Tid> other)
    {
        if (ReferenceEquals(this, other))
            return true;
        if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id))
        {
            var otherType = other.GetUnproxiedType();
            var thisType = GetUnproxiedType();
            return thisType.IsAssignableFrom(otherType) || otherType.IsAssignableFrom(thisType);
        }
        return false;
    }

    public override int GetHashCode()
    {
        if (Equals(Id, default(Tid)))
        {
            return base.GetHashCode();
        }
        else
        {
            return Id.GetHashCode();
        }
    }

}

Я прокомментировал, какие части потерпели неудачу в коде. Пожалуйста помоги. Кажется, что объекты, сохраненные каскадом, несовместимы с ICollection Contains / Remove. Актив a1 a2 сохранен и находится в родительской коллекции. Например, я могу найти их по Linq FirstOrDefault. Но Коллекция Содержит и Удалить не сможет найти их. Я заметил, что Коллекция использует GetHashCode, когда вызываются Contains () или Remove ().

Ответы [ 2 ]

3 голосов
/ 16 августа 2011
Assert.IsTrue(ag.Assets.Contains(a1));  // failed

Это могло бы действительно потерпеть неудачу. Вы должны управлять двунаправленными отношениями. Под этим я подразумеваю

В группе активов:

virtual public bool AddAsset(Asset asset)
{
    if (asset != null && _assets.Add(asset))
    {
        asset.AssetGroup = this;
        return true;
    }
    return false;
}

... and a corresponding remove

В активе:

virtual public bool SetAssetGroup(AssetGroup group)
{
    this.AssetGroup = group;
    group.Assets.Add(this);
}

Обратите внимание на разницу между вашим кодом и кодом выше. Это не единственный способ сделать это, но это самый независимый от отображения, безопасный способ сделать это ... так что, если вы установите inverse = true в своем отображении или нет, это будет работать. Я делаю это по умолчанию, даже не задумываясь об этом.

При работе с моделью извне вы используете AddXXX, RemoveXXX, SetXXX. При работе с моделью изнутри вы напрямую ссылаетесь на свойства и коллекции. Примите это как соглашение, и вы будете в порядке для большинства распространенных сценариев двунаправленного отображения.

Сказав это, я не уверен, почему этот код не работает:

Assert.IsTrue(ag2.Assets.Contains(aa1));

ag2 из нового запроса, так что все должно быть в порядке ... если сеанс не кэшировал объект, что я не думаю, что это делает ... но я не уверен.

0 голосов
/ 16 августа 2011

Я добавил сессию. Refresh (ag) и тогда все работает. Является ли сессия обновления дорогой операцией? Есть ли альтернатива

[TestMethod]
public void Can_Use_ISession()
{
    ISession session = TestConfig.SessionFactory.GetCurrentSession();
    var ag = new AssetGroup { Name = "NHSession" };
    session.Save(ag);

    var a1 = new Asset { Name = "s1" };
    var a2 = new Asset { Name = "s2" };

    a1.SetAssetGroup(ag);
    a2.SetAssetGroup(ag);

    session.Flush();
    session.Refresh(ag);

    Assert.IsTrue(a1.Id != default(Guid)); // ok
    Assert.IsTrue(a2.Id != default(Guid)); // ok

    var enumerator = ag.Assets.GetEnumerator();
    enumerator.MoveNext();
    Assert.IsTrue(ag.Assets.Contains(enumerator.Current));  // failed

    Assert.IsTrue(ag.Assets.Contains(a1));  // failed
    Assert.IsTrue(ag.Assets.Contains(a2));  // failed 

    var agRepo2 = new NHibernateRepository<AssetGroup>(TestConfig.SessionFactory, new QueryFactory(TestConfig.Locator));
    Assert.IsTrue(agRepo2.Contains(ag)); // ok
    var ag2 = agRepo2.FirstOrDefault(x => x.Id == ag.Id);
    Assert.IsTrue(ag2.Assets.FirstOrDefault(x => x.Id == a1.Id) != null); // ok
    Assert.IsTrue(ag2.Assets.FirstOrDefault(x => x.Id == a2.Id) != null); // ok

    var aa1 = session.Get<Asset>(a1.Id);
    var aa2 = session.Get<Asset>(a2.Id);
    Assert.IsTrue(ag2.Assets.Contains(aa1));  // failed
    Assert.IsTrue(ag2.Assets.Contains(aa2));  // failed

}
...