I know that I can Map(x => x.GroupName).WithUniqueConstraint() for a single property.
But how do create a composite unique constraint in fluent nHibernate (where the unique constraint operates on the combination of two columns)?
In the latest version that I have used, it isUniqueKey("KeyName")that does this.
Map(x => x.Something).UniqueKey("KeyName");
Map(x => x.SomeOtherThing).UniqueKey("KeyName");
Use SetAttribute in your mapping file like so:
Map(x => x.Something).SetAttribute("unique-key", "someKey");
Map(x => x.SomeOtherThing).SetAttribute("unique-key", "someKey");
Related
I am sure someone has to have done this before - looking at the previous queries it possibly cannot be done with Fluent - but here goes:
I have the following mappings
As you can probably spot - this is not going to work because the keys on the child table do not match the parent table and so the error will be that the number of fields on the foreign key contract don't match. However, I cannot change the underlying tables (and the relationship is a valid one). Is there a way around this in fluent nhibernate so I can somehow ignore the expected join on both composite fields and only join on the one which matches (i.e field_one?) Hibernate expects that field one and field two are present on both mappings.
public ParentMap()
{
Table("dbo.SOMEPARENT");
OptimisticLock.None();
LazyLoad();
CompositeId()
.KeyReference(x => x.FieldOne, x => x.Access.CamelCaseField(Prefix.Underscore), "field_one")
.KeyReference(x => x.FieldTwo, x => x.Access.CamelCaseField(Prefix.Underscore), "field_two");
HasMany(x => x.ChildData)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.AllDeleteOrphan()
.KeyColumns.Add("field_one")
.NotFound.Ignore();
}
public ChildDataMap()
{
Table("dbo.SOMECHILD");
OptimisticLock.None();
LazyLoad();
CompositeId()
.KeyProperty(x => x.FieldOne, x => x.Access.CamelCaseField(Prefix.Underscore).ColumnName("field_one"))
.KeyProperty(x => x.FieldTwo, x => x.Access.CamelCaseField(Prefix.Underscore).ColumnName("field_two"))
.KeyProperty(x => x.FieldThree, x => x.Access.CamelCaseField(Prefix.Underscore).ColumnName("field_three"))
.KeyProperty(x => x.FieldFour, x => x.Access.CamelCaseField(Prefix.Underscore).ColumnName("field_four"));
Map(x=>x.DontExecTrigger).Column("dont_exec_trigger").Access.CamelCaseField(Prefix.Underscore);
Map(x=>x.DateField).Column("date_field").Access.CamelCaseField(Prefix.Underscore);
Map(x=>x.DataField).Column("data_field").Access.CamelCaseField(Prefix.Underscore);
References(x => x.Parent)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.All()
.Fetch.Select()
.Columns("field_one")
.NotFound.Ignore()
.Not.LazyLoad();
}
From a purely database point of view, if a ChildDataMap FieldOne value always appears in ParentMap FieldOne and FieldOne is unique in ParentMap then there is a foreign key from the former to the latter. But also it means FieldOne is a candidate key of (unique in) ParentMap.
You should declare the FK and UNIQUE database constraints. You say you can't change the table but if the constraint is valid then it is not going to affect any useres of it. That ought to allow you to declare that ChildDataMap FirstOne References ParentMap FirstOne. The two-column set can still be declared PK (assuming it is). But if ParentMap FieldTwo is also unique, it should be declared UNIQUE also and so become available as one-column FK target.
Maybe the UNIQUE constraint and/or FK are already declared. Maybe you should just be declaring the corresponding unique property for FirstOne in ParentMap. The KeyColumns.Add ought rely on its being there and allow you to declare the References in ChildDataMap. But I guess that's where your code is not valid.
If Hibernate doesn't automatically join on same-named fields then you could just not declare the References and do your own join on the one field to get a set of references of which of course there will be only one element.
It's not clear what you mean by "you can't change the tables" (does that include, you can't have valid constraints added?) or "the relationship is a valid one" (does that mean, FieldOne in Parent is unique?). It would also help if you posted all the relevant code: namely the table delarations including constraints.
If you can have a new table defined, you can add StricterParentMap with the added constraints and have ParentMap declared as a view of it. This wouldn't affect others' use of it and might not count as "can't change".
I am upgrading to NHibernate 3.2. I was using Fluent NHibernate but I don't see a new build for NH 3.2. I am looking at using the included Conform mapper but it does not appear to allow for a composite id. I can't change the database so I have a constraint.
In Fluent NHibernate I had this (names changed for example only):
Schema("MY_SCHEMA");
Table("MY_TABLE");
CompositeId()
.KeyProperty(x => x.CompanyId, "COMPANY_ID")
.KeyProperty(x => x.OrderId, "ORDER_ID")
.KeyProperty(x => x.OrderDate, "ORDER_DATE")
.KeyProperty(x => x.ProgramId, "PROGRAM_ID");
How would I do this with Conform in NH 3.2?
Thanks,
Paul
You can try with:
mapper.Class<YourEntity>(m=>{
m.Table("MY_TABLE");
m.Schema("MY_SCHEMA");
m.ComposedId(cid=>
{
cid.Property((e)=>e.CompanyId);
cid.Property((e)=>e.OrderId);
cid.Property((e)=>e.OrderDate);
//others...
});
});
And, I'm just guessing since I can't figura out your db, you would probably map the single portion of the key a many-to-one ( ie the old key-many-to-one you would write in hbm ), in order to do so, use cid.ManyToOne() instead of cid.Property(..);
I need some help.
I'm just starting out with NHibernate and I'm using Fluent for mappings. Everything seemed to work fine until today.
Here is the story:
I have two tables in my db: Store and WorkDay
The first table contains info about the store, and the WorkDay table contains info about the days of week and start/end time when the store is open.
Store contains a Guid StoreID PK column that is referenced in the WorkDay table.
So I have a mapping file for Store where I have a HasMany association with the WorkDay table, and a corresponding POCO for Store.
Now, when I fill in all the necessary data and try to persist it to database, I get an exception telling me that the insert into table WorkDay failed because the StoreID had null value and the table constraint doesn't allow nulls for that column (which is, of course, expected behavior).
I understand the reason for this exception, but I don't know how to solve it.
The reason why the insert fails is because the StoreID gets generated upon insert, but the [b]WorkDay[/b] collection gets saved first, in the time when the StoreID hasn't yet been generated!
So, how do I force NHibernate to generate this ID to pass it to dependent tables? Or is there another solution for this?
Thank you!
Here's the code for StoreMap
public class StoreMap : ClassMap<Store> {
public StoreMap() {
Id(x => x.StoreID)
.GeneratedBy.GuidComb();
Map(x => x.City);
Map(x => x.Description);
Map(x => x.Email);
Map(x => x.Fax);
Map(x => x.ImageData).CustomType("BinaryBlob");
Map(x => x.ImageMimeType);
Map(x => x.Name);
Map(x => x.Phone);
Map(x => x.Street);
Map(x => x.Zip);
HasMany(x => x.WorkDays)
.Inverse().KeyColumn("StoreID").ForeignKeyCascadeOnDelete()
.Cascade.All();
}
}
and this is for the WorkDayMap
public class WorkDayMap : ClassMap<WorkDay>{
public WorkDayMap() {
Id(x => x.WorkDayID)
.GeneratedBy.Identity();
Map(x => x.TimeOpen);
Map(x => x.TimeClose);
References(x => x.Store).Column("StoreID");
References(x => x.Day).Column("DayID");
}
}
NHibernate shouldn't insert the WorkDay first, so there must be an error in your code. Make sure you do all of the following:
Add all WorkDay objects to the WorkDays collection.
Set the Store property on all WorkDay objects to the parent object.
Call session.Save() for the Store but not for the WorkDay objects.
edit: You should also note that ForeignKeyCascadeOnDelete() won't change anything at runtime. This is just an attribute for the hbm2ddl tool. If you want NHibernate to delete removed entries, use Cascade.AllDeleteOrphan().
It probably inserts the Workday before the Store because the first has an identity generator. This forces NH to execute an INSERT statement to generate the ID. guid.comb is generated in memory, NH doesn't need the database.
NH tries to access the db at the latest possible point in time, to avoid unnecessary updates and to make use of batches. Workday is inserted when you call session.Save, Store is inserted when it flushes the session next time (eg. on commit).
You shouldn't use the identity generator (unless you are forced to use it). It is bad for performance anyway.
If it still doesn't work, you probably need to remove the not-null constraint on the foreign key. NH can't resolve it every time. There is some smart algorithm which is also able to cope with recursive references. But there are sometimes cases where it sets a foreign key to null and updates it later even if there would be a solution to avoid it.
I have the following fluent mapping set up for an entity:
*Id(x => x.Id);
References(x => x.UserNominee).UniqueKey("UQ_SurveyNominee");
References(x => x.SurveyRequest).UniqueKey("UQ_SurveyNominee");
Map(x => x.NominationDate).Not.Nullable();*
Unfortunately the unique index is only created on one of the columns on the resulting SQL Server table and not both of them as I expected. What am I doing wrong?
Regards
mjj
OK I have managed to get this to work but I am not sure why it should make any difference. I had to change the mapping on the parent "SurveyRequest" entity. I changed the mapping from:
HasMany(x => x.SurveyAwarenessNominees)
.KeyColumn("SurveyRequest_Id")
.LazyLoad()
.Inverse()
;
to
HasMany(x => x.SurveyAwarenessNominees).Cascade.All().Inverse();
My unique index is now correctly created on the two foreign key columns.
I have this table mapping (details don't really matter I think):
WithTable("COPACKER_FACILITY");
Id(x => x.FacilityNumber, "FACILITY_NUM").GeneratedBy.Sequence("FACSEQ");
Map(x => x.FacilityName, "FACILITY_NAME").Not.Nullable().Trimmed();
Map(x => x.AddressLine1, "ADDR1").Not.Nullable().Trimmed();
...
WithTable("FACIL_OTH_AUDIT_INFO", m =>
{
m.WithKeyColumn("FACILITY_NUM");
m.Map(x => x.ProdnShiftsNum, "PRODN_SHIFTS_NUM").Not.Nullable();
m.Map(x => x.ProdnCapacity, "PRODN_CAPACITY").Not.Nullable();
m.Map(x => x.ProdnLinesNum, "PRODN_LINES_NUM").Not.Nullable();
m.Map(x => x.AuditScore, "AUDIT_SCORE");
m.References(x => x.FacilStatus, "STATUS_IND").Not.Nullable();
});
HasMany(x => x.ComplianceFlags)
.KeyColumnNames.Add("FACILITY_NUM")
.Inverse()
.Cascade.All();
...
The reason for the one to one table is for audit reasons. There's a FACIL_OTH_AUDIT_INFO_HIST table that should get a record for every insert and update in the main table.
My question: How can I know when an insert or update happens in that table so I know to insert an audit record?
Many thanks!
+1 to what kvalcanti said... here's another post that I think explains it a little better though (and shows you how to do it without XML configuration!). I'm doing what this guy is doing on my project and it's working really well.
http://www.codinginstinct.com/2008/04/nhibernate-20-events-and-listeners.html
Caveat: I'm not inserting new objects that need to be saved in this event in my project, which I assume will not be a problem, but I can't say for sure since I'm not doing exactly what you're doing.
I posted the final solution to this problem and thought I'd share
http://robtennyson.us/post/2009/08/23/NHibernate-Interceptors.aspx
You can use event listeners.
try http://nhibernate.info/doc/howto/various/creating-an-audit-log-using-nhibernate-events.html