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

发布于 2024-11-30 02:30:06 字数 6020 浏览 0 评论 0原文

我有 AssetGroup 实体与 Asset 实体具有一对多关系。有一个 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;
    }
}

我的测试代码如下:

[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和Remove将无法找到它们。我注意到 Collection 在调用 Contains() 或 Remove() 时使用 GetHashCode。

I have AssetGroup entity with has a one-to-many relation with Asset entity. There is a Entity base class which overrides Equals and GetHashCode . I am following the example of 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>

the code as folloow:

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;
    }
}

My TestCode is as follow :

[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 base class is here:

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();
        }
    }

}

I have commented which parts have failed in the code . Please help. It seems entities which are saved by cascading is not compatible with ICollection Contains/Remove . Asset a1 a2 are saved and they are inside the parent's Collection . For example I can find them by Linq FirstOrDefault . But the Collection's Contains and Remove will fail to find them . I notice the Collection use GetHashCode while Contains() or Remove() are called.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

请帮我爱他 2024-12-07 02:30:06
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 来自一个新查询,所以应该没问题......除非会话缓存了对象,我不认为它会缓存对象......但我'我不确定。

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

This could would indeed fail. You have to manage the bi-directional relationship. By this I mean,

In AssetGroup:

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

... and a corresponding remove

In Asset:

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

Note the difference between your code and the one above.
This is not the only way of doing it, but it's the most mapping-agnostic, safe way of doing it... so whether you set inverse=true on your mapping or not, it'll work. I do it by default without even thinking about it too much.

When working with the model from the outside, you use AddXXX, RemoveXXX, SetXXX. When working with the model from the inside you reference the properties and collections directly. Adopt this as a convention, and you'll be ok for most of the common bi-directional mapping scenarios.

Having said this, I'm not sure why this code fails:

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

ag2 is from a new query, so that should be ok ... unless the session cached the object, which I don't think it does... but I'm not sure.

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

}

I added session.Refresh(ag) and then it works . Is session Refresh an expensive operation ? Is there any alternative

[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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文