Note that there are some explanatory texts on larger screens.

plurals
  1. POHow correctly do a bi-directional many-to-many in nHibernate
    primarykey
    data
    text
    <p>I'm trying to get my head around how to correctly use many-to-many relationships in NHibernate in DotNet 4.5. What Collection types to use and what to init them as.</p> <p>Scenario: Modeling OrgUnits and Positions. There is a many to many relationship with the following tables:</p> <p>OrgUnits Positions OrgUnitPositions</p> <p>I'm using bi-directional setters so that no matter if I go orgUnit.AddPosition(position) OR position.AddOrgUnit(orgUnit), the relationship should be added to both collections.</p> <p>Here is my example code </p> <pre><code>public class Position { public Position() { OrgUnits = new List&lt;OrgUnit&gt;(); } public virtual ICollection&lt;OrgUnit&gt; 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&lt;Position&gt; { public PositionMap() { HasManyToMany(x =&gt; x.OrgUnits) .ChildKeyColumn("OrgUnitID") .ParentKeyColumn("PositionID") .Table("OrgUnitPositions") .Fetch.Select() .Cascade.AllDeleteOrphan() .AsBag() .Inverse(); } } public class OrgUnit { public OrgUnit() { Positions = new HashSet&lt;Position&gt;(); } public virtual ICollection&lt;Position&gt; 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&lt;OrgUnit&gt; { public OrgUnitMap() { HasManyToMany(x =&gt; x.Positions) .ChildKeyColumn("PositionID") .ParentKeyColumn("OrgUnitID") .Table("OrgUnitPositions") .Fetch.Select() .Cascade.AllDeleteOrphan() .AsSet() ; } } </code></pre> <p>My problem is that when I call orgUnit.AddPosition(position)), in some circumstancs, this line in Position.AddOrgUnit: if (!value.Positions.Contains(this)) returns false, even though i can see in the debuger that is does contain the org unit. This leads to it being added twice and then a duplicate key exception occurs on save.</p> <p>I've tried all sorts of things (my original collections where lists/ILists) but I suspect that my collection type (ilist/iset/etc) is the cause - hoping someone can point me in the direction of which collection types to use, what to initlise them as, etc.</p> <p>If I call position.AddOrgUnit(orgUnit); instead of orgUnit.AddPosition, everything works.</p> <p>--- Update</p> <p>In response to comments, I changed theAddOrgUnit in Position.cs as follows:</p> <pre><code>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); } } } </code></pre> <p>and here is what i found - keeping in mind that orgUnit.positions only contains 1 item and the for-each is just to get access to that item in the debugger.</p> <p>ReferenceEquals(p, this) <strong>false</strong></p> <p>p.GetHashCode() <strong>40148707</strong></p> <p>this.GetHashCode() <strong>53416668</strong></p> <p>this.ID <strong>{8386857d-a52e-4f17-8094-a231003299b5}</strong></p> <p>p.ID <strong>{8386857d-a52e-4f17-8094-a231003299b5}</strong></p> <p>p.Description <strong>"bob"</strong></p> <p>this.Description <strong>"bob"</strong></p> <p>p.description = "Jane"</p> <p>this.Description <strong>"Jane"</strong></p> <p>p.description <strong>"Jane"</strong></p> <p>This is the strange one! The hashCodes are different, yet it appears to be the same instance of the same object. IE, if I change a property on the "this" version, it changes in the "p" version.</p> <p>Finally, ReferenceEquals(p.ReportsToPosition, this.ReportsToPosition) <strong>true</strong></p> <p>Which tends to indicate they are from the same hibernate session? (ie, the parents are the same)</p> <hr> <p>Here is my calling code, we use StrctureMap for IoC, with ISession being injected into the repos. I have changed my code to pass in the BLL, which guarantees same Isession because the repositories are injected into it.</p> <p>Strangely, the only line the causes the error is the one indicated, and if I remove lines above, it works perfectly too!</p> <pre><code>[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); } </code></pre> <p>Even stranger is that I can also fix the problem be changing </p> <pre><code>private void AddPositionToOrgUnit(OrgUnit orgUnit, string positionName, OrgStructureLogic osl) { orgUnit.AddPosition(GetPositionByName(positionName), osl); } </code></pre> <p>to </p> <pre><code>private void AddPositionToOrgUnit(OrgUnit orgUnit, string positionName, OrgStructureLogic osl) { Position position = GetPositionByName(positionName, osl); position.AddOrgUnit(orgUnit); } </code></pre>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload