I have this problem where I have a one-to-many relationship and I have to be able to delete the parent entity without it's children being deleted, nor their foreign key column set to NULL. But whenever I try deleting a parent, I get the foreign key constraint violation exception.
NHibernate.Exceptions.GenericADOException: could not execute update query[SQL: delete from [Customer]] ---> System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "FK7867CB245055157F"
I have a similar one-to-one relationship, where I have to be able to delete the entity on one end without the foreign key on the other end to be set to NULL and I solved this problem successfully by using NotFound.Ignore(). I have found several answers suggesting exactly this solution, but it seems to have no effect at all. I use my mapping to build the database BTW.
Here are my entities and mappings:
public class User : Entity
{
...
public virtual Customer Customer { get; set; }
...
}
public class Customer : Entity
{
...
public virtual string CustomerNumber { get; set; }
public virtual User User { get; set; }
public virtual IList<Vehicle> Vehicles { get; set; }
...
}
public class Vehicle : Entity
{
...
public virtual string SerialNumber { get; set; }
public virtual Customer Customer { get; set; }
...
}
I'm using AutoMapping and overrides:
public class UserMappingOverride : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
...
mapping.References(u => u.Customer).Column("CustomerNumber").NotFound.Ignore();
}
}
public class CustomerMappingOverride : IAutoMappingOverride<Customer>
{
public void Override(AutoMapping<Customer> mapping)
{
mapping.Id(u => u.Kundenummer).GeneratedBy.Assigned().Not.Nullable();
mapping.HasOne(u => u.User).PropertyRef(c => c.Customer);
mapping.HasMany(u => u.Vehicles).KeyColumns.Add("CustomerNumber")
.Cascade.None()
.Inverse();
}
}
public class VehicleMappingOverride : IAutoMappingOverride<Vehicle>
{
public void Override(AutoMapping<Vehicle> mapping)
{
mapping.Id(u => u.SerialNumber).GeneratedBy.Assigned().Not.Nullable();
mapping.References(u => u.Customer).Column("CustomerNumber").NotFound.Ignore();
}
}
As said, in the one-to-one relationship, in the mapping of User I use NotFound.Ignore(), which does as promised - allows me to delete a Customer without firing a constraint violation exception and still keep the values of "CustomerNumber" in the User table intact. The mapping of the relationship between the User and the Customer entities, simply does not produce a foreign key constraint in the database when the database is built from the mapping.
But the same thing doesn't work for my one-to-many relationship. Although the mapping is almost the same as my one-to-one relationship, and I use NotFound.Ignore() as suggested in similar questions here, this relationship still produces a foreign key constraint and I get a constraint violation exception when trying to delete a Customer. The only work-around is to manually delete the FK in the database or modify it by setting Enforce Foreign Key Constraint to False.
How can I get NHibernate to either not create this Foreign key constraint, or setting the Enfore Foreign Key Constraint to False, when building the database?
Best Regards
- Nikolaj
BTW: I'm not interested in comments about the overall design of the entities and relationships. They're designed like this based on constraints from the source of data, and this is the only plausible workaround. :-) I've found that a lot of answers in similar posts, focus on the design rater then of the question at hand.
you can not work around the FK in the database. My guess is that there is no FK from User to Customer. If you create the Schema from mappings then you'll need to disable the creation of the FK with mapping.References(u => u.Customer).ForeignKey("none");
Firo's answer pointed me in the right direction as how to get rid of the FK constraint. Adding .ForeignKey("none") to the Vehicle mapping didn't do it though. But adding a similar property to the Customer mapping solved my problem.
So the solution became:
mapping.HasMany(u => u.Vehicles).ForeignKeyConstraintName("none")
Related
I have a situation similar to that described in Fluent NHibernate Mapping not on PK Field
However, the relationship between my tables is described by multiple non-primary key columns.
Imagine Chris Meek's situation but where a Person has a JobType and a Code that, together, should (sorry, it's a legacy database) uniquely describe a Person
Person
------
Id PK
JobType
Code
Name
Order
-----
Id PK
Person_JobType
Person_Code
OrderDetails
Serhat Özgel's answer describes using PropertyRef, but I can't find a way to do that individually for multiple columns. I've tried similar to
class PersonMap : ClassMap<Person>
{
public PersonMap()
{
HasMany(p => p.Order)
.KeyColumns.Add("Person_JobType")
.PropertyRef("JobType")
.KeyColumns.Add("Person_Code")
.PropertyRef("Code")
}
}
But this obviously doesn't work, since KeyColumns.Add() returns another OneToManyPart so PropertyRef() isn't being run against the individual column being added. The second PropertyRef() simply overwrites the first one, and I get the following error:
NHibernate.MappingException : collection foreign key mapping
has wrong number of columns: MyApp.Person.Order type: Int32
I've looked at the various overloads of KeyColumns.Add(),
public TParent Add(string name)
public TParent Add(params string[] names)
public TParent Add(string columnName, Action<ColumnPart> customColumnMapping)
public TParent Add(ColumnMapping column)
Specifically the last two, but couldn't find any way to set PropertyRef individually level for each column :(
Is there a way to do that? Am I going about this the wrong way entirely?
using hbm.xml and FluentNHibernate it is possible with a trick
class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Map(_ => JobTypeAndCode)
.Columns.Add("Person_JobType", "Person_Code")
.ReadOnly()
.LazyLoad() // optional: prevent loading the Columns twice
.Access.None();
HasMany(p => p.Orders)
.KeyColumns.Add("Person_JobType", "Person_Code")
.PropertyRef("JobTypeAndCode")
}
private object JobTypeAndCode { get; set; } // FakeProperty
}
Note: i never got this to work using NHibernate MappingByCode
I am using latest version of Fluent NHibernate automapping. Is there any convention or property I can set to stop creating the foreign key constraints across all the tables? I have nearly 200 classes, So I cannot go to each individual class and property name and set
ForeignKeyConstraintNames("none", "none")
How can we add ForeignKeyConstraintNames("none", "none") in Automapping? I don't want to hardcode the table name or column name. I would like to have the AutoMapping create all the mappings without foreign keys. Basicall don't create any foreign keys across the database. How can we do this?
There is similar POST HERE but the answer was not clear to me.
a simple convention
public class NoForeignKeys : IReferenceConvention, IHasManyConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.ForeignKey("none");
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.ForeignKey("none");
}
}
// use it
AutoMap.AssemblyOf().Conventions
.FromAssembly() or .Add(typeof(NoForeignKeys))
We have the following Domain objects :-
public class UserDevice : BaseObject
{
// different properties to hold data
}
public class DeviceRecipient:BaseObject
{
public virtual UserDevice LastAttemptedDevice{get;set;}
}
Hence the sql schema created based on this using fluent nhibernate automapper is like
DeviceRecipient's table is having primary key of UserDevice as a foreign key i.e UserDevice_Id.
Now, When we try to delete UserDevice object it gives a sql exception for foreign key constraint. What we want to do is :-
Delete the UserDevice object , hence the UserDevice row without deleting the DeviceRecipient as it will be used somewhere else in domain model. We just want to set null to UserDevice_Id column of DeviceRecipient when we delete UserDevice.
We want to do it using fluent nhibernate conventions as we use Automapping.
Any help will be appreciable.. Thanks in advance.!
As I can see you have uni-direction many-to-one relation. So firstly you have to write following override:
public class DeviceRecipientOverride : IAutoMappingOverride<DeviceRecipient>
{
public void Override(AutoMapping<DeviceRecipient> mapping)
{
mapping.References(x => x.LastAttemptedDevice)
.NotFound.Ignore(); // this doing what you want.
}
}
Secondly you could convert it to automapping convention, if you have more places with this behavior.
public class ManyToOneNullableConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
var inspector = (IManyToOneInspector) instance;
// also there you could check the name of the reference like following:
// inspector.Name == LastAttemptedDevice
if (inspector.Nullable)
{
instance.NotFound.Ignore();
}
}
}
EDIT:
From the NHibernate reference
not-found (optional - defaults to exception): Specifies how foreign
keys that reference missing rows will be handled: ignore will treat a
missing row as a null association.
So when you set not-found="ignore" SchemaExport/SchemaUpdate will just not create the FK for you. So if you have the FK then you need to delete it or set OnDelete behavior of the FK to Set Null. Assuming that you are using Microsoft Sql Server:
ALTER TABLE [DeviceRecipient]
ADD CONSTRAINT [FK_DeviceRecipient_LastAttemptedDevice]
FOREIGN KEY ([LastAttemptedDevice_ID])
REFERENCES [UserDevice]
ON DELETE SET NULL
I had a similar question to Fluent NHibernate: How to create one-to-many bidirectional mapping? but I was interested in the situation when I have a one-to-one mapping. For instance
Umbrealla
ID
Owner
UmbreallaOwner
ID
Umbrella
As we know each umbrella can only be owned by one person and nobody owns more than one umbrella. In a fluent map I would have something like
UmbrellaMap()
{
Id(x=>x.ID);
References<UmbrellaOwner>(x=>x.Owner);
}
UmbrellaOwnerMap()
{
Id(x=>x.ID);
References<Umbrella>(x=>x.Umbrella);
}
When creating the tables fluent will create a field in umbrella referncing the ID of umbrellaOwner and a field in umbrellaOwner referencing umbrella. Is there any way to change the mapping such that only one foreign key will be created but the Umbrella property and the Owner property will both exist? The examples I have seen involve setting the relations up in both directions so adding a new Umbrella looks like
AddUmbrealla(UmbrellaOwner owner)
{
var brolly = new Umbrella();
brolly.Owner = owner;
owner.Umbrella = brolly;
session.Save(owner); //assume cascade
}
which seems logical but a bit cumbersome.
Well, a reference is a reference; one object has a reference to the other. The reverse is not necessarily true.
In your case, you MIGHT get away with a HasOne relationship. However, HasOne is normally for denormalized data. Say you wanted more info about the owner, but you could not change Owner's schema because other code depended on it. You'd create an AdditionalOwnerInfo object, and create a table in the schema in which the OwnerID field of the table was a foreign key to Owner, and also the primary key of the table.
Ayende recommends a two-sided References() relationship in 99.9% of one-to-one cases, where the second object is conceptually separate from the first, but there is an implicit "I alone own exactly one thing" type of relationship. You can enforce the "one and one only" nature of the reference using a Unique().Not.Nullable() modifier set on the References mapping.
To streamline the referential setup, consider defining one object (UmbrellaOwner) as the "parent" and the other (Umbrella) as the "child", and in the parent's property setter, set the child's parent to the current reference:
public class Umbrella
{
public virtual string ID { get; set; }
public virtual Owner Owner { get; set; }
}
public class UmbrellaOwner
{
public virtual string ID { get; set; }
private Umbrella umbrella;
public virtual Umbrella Umbrella
{
get{
return umbrella;
}
set{
umbrella = value;
if(umbrella != null) umbrella.Owner = this;
}
}
}
Now, when you assign the child to the parent, the backreference is automagically set up:
var owner = new UmbrellaOwner{Umbrella = new Umbrella()};
Assert.AreEqual(owner, owner.Umbrella.Owner); //true;
I'm trying to set up a relationship as follows. Each Master item has one or more Detail items:
public class Detail {
public virtual Guid DetailId { get; set; }
public virtual string Name { get; set; }
}
public class Master {
public virtual Guid MasterId { get; set; }
public virtual string Name { get; set; }
public virtual IList<Detail> Details { get; set; }
}
And Mappings:
public class MasterMap : ClassMap<Master>
{
public MasterMap()
{
Id(x => x.MasterId);
Map(x => x.Name);
HasMany(x => x.Details).Not.KeyNullable.Cascade.All();
}
}
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
The Master database table is:
masterId uniqueidentifier NOT NULL
name nvarchar(max) NULL
and Detail is:
DetailId uniqueidentifier NOT NULL
name nvarchar(max) NULL
MasterId uniqueidentifier NULL
foreign key (masterId) references [Master]
I don't really care to have a link from Detail back to Master -- in otherwords, Detail objects on their own are just not interesting to my domain layer. They will always be accessed via their Master object.
Using code like this:
Master mast = new Master
{
MasterId = new Guid(),
Name = "test",
Details = new List<Detail>
{
new Detail { .DetailId = new Guid(), .Name = "Test1" },
new Detail { .DetailId = new Guid(), .Name = "Test1" }
}
};
using (transaction == Session.BeginTransaction)
{
Session.Save(mast);
transaction.Commit();
}
This works great, except for a crazy limitation outlined in this post: NHibernate does an INSERT and puts Detail.MasterId as NULL first, then does an UPDATE to set it to the real MasterId.
Really, I don't want Detail entries with NULL MasterIds, so if I set the MasterId field to NOT NULL, the INSERT to Detail will fail, because as I said NHibernate is trying to put in MasterId = NULL.
I guess my question boils down to this:
How can I get the above code sample to work with my existing domain model (eg, without adding a Detail.Master property), and the Detail.MasterId field in the database set to NOT NULL?
Is there a way to get Nhibernate to just put the correct MasterId in the initial INSERT, rather than running an UPDATE afterwards? Is there rationale somewhere for this design decision? -- I'm struggling to see why it would be done this way.
NH3 and above allow to correct save entities in case of uni-directional one-to-many mapping without annoying save null-save-update cycle, if you set both not-null="true" on <key> and inverse="false" on <one-to-many>
FluentNHibernate code snippet for that:
public class MasterMap : ClassMap<Master>
{
public MasterMap()
{
Id(x => x.MasterId);
Map(x => x.Name);
HasMany(x => x.Details)
.Not.Inverse() //these options are very
.Not.KeyNullable() //important and work only if set together
.Not.KeyUpdate() //to prevent double update
.Cascade.All();
}
}
You can't. To quote the link from my answer on the other question you linked to:
Very Important Note: If the <key> column of a <one-to-many> association is declared NOT NULL, NHibernate may cause constraint violations when it creates or updates the association. To prevent this problem, you must use a bidirectional association with the many valued end (the set or bag) marked as inverse="true". See the discussion of bidirectional associations later in this chapter.
Edit: as Hazzik has rightly pointed out, this has changed in NHibernate 3 and above. The docs sadly haven't been updated, so here's Hazzik:
[If you] set inverse="false" and not-null on <key>, NH3 and above will perform only two inserts insead of insert-insert-update.
The reason NHibernate does it this way is because:
When it saves the detail it only knows about the stuff the detail knows about. So any master references which happen in the background are ignored.
Only when the master is saved it sees the relation and updates the elements of the collection with the id of the master.
Which is from an object oriented point of view logical. However from a saving point-of-view is slightly less logical. I suppose you can always file a bug report, or look if it might have been filed already and ask them to change it. But I suppose they have their specific (design/domain) reasons.