与NHibernate一到多用级联关系集合问题 [英] problem with NHibernate one-to-many relationship Collection with cascade

查看:117
本文介绍了与NHibernate一到多用级联关系集合问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我AssetGroup实体与实体的资产一个一对多的关系。有它覆盖的Equals和GetHashCode一个实体基类。我下面 20章父子 <为例/ p>

 <?XML版本=1.0编码=UTF-8>?; 
<休眠映射的xmlns =金塔:NHibernate的映射 - 2.2
装配=TestNHibernate
命名空间=TestNHibernate.Models自动导入=真正的>
<类名=AssetGroup>
< ID名称=ID列=IDTYPE =GUID>
<生成器类=GUID>< /发电机>
< / ID>
<属性名=姓名类型=字符串不空=真/>
<集名称=资产级联=所有逆=真访问=field.camelcase下划线懒=真>
<键列=AssetGroupID/>
<一到多级=资产/>
< /集>
< /班>
< /休眠映射>

< XML版本=1.0编码=UTF-8>?;
<休眠映射的xmlns =金塔:NHibernate的映射 - 2.2
装配=TestNHibernate
命名空间=TestNHibernate.Models自动导入=真正的>
<类名=资产>
< ID名称=ID列=IDTYPE =GUID>
<生成器类=GUID>< /发电机>
< / ID>
<属性名=姓名类型=字符串不空=真/>
<多到一个名称=AssetGroup栏=AssetGroupID级联=所有懒人=FALSE/>

< /班>
< /休眠映射>



中的代码,folloow:

 公共类AssetGroup:实体<&的Guid GT; 
{
公共AssetGroup()
{
this._assets =新HashedSet<资产与GT;();
}


虚拟公共字符串名称{;组; }

私人的ISet<资产与GT; _资产;
虚拟公众的ISet<资产与GT;资产
{
{返回_assets; }
保护集合{_assets =价值; }
}

虚拟公共BOOL AddAsset(资产资产)
{
如果(资产= NULL&放大器;!&安培; _assets.Add(资产))
{
asset.SetAssetGroup(本);
返回真;
}
返回FALSE;
}

虚拟公共BOOL RemoveAsset(资产资产)
{
如果(资产= NULL&放大器;!&安培; _assets.Remove(资产))
{
asset.SetAssetGroup(NULL);
返回真;
}
返回FALSE;
}
}

公共类AssetGroup:实体<&的Guid GT;
{
公共AssetGroup()
{
this._assets =新HashedSet<资产与GT;();
}

虚拟公共字符串名称{;组; }

私人的ISet<资产与GT; _资产;
虚拟公众的ISet<资产与GT;资产
{
{返回_assets; }
保护集合{_assets =价值; }
}

虚拟公共BOOL AddAsset(资产资产)
{
如果(资产= NULL&放大器;!&安培; _assets.Add(资产))
{
asset.SetAssetGroup(本);
返回真;
}
返回FALSE;
}

虚拟公共BOOL RemoveAsset(资产资产)
{
如果(资产= NULL&放大器;!&安培; _assets.Remove(资产))
{
asset.SetAssetGroup(NULL);
返回真;
}
返回FALSE;
}
}



我TestCode情况如下:

  [TestMethod的] 
公共无效Can_Use_ISession()
{
的ISession会议= TestConfig.SessionFactory.GetCurrentSession() ;
变种AG =新AssetGroup {名称=NHSession};
session.Save(AG);

变种A1 =新的资产{名称=S1};
变种A2 =新的资产{名称=S2};

a1.SetAssetGroup(AG);
a2.SetAssetGroup(AG);

调用Session.flush();

Assert.IsTrue(!a1.Id =默认(GUID)); // OK
Assert.IsTrue(!a2.Id =默认(GUID)); // OK

VAR枚举= ag.Assets.GetEnumerator();
enumerator.MoveNext();
Assert.IsTrue(ag.Assets.Contains(enumerator.Current)); //未能

Assert.IsTrue(ag.Assets.Contains(A1)); //未能
Assert.IsTrue(ag.Assets.Contains(A2)); //失败

变种agRepo2 =新NHibernateRepository< AssetGroup>(TestConfig.SessionFactory,新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<资产与GT;(a1.Id);
VAR AA2 = session.Get<资产与GT;(a2.Id);
Assert.IsTrue(ag2.Assets.Contains(AA1)); //失败
Assert.IsTrue(ag2.Assets.Contains(AA2)); //失败

}



我的实体基类是在这里:

 公共抽象类实体LT; TID> :IEquatable<实体LT; TID>> 
{
[HiddenInput(DisplayValue = FALSE)]
公共虚拟TID标识{搞定;保护套; }

公众覆盖布尔等于(obj对象)
{
如果(OBJ == NULL)
返回base.Equals(OBJ);
收益等于(OBJ作为实体LT; TID>);
}

公共静态布尔IsTransient(实体<&TID GT; OBJ)
{
回报的obj = NULL&放大器;!&安培;等于(obj.Id,默认(TID));
}

私有类型GetUnproxiedType()
{
返回的GetType();
}

公共虚拟BOOL等于(实体< TID>其他)
{
如果(的ReferenceEquals(这一点,其他))
返回真;
如果(IsTransient(本)及!&安培;!IsTransient(其他)及和放大器;等于(身份证,other.Id))
{
VAR OTHERTYPE = other.GetUnproxiedType() ;
VAR thisType = GetUnproxiedType();
返回thisType.IsAssignableFrom(OTHERTYPE)|| otherType.IsAssignableFrom(thisType);
}
返回FALSE;
}

公共覆盖INT的GetHashCode()
{
如果(等号(ID,默认值(TID)))
{
返回base.GetHashCode();
}
,否则
{
返回Id.GetHashCode();
}
}

}



我有注释哪些部分已经在代码的失败。请帮忙。看来这是由级联保存实体是不兼容的ICollection包含/删除。资产A1 + A2被保存,他们是父母的集合中。例如,我可以通过LINQ的FirstOrDefault找到它们。但集合的包含和删除将无法找到他们。我注意到收集利用的GetHashCode同时包含()或删除()被调用。


解决方案

  Assert.IsTrue(ag.Assets.Contains(A1)); //失败

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



在AssetGroup:

 虚拟公共BOOL AddAsset(资产资产)
{
如果(资产= NULL&放大器;!&安培; _assets.Add(资产))
{
asset.AssetGroup =这一点;
返回真;
}
返回FALSE;
}

......和相应的删除

在资产:

 虚拟公共BOOL SetAssetGroup(AssetGroup组)
{
this.AssetGroup =组;
group.Assets.Add(本);
}

请注意您的代码和上面的区别之一。
这是没有这样做的唯一途径,但它是这样做的最映射无关的,安全的方式......所以不管你设置逆=真正在你的映射或没有,它会工作。我这样做,在默认情况下,甚至没有考虑太多。



在与模型的从外面,您使用AddXXX,RemoveXXX工作,一些SetXXX。当从内部模型的工作您引用的直接属性和集合。采用这是一个惯例,你会没事的最常见的双向映射方案。



话虽如此,我不知道为什么这个代码失败:

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



AG2是一个新的查询,所以这应该是确定...除非会话缓存的对象,我不认为它......但我不知道。


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.

解决方案

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.

这篇关于与NHibernate一到多用级联关系集合问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