Cascade delete from collections in multiple entities - nhibernate

I have three entities: Customer, Device and LocationTag.
Customers have a list of LocationTags (nothing more than an ID and a Description). They also have a list of Devices.
Devices are tagged with a subset of the Customer's LocationTags, so Devices have a List of LocationTags, too (but only of the Customer's).
If I delete a LocationTag from the Customer's list, I would like it also to cascade delete from the list of LocationTags in the Device. Currently, I have it working but with manual code in the domain object classes, but violates DRY in my opinion.
Is it possible to accomplish this via Fluent NHibernate mappings?
Simplified Fluent NHib mappings:
Customer
public CustomerMap()
{
WithTable("Customers");
Id(x => x.ID)
.WithUnsavedValue(0)
.GeneratedBy.Identity();
Map(x => x.Name);
HasMany<LocationTag>(t => t.LocationTags).IsInverse();
HasMany<Device>(d => d.Devices).IsInverse();
}
Device
public DeviceMap()
{
WithTable("Devices");
Id(x => x.ID)
.WithUnsavedValue(0)
.GeneratedBy.Identity();
Map(x => x.Name);
HasMany<LocationTag>(x => x.LocationTags).IsInverse();
}
LocationTag
public LocationTagMap()
{
WithTable("LocationTags");
Id(x => x.ID)
.WithUnsavedValue(0)
.GeneratedBy.Identity();
Map(x => x.Description);
}

I'm not sure if this is even possible with standard NHibernate. You're best asking this on the NHibernate users mailing list, you're more likely to get a helpful answer there.

You could have an associative class between the Customer and the LocationTags. Then have the Device use a FK into the associative class instead of to the LocationTag directly.
Customer
- CustomerID (PK)
CustomerLocationTag
- CustomerLocationTagID (PK)
- CustomerID (FK)
- LocationTagID (FK)
LocationTag
- LocationTagID (PK)
Device
- DeviceID (PK)
- CustomerLocationTagID (FK)

Related

Delete an item from many-to-many relationship

