【问题标题】:How correctly do a bi-directional many-to-many in nHibernate在 nHibernate 中如何正确地进行双向多对多
【发布时间】:2013-09-05 00:48:56
【问题描述】:

我试图弄清楚如何在 DotNet 4.5 中的 NHibernate 中正确使用多对多关系。使用什么 Collection 类型以及将它们初始化为什么。

场景:为组织单位和职位建模。与下表存在多对多关系:

组织单位 职位 组织单位职位

我正在使用双向设置器,因此无论我使用 orgUnit.AddPosition(position) 还是 position.AddOrgUnit(orgUnit),都应该将关系添加到两个集合中。

这是我的示例代码

public class Position 
    {
        public Position()
        {
            OrgUnits = new List<OrgUnit>(); 
        }

        public virtual ICollection<OrgUnit> OrgUnits { get; set; }

        public virtual void AddOrgUnit(OrgUnit orgUnit)
        {
            if (!OrgUnits.Contains(orgUnit))
            {
                OrgUnits.Add(orgUnit);
                if (!orgUnit.Positions.Contains(this))
                    orgUnit.AddPosition(this);
            }
        }


        public class PositionMap : ClassMap<Position>
        {
            public PositionMap()
            {
                    HasManyToMany(x => x.OrgUnits)
                    .ChildKeyColumn("OrgUnitID")
                    .ParentKeyColumn("PositionID")
                    .Table("OrgUnitPositions")
                    .Fetch.Select()
                    .Cascade.AllDeleteOrphan()
                    .AsBag()
                    .Inverse();
            }

        }

        public class OrgUnit
        {
            public OrgUnit()
            {
                Positions = new HashSet<Position>();
            }

            public virtual ICollection<Position> Positions { get; set; }

            public virtual void AddPosition(Position value)
            {
                if (!Positions.Contains(value))
                {
                    Positions.Add(value);
                    if (!value.OrgUnits.Contains(this))
                        value.AddOrgUnit(this);
                }
            }
        }
        public class OrgUnitMap : ClassMap<OrgUnit>
        {
        public OrgUnitMap()
        {
            HasManyToMany(x => x.Positions)
            .ChildKeyColumn("PositionID")
            .ParentKeyColumn("OrgUnitID")
            .Table("OrgUnitPositions")
            .Fetch.Select()
            .Cascade.AllDeleteOrphan()
            .AsSet()
            ;
        }
    }

我的问题是,当我调用 orgUnit.AddPosition(position)) 时,在某些情况下,Position.AddOrgUnit: if (!value.Positions.Contains(this)) 中的这一行返回 false,即使我可以看到确实包含组织单元的调试器。这会导致它被添加两次,然后在保存时出现重复键异常。

我已经尝试了各种方法(我的原始集合在列表/IList 中),但我怀疑我的集合类型(ilist/iset/etc)是原因 - 希望有人可以指出哪些集合类型的方向使用,将它们初始化为什么,等等。

如果我调用 position.AddOrgUnit(orgUnit);而不是 orgUnit.AddPosition,一切正常。

--- 更新

针对cmets,我将Position.cs中的AddOrgUnit改成如下:

public virtual void AddOrgUnit(OrgUnit orgUnit)
    {
        if (!OrgUnits.Contains(orgUnit))
        {
            OrgUnits.Add(orgUnit);
            foreach (Position p in orgUnit.Positions)
            {
                System.Console.WriteLine(string.Format("{0}", ReferenceEquals(p, this)));
            }

            if (!value.Positions.Contains(this))
            {
                value.AddPosition(this);
            }
        }
    }

这是我发现的 - 请记住 orgUnit.positions 仅包含 1 个项目,而 for-each 只是为了在调试器中访问该项目。

ReferenceEquals(p, this) false

p.GetHashCode() 40148707

this.GetHashCode() 53416668

this.ID {8386857d-a52e-4f17-8094-a231003299b5}

p.ID {8386857d-a52e-4f17-8094-a231003299b5}

p.描述“鲍勃”

this.Description “鲍勃”

p.description = "简"

this.Description “简”

p.description “简”

这很奇怪! hashCodes 是不同的,但它似乎是同一个对象的同一个实例。 IE,如果我更改“this”版本的属性,它会更改“p”版本。

最后, ReferenceEquals(p.ReportsToPosition, this.ReportsToPosition) true

这往往表明它们来自同一个休眠会话? (即父母是一样的)


这是我的调用代码,我们将 StrctureMap 用于 IoC,并将 ISession 注入到存储库中。我已经更改了我的代码以传递 BLL,这保证了相同的 Isession,因为存储库被注入其中。

