I have an application storing a tree structure and is able to generate the database fine, but when inserting I get an exception. I simplified the code to narrow down the problem.
The class to be saved:
public class Node
{
public virtual int NodeId { get; set; }
public virtual string Name { get; set; }
public virtual Node Parent { get; set; }
public virtual IList<Node> Children { get; set; }
}
This is the mapper code:
public sealed class NodeMap : ClassMap<Node>
{
public NodeMap()
{
Id(n => n.NodeId).GeneratedBy.Identity();
Map(n => n.Name);
References(n => n.Parent).LazyLoad().Nullable();
HasMany(n => n.Children).LazyLoad().KeyColumn("ParentId");
}
}
When I do an insert like dataClient.Insert(new Node { Name = "Test" }); I get an exception saying:
NHibernate.Exceptions.GenericADOException: could not insert: [Model.Node][SQL: INSERT INTO [Node] (Name, NodeId) VALUES (?, ?); select SCOPE_IDENTITY()]
And the inner exception says:
System.Data.SqlClient.SqlException: Cannot insert explicit value for identity column in table 'Node' when IDENTITY_INSERT is set to OFF.
Which is actually perfectly understandable, but I just wonder why it is trying to set an explicit value for NodeId. When I remove the References part, the insert goes through, so it must be related to that somehow.
The version of FluentNHibernate is 1.2.0.712 and the version of NHibernate is 3.1.0.4000.
it is because the default column is typename + id for the reference which happens to be the same name as the id column. Explicitly state it in the reference.
References(n => n.Parent, "ParentId").LazyLoad().Nullable();
Update: as convention
public class ReferenceColumnConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
// uncomment if needed
//if (instance.EntityType == instance.Property.PropertyType)
instance.Column(instance.Name + "id");
}
}
Related
I am trying to create my own foreign key convention that will name the FK in "FK_SourceTable_TargetTable" format.
However, when I run it I end up with two foreign keys instead of one.
My custom foreign key convention looks like this:
public class OurForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
if (property == null)
return string.Format("FK_{0}Id", type.Name); // many-to-many, one-to-many, join
if (property.Name == type.Name)
return string.Format("FK_{0}_{1}", property.DeclaringType.Name, type.Name);
return string.Format("FK_{0}_{1}_{2}", property.DeclaringType.Name, property.Name, type.Name);
}
}
My code to exercise it:
[TestMethod]
public void ShouldBeAbleToBuildSchemaWithOurConventions()
{
var configuration = new Configuration();
configuration.Configure();
Fluently
.Configure(configuration)
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<Widget>()
.Conventions.Add<OurForeignKeyConvention>()
)
.BuildSessionFactory();
new SchemaExport(configuration).Create(false, true);
}
My classes and mappings:
public class Widget
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual WidgetType Type { get; set; }
public virtual ISet<WidgetFeature> Features { get; set; }
}
public class WidgetFeature
{
public virtual int Id { get; set; }
public virtual Widget Widget { get; set; }
public virtual string FeatureDescription { get; set; }
}
public class WidgetMap : ClassMap<Widget>
{
public WidgetMap()
{
Id(w => w.Id);
Map(w => w.Description);
HasMany(w => w.Features).Cascade.AllDeleteOrphan().Inverse();
}
}
public class WidgetFeatureMap : ClassMap<WidgetFeature>
{
public WidgetFeatureMap()
{
Id(w => w.Id);
Map(w => w.FeatureDescription);
References(w => w.Widget);
}
}
The end result is two foreign keys, one called what I want - FK_WidgetFeature_Widget - and another one called FK_WidgetId.
If I change OurForeignKeyConvention to always return the same name regardless of whether the "property" parameter is null then I correctly get a single FK - but I then cannot get the "SourceTable" part of my FK name.
Can anyone explain what I am doing wrong here? Why is GetKeyName called twice? And why does one of the calls not provide a value for the "property" parameter?
Doh. ForeignKeyConvention provides the name for the FK column. What I should have been using is the IHasManyConvention, which can be used to name the FK constraint itself.
public class OurForeignKeyConstraintNamingConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.ForeignKey(string.Format("FK_{0}_{1}", instance.Relationship.Class.Name, instance.EntityType.Name));
}
}
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);
}
}
I am having trouble using CreateCriteria to add an outer join to a criteria query while using Fluent NHibernate with automapping.
Here are my entities -
public class Table1 : Entity
{
virtual public int tb1_id { get; set; }
virtual public DateTime tb1_date_filed { get; set; }
.
.
.
virtual public IList<Table2> table2 { get; set; }
}
public class Table2: Entity
{
public virtual int tb2_id { get; set; }
public virtual int tb2_seqno { get; set; }
.
.
.
public virtual Table2 table2 { get; set; }
}
I try to use the following to add an outer join to my criteria query -
CreateCriteria("Table2", NHibernate.SqlCommand.JoinType.LeftOuterJoin);
But I am getting an error -
{"EIX000: (-217) Column (tbl1_id) not found in any table in the query (or SLV is undefined)."}
So it seems that it is trying to automatically set the id of the second table, but doesn't know what to set it to. Is there a way that I can specifically set the id? Here is my Session -
var persistenceModel = AutoMap.AssemblyOf<Table1>()
.Override<Table1>(c => {
c.Table("case");
c.Id(x => x.id).Column("tbl1_id");
})
.Where(t => t.Namespace == "MyProject.Data.Entities")
.IgnoreBase<Entity>();
Hope that makes some sense. Thanks for any thoughts.
You seem to have answered your own question so I'm just going to spout some recommendations...
One of the nice things about fluent nhibernate is that it follows conventions to automatically create mappings. Your entities seem to be very coupled to the names of your database tables.
In order to map to a different database convention while keeping idealistic names for entities and columns you can use some custom conventions:
public class CrazyLongBeardedDBATableNamingConvention
: IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.Table("tbl_" + instance.EntityType.Name.ToLower());
}
}
public class CrazyLongBeardedDBAPrimaryKeyNamingConvention
: IIdConvention
{
public void Apply(IIdentityInstance instance)
{
string tableShort = TableNameAbbreviator.Abbreviate(instance.EntityType.Name);
instance.Column(tableShort + "_id");
}
}
class CrazyLongBeardedDBAColumnNamingConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
string name = Regex.Replace(
instance.Name,
"([A-Z])",
"_$1").ToLower();
var tableShort = TableNameAbbreviator.Abbreviate(instance.EntityType.Name);
instance.Column(tableShort + name);
}
}
TableNameAbbreviator is a class that would know how to abbreviate your table names.
These would map from:
public class Table1 : Entity
{
virtual public int Id { get; set; }
virtual public DateTime DateFiled { get; set; }
}
To a table like:
CREATE TABLE tbl_table1 {
tbl1_id INT PRIMARY KEY
tbl1_date_filed datetime
}
I added a HasMany option to the override for my first table to define the relationship to my second table. I then added an override for my second table which defines the id column for that table.
Thank
I need to persist this class on database using Fluent NHibernate:
public class RaccoonCity
{
public virtual int Id { get; private set; }
public virtual DateTime InfectionStart { get; private set; }
private IList<Zombie> _zombies = new List<Zombie>();
public virtual IEnumerable<Zombie> Zombies
{
get { return _zombies; }
}
protected RaccoonCity()
{}
public RaccoonCity(DateTime startMonth)
{
InfectionStart = startMonth;
}
public virtual void AddZombie(Zombie z)
{
_zombies.Add(z);
}
}
The property has type IEnumerable to indicate that you shouldn´t use it to insert new items. The backing field is of IList to make it easy to insert new items from the own class.
Zombie is a simple class:
public class Zombie
{
public virtual int Id { get; private set; }
public virtual string FormerName { get; set; }
public virtual DateTime Infected { get; set; }
}
The map is the following:
public class RaccoonCityMap: ClassMap<RaccoonCity>
{
public RaccoonCityMap()
{
Id(x => x.Id);
Map(x => x.InfectionStart);
HasMany(x => x.Zombies)
.Access.CamelCaseField(Prefix.Underscore)
.Inverse()
.Cascade.All();
}
}
When I test this, the data is inserted in database, but the zombie´s foreign keys are empty, and the RaccoonCity instance has zero items on Zombies list.
You are declaring the relationship as Inverse, which means the Zombie and not the RacoonCity is responsible for maintaining the relationship.
Either add the corresponding reference to zombie and set it on the AddZombie method, or remove the Inverse (in that case, you'll see an INSERT with a null FK followed by an update).
Suggested reading: http://nhibernate.info/doc/nh/en/index.html#collections-onetomany
Found a post about it: https://web.archive.org/web/20090831052429/http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/08/15/a-fluent-interface-to-nhibernate-part-3-mapping.aspx
I had to implement the method
HasManyComponent by myself since it
was missing in the actual trunk of the
framework. That is, it was not
possible to map a collection of value
objects. But it has not been that hard
since the source base is really nice.
My changes will probably be integrated
into the framework soon.
And this one:
http://nhforge.org/blogs/nhibernate/archive/2008/09/06/a-fluent-interface-to-nhibernate-part-3-mapping-relations.aspx
I'm not sure if it's a correct behavior or something done wrong on my side:
I've got a very simple parent-child relationship
public class SubmittedBatch
{
public virtual Guid Id { get; set; }
public virtual IList<SubmittedBatchParameter> Parameters { get; private set; }
}
public class SubmittedBatchParameter
{
public virtual string Value { get; set; }
}
And with FluentNH it is configured like:
mapping.HasMany<SubmittedBatchParameter>(sb => sb.Parameters).Cascade.All();
I do something easy like adding a new element to the Parameters collection and then call SaveOrUpdate on the parent element.
Looking at the trace of the SQL Statements I get an insert:
INSERT INTO [SubmittedBatchParameter]
(Value)
VALUES ('Disabled' /* #p0 */)
select SCOPE_IDENTITY()
and then an update:
UPDATE [SubmittedBatchParameter]
SET SubmittedBatch_id = '209971b7-c311-46bd-b989-9cf80113654c' /* #p0_0 */
WHERE Id = 39 /* #p1_0 */
Why isn't NH just doing the Insert with also the Guid specified?
Is this correct, or am I doing something wrong?
Thank you
Simone
You have not mapped the reverse parent relationship explicitly. Therefore the only way that NHibernate knows to save the SubmittedBatch_id column value is when you save the parent object. It can't save both objects at once, so what it does is to save the child, then when it saves the parent, save the relationship.
In fact, even if you were to map both directions of the relationship, you would still have to specify which is the "master" by marking the other as an "inverse" relationship. The field is then updated by saving the master side.
So, if you were to map a SubmittedBatch property in the SubmittedBatchParameter class, map this as the "master" (i.e. set the collection mapping as the inverse using .Inverse in Fluent), and then set that when you add the parameter to the batch, then you would see just the insert that you expect.
Here's what I mean:
public class SubmittedBatch
{
public virtual Guid Id { get; set; }
public virtual IList<SubmittedBatchParameter> Parameters { get; private set; }
}
public class SubmittedBatchParameter
{
public virtual SubmittedBatch SubmittedBatch { get; set; }
public virtual string Value { get; set; }
}
Then in child mapping:
HasMany<SubmittedBatchParameter>(sb => sb.Parameters).Inverse();
and in parent mapping:
References(sbp => sbp.SubmittedBatch);