I've following mapping for two tables having a Many-to-Many relationship between them. How do I delete an entry from the mapping table, which is 'TB_EMAIL_GRUPO_ARQUIVO' in my case? I just need to delete the data from this "joining" table and I donĀ“t need to delete it from the "parent tables".
GrupoModulo
public GrupoModuloMap()
{
Schema(Const.SCHEMA);
Table(Const.TB_EMAIL_GRUPO_MODULO);
CompositeId()
.KeyReference(x => x.Grupo, Const.ID_GRUPO)
.KeyReference(x => x.Modulo, Const.ID_MODULO);
Map(x => x.GrupoId).Column(Const.ID_GRUPO).ReadOnly();
Map(x => x.ModuloId).Column(Const.ID_MODULO).ReadOnly();
HasManyToMany(x => x.Arquivos)
.Table(Const.TB_EMAIL_GRUPO_ARQUIVO)
.ParentKeyColumns.Add(Const.ID_GRUPO, Const.ID_MODULO)
.ChildKeyColumn(Const.ID_ARQUIVO)
.Cascade.SaveUpdate()
.Not.LazyLoad();
}
ArquivoRetorno
public ArquivoRetornoMap()
{
Schema(Const.SCHEMA);
Table(Const.TB_EMAIL_ARQUIVO_RETORNO);
Id(x => x.Id)
.Column(Const.ID_ARQUIVO)
.GeneratedBy.Sequence("SEQ_TB_EMAIL_ARQUIVO_RETORNO")
.Length(7).CustomSqlType("number(7)")
.Not.Nullable();
Map(x => x.Nome)
.Column("NM_ARQUIVO_RETORNO")
.Length(50)
.Not.Nullable();
References(x => x.Modulo)
.Column(Const.ID_MODULO)
.Not.Nullable();
HasManyToMany(x => x.GrupoModulos)
.Table(Const.TB_EMAIL_GRUPO_ARQUIVO)
.ChildKeyColumns.Add(Const.ID_GRUPO, Const.ID_MODULO)
.ParentKeyColumn(Const.ID_ARQUIVO)
.Cascade.SaveUpdate()
.Not.LazyLoad();
}
Whenever I try to delete I'm getting the following error:
deleted object would be re-saved by cascade (remove deleted object from associations)[Domain.Entity.GrupoModulo#Domain.Entity.GrupoModulo]
Someone has any idea?
The answer is (I am really sure) here: NHibernate Deleted object would be re-saved by cascade
Let me re-phrase that for your case, what could happen:
we remove an GrupoArquivo from ArquivoRetorno.GrupoModulos collection.
During that transaction, unit of work, we also tuch and therefore load the GrupoModulo
GrupoModulo gets initiated - and NOW - collection of Arquivos is loaded. So the reference to removed GrupoArquivo is kept there
NHibernate MUST inform: Deleted object would be re-saved by cascade
Solution(s):
be sure that the GrupoModulo is never loaded (stays as proxy)
or (the way I use) Remove() the GrupoArquivo also from GrupoModulo.Arquivos
or do NOT use CASCADE mapping on GrupoArquivo side:
(do not use the cascade)
HasManyToMany(x => x.Arquivos)
.Table(Const.TB_EMAIL_GRUPO_ARQUIVO)
.ParentKeyColumns.Add(Const.ID_GRUPO, Const.ID_MODULO)
.ChildKeyColumn(Const.ID_ARQUIVO)
//.Cascade.SaveUpdate()
// here
.Cascade.None()
.Not.LazyLoad();

NHibernate still issues update after insert

I have a very simple unidirectional mappings. see below:
public ContactMap()
{
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device);
HasMany(x => x.Numbers)
.Not.Inverse()
.Not.KeyNullable()
.Cascade.AllDeleteOrphan()
.Not.LazyLoad()
.Fetch.Subselect();
Table("Contacts");
}
public PhoneNumberMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Number);
Table("ContactNumbers");
}
According to this post after nhibernate 3 and above, setting key as non-nullable should fix the insert-update issue (The issue when NHibernate issues an insert with foreign key set to null and then an update to update the foreign key to correct value), but this is not the case for me. When I set the key as not nullable, NHibernate issues a correct insert statement
INSERT INTO ContactNumbers
(Number,
ContactId)
VALUES ('(212) 121-212' /* #p0 */,
10 /* #p1 */);
As you can see, it inserts ContactId field, but after that, it still issues update statement
UPDATE ContactNumbers
SET ContactId = 10 /* #p0 */
WHERE Id = 34 /* #p1 */
So to clarify the problem. NHibernate inserts Contact row with foreign key assigned correctly and after that, it issues an update statement to update the foreign key (ContactId) which is redundant.
How can I get rid of this redundant update statement?
Thanks.
BTW, I'm using latest version of NHibernate and Fluent NHibernate. The database is SQLite
You have to set "updatable"=false to your key to prevent update.
public ContactMap()
{
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device);
HasMany(x => x.Numbers)
.Not.Inverse()
.Not.KeyNullable()
.Not.KeyUpdate() // HERE IT IS
.Cascade.AllDeleteOrphan()
.Not.LazyLoad()
.Fetch.Subselect();
Table("Contacts");
}
You can't as of 3.2.0 BETA.
In v3.2.0 BETA an improvment to one-to-many introduced this anomaly to uni-directional one-to-many relationships (actually I am not sure if anormaly is what you would call this).
Before 3.2 you would need to set the foreign key to allow nulls for this type of relationship to work. So I would ignore the fact that this happens and just go with it. Otherwise you will need to change it to a fully bi-directional relationship.
[NH-941] - One-Many Requiring Nullable Foreign Keys
Release notes or JIRA issue
edit Also the answer to the post you point to is to fix save null-save-update rather than fixing the addtional update
Try setting inverse to true on the mapping and assigning the relationship in code.
Inverse means that the child is responsible for holding the ID of the parent.
e.g.
var contact = new Contact();
var phoneNumber = new PhoneNumber();
phoneNumber.Contact = contact;
That way, when you do the insert for the PhoneNumber record, NH can insert the ContactId without having to do a separate update.
That's what I used to do in NH 2, I would assume the behaviour still works the same in 3.
I don't know if you really can get rid of it.
Try using another id generator as native. It forces NH to insert the record only to get the id. The id is used for every entity in the session, so it can't do the insert later. It may case subsequent updates. Use hi-lo or something similar.
Edit
Why aren't you using a component in this case? You don't need to map the phone number separately, if they consist only of a number. Something like this (I'm not a FNH user, so it may be wrong):
public ContactMap()
{
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device);
HasMany(x => x.Numbers)
.Not.Inverse()
.Not.KeyNullable()
.Cascade.AllDeleteOrphan()
.Not.LazyLoad()
.Fetch.Subselect()
.Component(c =>
{
Map(x => x.Number);
})
.Table("ContactNumbers");
Table("Contacts");
}
It is what Trevor Pilley said. Use inverse="true". If you choose not to have inverse="true", this is the consequence of that choice. You can't have it both ways.

