NHibernate一对多关系的问题与级联的集合

时间:2011-08-15 18:01:53

标签: c# nhibernate one-to-many cascade

我的AssetGroup实体与Asset实体具有一对多的关系。有一个实体基类,它覆盖了Equals和GetHashCode。我正在关注ch 20 parent child

的例子
<?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

}

我的实体基类在这里:

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已保存,并且它们位于父级的Collection中。例如,我可以通过Linq FirstOrDefault找到它们。但是Collection的Contains and Remove将无法找到它们。我注意到Collection使用GetHashCode,同时调用Contains()或Remove()。

2 个答案:

答案 0 :(得分:3)

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

这可能会失败。你必须管理双向关系。我的意思是,

在AssetGroup中:

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来自一个新的查询,所以应该没问题...除非会话缓存了对象,我认为它没有...但我不确定。

答案 1 :(得分:0)

我添加了session.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

}