NHibernate 一对多关系集合级联的问题
我有 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这确实可能会失败。您必须管理双向关系。我的意思是,
在资产组中:
在资产中:
请注意您的代码与上面的代码之间的区别。
这不是唯一的方法,但它是最与映射无关、最安全的方法......所以无论您是否在映射上设置 inverse=true ,它都会起作用。我默认就这样做,甚至没有考虑太多。
当从外部使用模型时,您可以使用 AddXXX、RemoveXXX、SetXXX。当从内部使用模型时,您可以直接引用属性和集合。将此作为惯例,您将可以应对大多数常见的双向映射场景。
话虽如此,我不确定为什么这段代码会失败:
ag2 来自一个新查询,所以应该没问题......除非会话缓存了对象,我不认为它会缓存对象......但我'我不确定。
This could would indeed fail. You have to manage the bi-directional relationship. By this I mean,
In AssetGroup:
In Asset:
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:
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) 然后它就可以工作了。会话刷新是一个昂贵的操作吗?有什么替代方案吗
I added session.Refresh(ag) and then it works . Is session Refresh an expensive operation ? Is there any alternative