Fluent NHibernate one of Primary Keys is also Foreign key - OutOfRangeException

Cart
{
CompositeId().KeyProperty(x => x.CART_ID, "CART_ID").KeyProperty(x => x.COMM_CD, "COMM_CD");
References(x => x.Product, "COMM_CD");
}
When I tried to save cart object into database I received out of range exception. I think it's because I have two COMM_CD properties. Is there a way to solve this problem ?
Thanks
You need to use KeyReference instead of KeyProperty
CompositeId()
.KeyProperty(x => x.CART_ID, "CART_ID")
.KeyReference(x => x.Product, "COMM_CD");

Optional many-to-one/References results in foreign key violations on insert

I currently have the following relationship: ProductUom -> ProductImage
They both have the same primary keys: PROD_ID and UOM_TYPE
I have them mapped like this:
public ProductUomMap()
{
Table("PROD_UOM");
CompositeId()
.KeyReference(x => x.Product, "PROD_ID")
.KeyProperty(x => x.UomType, "UOM_TYPE");
References(x => x.Image)
.Columns(new string[] { "PROD_ID", "UOM_TYPE" })
.Not.Update()
.Not.Insert()
.NotFound.Ignore()
.Cascade.All();
}
public ProductImageMap()
{
Table("PROD_UOM_IMAGE");
CompositeId()
.KeyReference(x => x.ProductUom, new string[] {"PROD_ID", "UOM_TYPE"});
Map(x => x.Image, "PROD_IMAGE").Length(2147483647);
}
Whenever I create a ProductUom object that has a ProductImage it tries to insert the ProductImage first which results in a foreign key violation. I swear this was working at one time with the mapping that I have but it doesn't now.
I need the ProductImage to be a Reference (many-to-one) because the relationship here is optional and I want to be able to lazy load product images. The inserts do work correctly if I use a HasOne (one-to-one) mapping but the I cannot lazy load when I do this and querying a ProductUom seems to cause issues.
Is there something that I'm missing here? How can this mapping be modified to get what I want?
can you use LazyLoaded Properties? Then you could use something like this
Join("PROD_UOM_IMAGE", join =>
{
join.KeyColumn("PROD_ID", "UOM_TYPE");
join.Optional();
join.Map(x => x.Image, "PROD_IMAGE").Length(2147483647).LazyLoad();
}
another option is:
Id().GeneratedBy.Foreign(x => x.ProductUom);
can't test it here though, i'm writing on Mobile

NHibernate many-to-many assocations making both ends as a parent by using a relationship entity in the Domain Model

Entities:
Team <-> TeamEmployee <-> Employee
Requirements:
A Team and an Employee can exist without its counterpart.
In the Team-TeamEmployee relation the Team is responsible (parent) [using later a TeamRepository].
In the Employee-TeamEmployee relation the Employee is responsible (parent) [using later an EmployeeRepository].
Duplicates are not allowed.
Deleting a Team deletes all Employees in the Team, if the Employee is not in another Team.
Deleting an Employee deletes only a Team, if the Team does not contain no more Employees.
Mapping:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
// identity mapping
Id(p => p.Id)
.Column("TeamID")
.GeneratedBy.Identity();
// column mapping
Map(p => p.Name);
// associations
HasMany(p => p.TeamEmployees)
.KeyColumn("TeamID")
.Inverse()
.Cascade.SaveUpdate()
.AsSet()
.LazyLoad();
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
// identifier mapping
Id(p => p.Id)
.Column("EmployeeID")
.GeneratedBy.Identity();
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// associations
HasMany(p => p.TeamEmployees)
.Inverse()
.Cascade.SaveUpdate()
.KeyColumn("EmployeeID")
.AsSet()
.LazyLoad();
HasMany(p => p.LoanedItems)
.Cascade.SaveUpdate()
.LazyLoad()
.KeyColumn("EmployeeID");
}
}
public class TeamEmployeeMap : ClassMap<TeamEmployee>
{
public TeamEmployeeMap()
{
Id(p => p.Id);
References(p => p.Employee)
.Column("EmployeeID")
.LazyLoad();
References(p => p.Team)
.Column("TeamID")
.LazyLoad();
}
}
Creating Employees and Teams:
var employee1 = new Employee { EMail = "Mail", FirstName = "Firstname", LastName = "Lastname" };
var team1 = new Team { Name = "Team1" };
var team2 = new Team { Name = "Team2" };
employee1.AddTeam(team1);
employee1.AddTeam(team2);
var employee2 = new Employee { EMail = "Mail2", FirstName = "Firstname2", LastName = "Lastname2" };
var team3 = new Team { Name = "Team3" };
employee2.AddTeam(team3);
employee2.AddTeam(team1);
team1.AddEmployee(employee1);
team1.AddEmployee(employee2);
team2.AddEmployee(employee1);
team3.AddEmployee(employee2);
session.SaveOrUpdate(team1);
session.SaveOrUpdate(team2);
session.SaveOrUpdate(team3);
session.SaveOrUpdate(employee1);
session.SaveOrUpdate(employee2);
After this I commit the changes by using transaction.Commit().
The first strange thing is that I have to save Teams and Employees instead only one of them (why?!). If I only save all teams or (Xor) all employees then I get a TransientObjectException:
"object references an unsaved
transient instance - save the
transient instance before flushing.
Type: Core.Domain.Model.Employee,
Entity: Core.Domain.Model.Employee"
When I save all created Teams and Employees everything saves fine, BUT the relation table TeamEmployee has duplicate assoications.
ID EID TID
1 1 1
2 2 1
3 1 2
4 2 3
5 1 1
6 1 2
7 2 3
8 2 1
So instead of 4 relations there are 8 relations. 4 relations for the left side and 4 relations for the right side. :[
What do I wrong?
Further questions: When I delete a Team or an Employee, do I have to remove the team or the Employee from the TeamEmployee list in the object model or does NHibernate make the job for me (using session.delete(..))?
You are talking about business logic. It's not the purpose of NHibernate to implement the business logic.
What your code is doing:
You mapped two different collections of TeamEmployees, one in Team, one in Employee. In your code, you add items to both collections, creating new instances of TeamEmployee each time. So why do you expect that NHibernate should not store all these distinct instances?
What you could do to fix it:
You made TeamEmployee an entity (in contrast to a value type). To create an instance only once, you would have to instantiate it only once in memory and reuse it in both collections. Only do this when you really need this class in your domain model. (eg. because it contains additional information about the relations and is actually an entity of its own.)
If you don't need the class, it is much easier to map it as a many-to-many relation (as already proposed by Chris Conway). Because there are two collections in memory which are expected to contain the same data, you tell NHibernate to ignore one of them when storing, using Inverse.
The parent on both ends problem
There is no parent on both ends. I think it's clear that neither the Team nor the Employee is a parent of the other, they are independent. You probably mean that they are both parents of the intermediate TeamEmployee. They can't be parent (and therefore owner) of the same instance. Either one of them is the parent, or it is another independent instance, which makes managing it much more complicated (this is how you implemented it now). If you map it as a many-to-many relation, it will be managed by NHibernate.
To be done by your business logic:
storing new Teams and new Employees
managing the relations and keeping them in sync
deleting Teams and Employees when they are not used anymore. (There is explicitly no persistent garbage collection implementation in NHibernate, for several reasons.)
Looks like you need a HasManyToMany instead of two HasMany maps. Also, there is no need for the TeamEmployeeMap unless you have some other property in that table that needs mapped. Another thing, only one side needs to have the Inverse() set and since you're adding teams to employees I think you need to make the TeamMap the inverse. Having the inverse on one side only will get rid of the duplicate entries in the database.
Maybe something like this:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
// identity mapping
Id(p => p.Id)
.Column("TeamID")
.GeneratedBy.Identity();
// column mapping
Map(p => p.Name);
// associations
HasManyToMany(x => x.TeamEmployees)
.Table("TeamEmployees")
.ParentKeyColumn("TeamID")
.ChildKeyColumn("EmployeeID")
.LazyLoad()
.Inverse()
.AsSet();
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
// identifier mapping
Id(p => p.Id)
.Column("EmployeeID")
.GeneratedBy.Identity();
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// associations
HasManyToMany(x => x.TeamEmployees)
.Table("TeamEmployees")
.ParentKeyColumn("EmployeeID")
.ChildKeyColumn("TeamID")
.Cascade.SaveUpdate()
.LazyLoad()
.AsSet();
HasMany(p => p.LoanedItems)
.Cascade.SaveUpdate()
.LazyLoad()
.KeyColumn("EmployeeID");
}
}
Using this, the delete will delete the TeamEmployee from the database for you.
NHibernate does not allow many-to-many association with parents at both ends.