I have a child table containing an id to the parent. This is a one to one mapping, but the child table might be missing values. I'm having problems mapping this without getting an error though... I've tried several things; mapping the same column, having distinct properties etc..
Parent table
int id
Child table
int parentid
Parent class
int id
Child class
Parent parent // note I'm referencing parent, not using an int id..
Mapping
Id(x => x.Parent)
.Column("parentid"); // fails
Id(x => x.Parent.Id)
.Column("parentid"); // fails
References(x => x.Parent)
.Column("parentid"); // fails - missing id
// Adding an id field in addition to parent for
// child class (id is then the same as parent.id)
// fails on save
Id( x => x.Id )
.Column("parentid");
References(x => x.Parent)
.Column("parentid");
I would like the child class not to have a distinct Id field, but rather only a reference to parent as there can never be a child without a parent. In the database however, I want to just store the parent's id.
Any ideas how I might do this?
The following works:
Id(x => x.Parent.Id).Column("MemberID");
References(x => x.Parent).Column("MemberID").ReadOnly();
The ReadOnly for the reference is important to not get an exception
EDIT: Wasn't so simple...
My child class still had the Id property being called. Seems the Id reference for Parent.Id confuses nhibernate, and it tries to call child.Id instead.
I added the following to child, and now it seems to work.. A pretty ugly hack though.
public virtual int Id {
get { return Parent.Id; }
set { Debug.Assert(value == Parent.Id); }
}
FluentNHibernate's API has changed over the years so I'm not sure if this syntax was available when this question was originally asked but you can now use a reference as an id if you map it as a composite id. I wouldn't call this a hack but it is a little strange that you have to map the reference to the parent entity as part of a composite id. Here's a full example:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table( "StackOverflowExamples.dbo.Parent" );
Id( x => x.ParentId );
Map( x => x.FirstName );
Map( x => x.LastName );
}
}
public class OnlyChildOfParentMap : ClassMap<OnlyChildOfParent>
{
public OnlyChildOfParentMap()
{
Table( "StackOverflowExamples.dbo.OnlyChildOfParent" );
CompositeId().KeyReference( x => x.Parent, "ParentId" );
Map( x => x.SomeStuff );
Map( x => x.SomeOtherStuff );
}
}
public class Parent
{
public virtual int ParentId { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class OnlyChildOfParent
{
public virtual Parent Parent { get; set; }
public virtual string SomeStuff { get; set; }
public virtual string SomeOtherStuff { get; set; }
#region Overrides
public override bool Equals( object obj )
{
if ( obj == null || GetType() != obj.GetType() )
return false;
var child = obj as OnlyChildOfParent;
if ( child != null && child.Parent != null )
{
return child.Parent.ParentId == Parent.ParentId;
}
return false;
}
public override int GetHashCode()
{
return Parent.ParentId;
}
#endregion Overrides
}
Maybe this post can help.I've used the annotation .Cascade.SaveUpdate().My case was with a hasone in the parent putting the annotation on both sides
Obs: Language PT-BR
Related
I have a parent class and a child class. One Child is always related to just one parent, but a parent can have multiple children:
public class Parent
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public virtual int Id { get; set; }
public virtual string ParentId { get; set; }
public virtual string Name { get; set; }
}
I'm using the latest version of NHibernate 5.1.3 and mapping by code:
internal class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Table("Parent");
Id(x => x.Id);
Property(x => x.Name);
Bag(
x => x.Children,
map =>
{
map.Key(km => km.Column("ParentId"));
map.Lazy(CollectionLazy.NoLazy);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany());
}
}
internal class ChildMapping : ClassMapping<Child>
{
public ChildMapping()
{
Table("Child");
Id(x => x.Id, x => x.Generator(Generators.Identity));
Property(x => x.ParentId);
Property(x => x.Name);
}
}
Queries work but are quite inefficient. Instead of creating a single JOIN statement to query the children together with their parent, an explicit SELECT is made to retrieve the Child objects.
Even worse, inserts result in the following error:
NHibernate.StaleStateException: 'Batch update returned unexpected row count from update; actual row count: 0; expected: 3'
Here's a sample of a query:
using (var session = _sessionProvider.GetSession())
return session.Query<T>().ToList();
And that's the code to save a new item:
using (var session = _sessionProvider.GetSession())
{
session.Transaction.Begin();
session.Save(newEntity);
session.Transaction.Commit();
}
So everything's pretty easy.
I assume, the Bag() configuration in ParentMapping needs to be fixed. What am I doing wrong?
Change the fetch strategy of the bag mapping to join will generate a join query, like this:
Bag(
e => e.Children,
map => {
map.Key(km => km.Column("ParentId"));
// map.Lazy(CollectionLazy.NoLazy);
// change fetch str
map.Fetch(CollectionFetchMode.Join);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany()
);
And you have the collection persist as inverse (map.Inverse(true);), you should have a many to one mapping for Parent in your Child class like this:
public class Child {
public virtual int Id { get; set; }
// public virtual string ParentId { get; set; }
public virtual Parent Parent { get; set; }
public virtual string Name { get; set; }
}
Then map the Parent property as ManyToOne like this:
public ChildMapping() {
// other mapping goes here
ManyToOne(
x => x.Parent,
map => {
map.Column("ParentId");
map.Class(typeof(Parent));
map.Fetch(FetchKind.Join);
}
);
}
But nhibernate do not include children of parent by default (maybe it is too heavy), and if you want to query children with parent instance, you can query like this:
using (var session = OpenSession()) {
var query = session.Query<Parent>().Select(p => new {
Parent = p, Children = p.Children
});
var data = query.ToList();
}
For saving entities to database, should do like this:
try {
// save parent first
var parent = new Parent();
parent.Name = "Parent object";
session.Save(parent);
// then save child
var child = new Child();
child.Name = "Child object";
child.Parent = parent;
session.Save(child);
session.Flush();
tx.Commit();
}
catch (Exception) {
tx.Rollback();
throw;
}
I am trying to create a parent object that has multiple children in a 1 to many relationship. I am not referencing the Parent object on the child object, instead I am mapping its keycolumn as a field.
When I try to save this object for the first time, it works as expected without any issues (cascading all the Id's and children). When I try to get the object from the database, update some properties on it and re-save it again, it fails. The actual error message I am getting is "Could not delete collection".
The error above is due to the fact that it is trying to set the "ParentId" field on the child objects to NULL (which violates the FK constraint I have in the db). If I remove this constraint from the DB, the end result is what I want; however, I do not want it to perform this update (setting parent id to null) at all and I'm not sure why it is. From what I can tell in the SQL code it is generating and sending to the DB, everything else appears to be correct and it would all work if it wasnt for that last update statement.
Obviously, I must have something wrong with my mapping but I cannot figure out what. I tried adding Not.KeyUpdate() but that simply made it not generate a key at all. Does anyone have any ideas what I am doing wrong??
Thanks in advance, I really appreciate it!!!
Please see below for my mapping:
public class Parent
{
public Parent()
{
Children = new List<Child>();
}
public virtual Guid Id { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class Child
{
public virtual Guid Id { get; set; }
public virtual Guid ParentId { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("Parent");
Id(x => x.Id);
HasMany(x => x.Children).KeyColumn("ParentId").Cascade.SaveUpdate().Not.LazyLoad();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("Child");
Id(x => x.Id);
Map(x => x.ParentId);
}
}
This is caused by the fact, that the Collection of children is not marked as inverse="true".
What you can do is: I. to remove the constraint from DB. NHiberante simply must (without the inverse setting) do 2 steps. Firstly update record to break relation, secondly (due to cascades) alse delete the item
II. Change the mapping, and entities. Like this:
A Child must have reference to the Parent:
public class Child
{
public virtual Guid Id { get; set; }
public virtual Guid ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Mapping then will be like this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table("Parent");
Id(x => x.Id);
HasMany(x => x.Children)
.KeyColumn("ParentId")
.Cascade.SaveUpdate()
.Not.LazyLoad()
.Inverse() // here we do have the inverse setting
;
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Table("Child");
Id(x => x.Id);
Map(x => x.ParentId).Not.Insert().Not.Update(); // this is readonly now
References(x => x.Parent).Column("ParentId"); // just a Parent is writable
}
}
Now, you have to all the time properly set the relation in the C# code. I.e. if child is added to Parents collection, it should also get set the Parent reference
parent.Children.Add(child);
child.Parent = parent;
NHibernate will now issue only one statement, to delete child from its table
I have this 2 objects:
public class Parent
{
public virtual int Poid { get; set; }
public virtual IEnumerable<Child> Child { get; set; }
}
public class Child
{
public virtual int Poid { get; set; }
public virtual string Name {get; set;}
}
I want to use NHibernet QueryOver API to get a child based on the Parent Id and Child Id, That's mean something like give me the child with Id = x belonging to the parent with Id = y.
I tried something like this:
return Session.QueryOver<Parent>().Where(p => p.Poid == y)
.JoinQueryOver(p => p.WishesLists)
.Where(c => c.Poid == x)
.SingleOrDefault<Child>();
But I'm getting an exception that is not possible to convert an object of type Child to Parent.
How is the correct form to QueryOver starting with a Parent Entity but return a Child Entity?
I don't know if this is possible with QueryOver, I worked at it for a while without getting anywhere. It is possible with LINQ:
var child = session.Query<Parent>()
.Where(p => p.Poid == y)
.SelectMany(p => p.WishesLists)
.SingleOrDefault(c => c.Poid == x);
I strongly prefer the LINQ syntax over QueryOver.
See also NH-3176
I'm trying to figure out how to use CompositeId to map another class. Here's a test case:
The tables:
TestParent:
TestParentId (PK)
FavoriteColor
TestChild:
TestParentId (PK)
ChildName (PK)
Age
The classes in C#:
public class TestParent
{
public TestParent()
{
TestChildList = new List<TestChild>();
}
public virtual int TestParentId { get; set; }
public virtual string FavoriteColor { get; set; }
public virtual IList<TestChild> TestChildList { get; set; }
}
public class TestChild
{
public virtual TestParent Parent { get; set; }
public virtual string ChildName { get; set; }
public virtual int Age { get; set; }
public override int GetHashCode()
{
return Parent.GetHashCode() ^ ChildName.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is TestChild)
{
var toCompare = obj as TestChild;
return this.GetHashCode() != toCompare.GetHashCode();
}
return false;
}
}
The Fluent NHibernate maps:
public class TestParentMap : ClassMap<TestParent>
{
public TestParentMap()
{
Table("TestParent");
Id(x => x.TestParentId).Column("TestParentId").GeneratedBy.Native();
Map(x => x.FavoriteColor);
HasMany(x => x.TestChildList).KeyColumn("TestParentId").Inverse().Cascade.None();
}
}
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId"); /** breaks insert **/
}
}
When I try to add a new record, I get this error:
System.ArgumentOutOfRangeException :
Index was out of range. Must be
non-negative and less than the size of
the collection. Parameter name: index
I know this error is due to the TestParentId column being mapped in the CompositeId and References calls. However, removing the References call causes another error when querying TestChild based on the TestParentId.
Here's the code that does the queries:
var session = _sessionBuilder.GetSession();
using (var tx = session.BeginTransaction())
{
// create parent
var p = new TestParent() { FavoriteColor = "Red" };
session.Save(p);
// creat child
var c = new TestChild()
{
ChildName = "First child",
Parent = p,
Age = 4
};
session.Save(c); // breaks with References call in TestChildMap
tx.Commit();
}
// breaks without the References call in TestChildMap
var children = _sessionBuilder.GetSession().CreateCriteria<TestChild>()
.CreateAlias("Parent", "p")
.Add(Restrictions.Eq("p.TestParentId", 1))
.List<TestChild>();
Any ideas on how to create a composite key for this scenario?
I found a better solution that will allow querying and inserting. The key is updating the map for TestChild to not insert records. The new map is:
public class TestChildMap : ClassMap<TestChild>
{
public TestChildMap()
{
Table("TestChild");
CompositeId()
.KeyProperty(x => x.ChildName, "ChildName")
.KeyReference(x => x.Parent, "TestParentId");
Map(x => x.Age);
References(x => x.Parent, "TestParentId")
.Not.Insert(); // will avoid "Index was out of range" error on insert
}
}
Any reason you can't modify your query to just be
_sessionBuilder.GetSession().CreateCriteria<TestChild>()
.Add(Restrictions.Eq("Parent.TestParentId", 1))
.List<TestChild>()
Then get rid of the reference?
I'm wanting to have a 1 to many relationship in NHibernate where the Child table only has access to it's parentsId. Or the foreign key in the DB.
I've tried the following setup:
public class ParentTable
{
public ParentTable()
{
_childRecords = new List<ChildTable>();
}
public virtual int ParentId { get; set; }
private IList<ChildTable> _childRecords;
public virtual IEnumerable<ChildTable> ChildRecords
{
get { return _childRecords; }
}
public void AddChildTable(string value)
{
_childRecords.Add(new ChildTable{ StringField = value });
}
}
public class ChildTable
{
public virtual int ChildTableId { get; set; }
public virtual string StringField { get; set; }
public virtual int ParentId { get; set; }
}
Mappings:
public class ParentTableMap : ClassMap<ParentTable>
{
public ParentTableMap()
{
Not.LazyLoad();
Id(x => x.ParentId);
HasMany(x => x.ChildRecords)
.Not.LazyLoad()
.KeyColumn("ParentId").Cascade.All()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
public class ChildTableMap : ClassMap<ChildTable>
{
public ChildTableMap()
{
Not.LazyLoad();
Id(x => x.ChildTableId);
Map(x => x.StringField);
Map(x => x.ParentId).Not.Nullable();
}
}
The following test fails as it's trying to insert 0 into the ParentId column?
[TestFixture]
public class Tests
{
[Test]
public void SaveOrUpdate_ParentWithChildren_WillCreateParentWithChildRecordsHavingMatchingParentId()
{
int id;
using (var sessionForInsert = SessionProvider.SessionFactory.OpenSession())
{
using (var trx = sessionForInsert.BeginTransaction())
{
//Assign
var parent = new ParentTable();
parent.AddChildTable("Testing");
parent.AddChildTable("Testing2");
sessionForInsert.SaveOrUpdate(parent); // Fails here with DB constraint error
id = parent.ParentId;
}
}
using (var sessionForSelect = SessionProvider.SessionFactory.OpenSession())
{
//Action
var result = sessionForSelect.Get<ParentTable>(id);
Assert.AreEqual(id, result.ParentId);
Assert.AreEqual(id, result.ChildRecords.First().ParentId);
Assert.AreEqual(id, result.ChildRecords.Last().ParentId);
}
}
}
This is what it's trying to do:
exec sp_executesql N'INSERT INTO ChildTable (StringField, ParentId) VALUES (#p0, #p1); select SCOPE_IDENTITY()',N'#p0 nvarchar,#p1 int',#p0='Testing;,#p1=0
I realise I could set-up a reference to the Parent Class in the Child Class. However I'd like to avoid this if at all possible, due to circular references and the problems that will cause when serializing and de-serializing these classes.
Has anyone successfully set-up and 1 to many relationship like the above?
Thanks
Dave
I think you either need to:
Make the ParentId on ChildTable nullable, or
Change your id generators to something NHibernate can generate.
The second option is nice. Switch to Guid.Comb for your id's. There's a restriction on what object relational mappers can do. Specifically, it is recommended to let NHibernate generate the id's instead of the database. I think this (long) blog post explains it in detail: http://fabiomaulo.blogspot.com/2009/02/nh210-generators-behavior-explained.html.
Good luck!
The problem is that you are attempting to insert a parent and its children in one operation. To do this, NHibernate wants to insert the child records with a null ParentId then update ParentId after the parent record is inserted. This foreign key constraint causes this to fail.
The best solution is to map the relationship from child to parent. You don't have to publicly expose the parent, you could just expose its ParentId as int? if desired.
If that's unacceptable, you should be able to accomplish this by changing the order of operations. First, I would require the ParentId in ChildTable's constructor. Then change the operation order in the test to get it to pass.
public class ChildTable
{
public ChildTable(int parentId) { ParentId = parentId; }
public virtual int ChildTableId { get; set; }
public virtual string StringField { get; set; }
public virtual int ParentId { get; private set; }
}
using (var trx = sessionForInsert.BeginTransaction())
{
//Assign
var parent = new ParentTable();
sessionForInsert.Save(parent);
sessionForInsert.Flush(); // may not be needed
parent.AddChildTable("Testing");
parent.AddChildTable("Testing2");
trx.Commit();
id = parent.ParentId;
}
EDIT:
public class ChildTable
{
private ParentTable _parent;
public ChildTable(Parent parent) { _parent = parent; }
public virtual int ChildTableId { get; set; }
public virtual string StringField { get; set; }
public virtual int? ParentId
{
get { return _parent == null : null ? _parent.ParentId; }
}
}
public class ChildTableMap : ClassMap<ChildTable>
{
public ChildTableMap()
{
Not.LazyLoad();
Id(x => x.ChildTableId);
Map(x => x.StringField);
// From memory, I probably have this syntax wrong...
References(Reveal.Property<ParentTable>("Parent"), "ParentTableId")
.Access.CamelCaseField(Prefix.Underscore);
}
}