奇怪的是,导致错误的唯一行是指示的行,如果我删除上面的行,它也可以正常工作!

[TestMethod]
    public void CreateExampleOrgStructure()
    {
        OrgStructureLogic osl = (OrgStructureLogic)ObjectFactory.GetInstance(typeof(OrgStructureLogic));
        Domain domain = osl.GetNewDomain(DomainTypeEnum.ReportingLines);
        domain.Name = string.Format("blah.Net - {0} {1}", DateTime.Now.ToLongDateString(), DateTime.Now.ToLongTimeString());

        OrgUnit ouGlobal = domain.RootOrgUnit;
        ouGlobal.Name = "Global";

        OrgUnit ouTech = GetNewOrgUnit(ouGlobal, "Technology", osl);
        AddPositionToOrgUnit(ouTech, "Chief Technology Officer", osl);
        AddPositionToOrgUnit(ouTech, "General Manager Technology", osl);

        OrgUnit ouFeatureDevelopers = GetNewOrgUnit(ouTech, "Feature Dev", osl);
        AddPositionToOrgUnit(ouFeatureDevelopers, "Senior Feature Developer", osl);
        AddPositionToOrgUnit(ouFeatureDevelopers, "Feature Developer", osl);

        OrgUnit ouSupportDevelopers = GetNewOrgUnit(ouTech, "Support Dev", osl);
        AddPositionToOrgUnit(ouSupportDevelopers, "Senior Support Developer", osl);
        AddPositionToOrgUnit(ouSupportDevelopers, "Support Developer", osl);

        OrgUnit ouDevManagement = GetNewOrgUnit(ouTech, "Management", osl);
        AddPositionToOrgUnit(ouDevManagement, "Dev Manager", osl); /* This is the problem!!!!*/
        AddPositionToOrgUnit(ouDevManagement, "Application Architect", osl);

        osl.SaveDomain(domain);
    }

更奇怪的是我也可以解决正在改变的问题

private void AddPositionToOrgUnit(OrgUnit orgUnit, string positionName, OrgStructureLogic osl)
    {
        orgUnit.AddPosition(GetPositionByName(positionName), osl);
    }

private void AddPositionToOrgUnit(OrgUnit orgUnit, string positionName, OrgStructureLogic osl)
    {       
        Position position = GetPositionByName(positionName, osl);
        position.AddOrgUnit(orgUnit);
    }

【问题讨论】:

  • 您是否尝试过一次比较实体的 ID 而不是参考?我之前也遇到过这种问题,尤其是NHibernate生成Proxy-Classes的时候。有时我覆盖了Equals(和GetHashCode),我在其中比较了实体的ID。
  • 你能显示你调用orgUnit.AddPosition(position)的代码吗?我的猜测是 orgUnitposition 来自两个不同的 NHibernate 会话。当您在调试器中看到它时,您能否检查一下您认为相等的两个实例实际上是否执行ReferenceEquals
  • @sl3dg3 我已经添加了 ID 比较的结果 - 它们是相同的。我对覆盖 Equals/GetHashCode 犹豫不决,因为我喜欢创建空对象的图形,然后通过调用 save(rootObject) 来保存它们 - 即,我希望能够在对象具有空 ID 时进行比较。
  • 我尝试将集合更改为 IESI 的集合并遇到同样的问题
  • @DanielSchilling - 我更改了我的代码以通过 BLL 的引用传递对我的 repos 的引用 - 所以我不认为这是一个不同的 iSession。

标签: c# c#-4.0 nhibernate fluent-nhibernate


【解决方案1】:

随着堆栈跟踪,GetHashCode 的值在此处发生变化:

if (!position.OrgUnits.Contains(this))
{
    position.AddOrgUnit(this);
}

IE,position.GetHashCode() = "a",但是当我进入 position.AddOrgUnit 时,this.GetHashCode() 是 "b"。

查看堆栈跟踪,

[Native to Managed Transition]      NHibernate.dll!NHibernate.Proxy.DefaultLazyInitializer.Intercept(NHibernate.Proxy.DynamicProxy.InvocationInfo info = {NHibernate.Proxy.DynamicProxy.InvocationInfo}) + 0x14d bytes  
PositionProxyModule.mod!PositionProxy.AddOrgUnit() + 0x1f2 bytes    

似乎 nHibernate 代理正在阻碍。因此 – sl3dg3 是对的,解决方案是覆盖 Equals & GetHashCode。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-14
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    相关资源
    最近更新 更多