Double many-to-one?
I’m working on a mapping for a project at work. The entity names have been changed to protect the innocent.
In the application I’m working on, the model is more like glasses with a left and right lens, but I’ve simplified it.
Public Class Monocle
Inherits Entity
Private m_LeftLens As Lens
Public Overridable Property Lens() As Lens
Get
Return m_LeftLens
End Get
Set(ByVal value As Lens)
m_LeftLens = value
End Set
End Property
End Class
Public Class Lens
Inherits Entity
Private m_Glasses As Monocle
Public Overridable Property Monocle() As Monocle
Get
Return m_Glasses
End Get
Set(ByVal value As Monocle)
m_Glasses = value
End Set
End Property
End Class
Public Class MonocleMapping
Inherits ClassMap(Of Monocle)
Public Sub New()
Id(Function(x As Monocle) x.ID).GeneratedBy.GuidComb()
References(Function(x As Monocle) x.Lens) _
.Cascade.All() _
.WithForeignKey("MonocleLens")
End Sub
End Class
Public Class LensMapping
Inherits ClassMap(Of Lens)
Public Sub New()
Id(Function(x As Lens) x.ID).GeneratedBy.GuidComb()
References(Function(x As Lens) x.Monocle) _
.Cascade.All() _
.WithForeignKey("LensMonocle")
End Sub
End Class
<TestFixture()> _
Public Class GlassesMappingTests
<Test()> _
Public Sub CanCascadeSaveFromMonocleToLens()
Dim ID As Guid
Dim Monocle As Monocle
Dim Lens As Lens
Using Scope As New SQLiteDatabaseScope(Of MonocleMapping)
Using Session = Scope.OpenSession
Using Tran = Session.BeginTransaction
Monocle = New Monocle
Lens = New Lens With {.Monocle = Monocle}
Monocle.Lens = Lens
'These are just idiot checks...
Monocle.Should.Not.Be.Null()
Lens.Should.Not.Be.Null()
Monocle.Lens.Should.Be.SameInstanceAs(Lens)
Lens.Monocle.Should.Be.SameInstanceAs(Monocle)
ID = Session.Save(Monocle)
Tran.Commit()
End Using
End Using
Using Session = Scope.OpenSession
Using Tran = Session.BeginTransaction
Monocle = Session.Get(Of Monocle)(ID)
Monocle.Should.Not.Be.Null()
Monocle.ID.Should.Be.EqualTo(ID)
Monocle.Lens.Should.Not.Be.Null()
Monocle.Lens.Should.Not.Be.SameInstanceAs(Lens)
Monocle.Lens.Should.Be.EqualTo(Lens)
Monocle.Lens.Monocle.Should.Be.SameInstanceAs(Monocle)
Tran.Commit()
End Using
End Using
End Using
End Sub
End Class
using NUnit.Framework;
using FluentNHibernate.Mapping;
public class Monocle : Entity
{
private Lens m_Lens;
public virtual Lens Lens {
get { return m_Lens; }
set { m_Lens = value; }
}
}
public class Lens : Entity
{
private Monocle m_Glasses;
public virtual Monocle Monocle {
get { return m_Glasses; }
set { m_Glasses = value; }
}
}
public class MonocleMapping : ClassMap<Monocle>
{
public MonocleMapping()
{
Id((Monocle x) => x.ID).GeneratedBy.GuidComb();
References((Monocle x) => x.Lens).Cascade.All().WithForeignKey("MonocleLens");
}
}
public class LensMapping : ClassMap<Lens>
{
public LensMapping()
{
Id((Lens x) => x.ID).GeneratedBy.GuidComb();
References((Lens x) => x.Monocle).Cascade.All().WithForeignKey("LensMonocle");
}
}
[TestFixture()]
public class GlassesMappingTests
{
[Test()]
public void CanCascadeSaveFromGlassesToLense()
{
Guid ID;
Monocle Monocle;
Lens Lens;
using (SQLiteDatabaseScope<MonocleMapping> Scope = new SQLiteDatabaseScope<MonocleMapping>()) {
using (Session == Scope.OpenSession()) {
using (Tran == Session.BeginTransaction()) {
Monocle = new Monocle();
Lens = new Lens { Monocle = Monocle };
Monocle.Lens = Lens;
//These are just idiot checks...
Monocle.Should.Not.Be.Null();
Lens.Should.Not.Be.Null();
Monocle.Lens.Should.Be.SameInstanceAs(Lens);
Lens.Monocle.Should.Be.SameInstanceAs(Monocle);
ID = Session.Save(Monocle);
Tran.Commit();
}
}
using (Session == Scope.OpenSession) {
using (Tran == Session.BeginTransaction) {
Monocle = Session.Get<Monocle>(ID);
Monocle.Should.Not.Be.Null();
Monocle.ID.Should.Be.EqualTo(ID);
Monocle.Lens.Should.Not.Be.Null();
Monocle.Lens.Should.Not.Be.SameInstanceAs(Lens);
Monocle.Lens.Should.Be.EqualTo(Lens);
Monocle.Lens.Monocle.Should.Be.SameInstanceAs(Monocle);
Tran.Commit();
}
}
}
}
}
Here’s the resulting SQL.
drop table if exists "Monocle" drop table if exists "Lens" create table "Monocle" (ID UNIQUEIDENTIFIER not null, Lens_id UNIQUEIDENTIFIER, primary key (ID)) create table "Lens" (ID UNIQUEIDENTIFIER not null, Monocle_id UNIQUEIDENTIFIER, primary key (ID)) INSERT INTO "Lens" (Monocle_id, ID) VALUES (@p0, @p1); @p0 = '', @p1 = 'f6f37089-a66b-4007-b732-9c6b008d1448' INSERT INTO "Monocle" (Lens_id, ID) VALUES (@p0, @p1); @p0 = 'f6f37089-a66b-4007-b732-9c6b008d1448', @p1 = 'bb2879f6-d27a-4eb0-8bf0-9c6b008d143b' UPDATE "Lens" SET Monocle_id = @p0 WHERE ID = @p1; @p0 = 'bb2879f6-d27a-4eb0-8bf0-9c6b008d143b', @p1 = 'f6f37089-a66b-4007-b732-9c6b008d1448' SELECT monocle0_.ID as ID0_0_, monocle0_.Lens_id as Lens2_0_0_ FROM "Monocle" monocle0_ WHERE monocle0_.ID=@p0; @p0 = 'bb2879f6-d27a-4eb0-8bf0-9c6b008d143b' SELECT lens0_.ID as ID1_0_, lens0_.Monocle_id as Monocle2_1_0_ FROM "Lens" lens0_ WHERE lens0_.ID=@p0; @p0 = 'f6f37089-a66b-4007-b732-9c6b008d1448'
Of course, I think that making this a double many-to-one relationship is probably wrong, but it gets me close to a working solution.
This works, except for the unnecessary UPDATE “Lens” statement. In my mind, since I’m using the GuidComb generator, NHibernate should specify the Monicle_id in the INSERT “Lens” statement. Instead, it’s inserting NULL, then updating it later. This prevents me from putting a NOT NULL constraint on Lens.Monicle_id. It’s been suggested that I have superfluous update, and I agree. However, the prescribed inverse=”true” won’t work. I don’t have a one-to-many relationship to put it on.
I’ve tried using HasOne instead of References to create a bidirectional one-to-one relationship. It didn’t work at all. My database schema didn’t even have FK fields.
Am I missing something or is this double many-to-one as good as it’s going to get?
