Intershop EDL modelling - How to add dependency with on cascade delete - orm

We have some custom objects modelled through EDL which have foreign keys to system Intershop objects (ISPRODUCT and ISORDER). We need our objects to get deleted when referenced order or product is deleted.
This is the extract from the EDL file:
/**
* Relation to product PO (tariff item)
*/
dependency tariff: ProductPO
{
foreign key(tariffID);
}
/*
* Order relation
*/
dependency order: OrderPO
{
foreign key(orderID);
}
As I can see, it is possible to add delete actions on EDL relations but it is not possible to add delete actions on dependencies.
What we are doing at the moment is modifying the statements in the generated dbconstraints.oracle.ddl files like this:
EXEC staging_ddl.add_constraint('A1APPLICATIONFORM', 'A1APPLICATIONFORM_CO_003', 'FOREIGN KEY (TARIFFID) REFERENCES PRODUCT (UUID) ON DELETE SET NULL INITIALLY DEFERRED DEFERRABLE DISABLE NOVALIDATE');
EXEC staging_ddl.add_constraint('A1APPLICATIONFORM', 'A1APPLICATIONFORM_CO_004', 'FOREIGN KEY (ORDERID) REFERENCES ISORDER (UUID) ON DELETE CASCADE INITIALLY DEFERRED DEFERRABLE DISABLE NOVALIDATE');
But it is only the temporary workaround because these files will get overwritten each time we restart the code generator on the EDL.
On relationship it is possible to define the on delete action like this:
relation promotionBenefitPOs : A1PromotionBenefitPO[0..n] inverse promotionPO implements promotionBenefits delete default;
Is it possible to achieve the same thing on the dependency with the system objects?

I didn't know that was possible with EDL, good to know. My problem with this approach is that the orm cache does not know that these objects are being removed by oracle, so it might have phantom object floating around in the orm cache.
I would use this register listener solution to remove these objects so that everything is updated and flushed out of the cache.
I do wonder how the code generator deals with this delete property on the relation.

I'm afraid you need to do that by hand. Meaning once an instance of the types involved is removed, you need to query for your custom glue object and remove that one a subsequent action by your own. As dependency is merely a weak (unidirectional) relation that orm cannot automatically remove.
See here for documentation about EDL-dependency: https://support.intershop.com/kb/index.php/Display/247P28
For example, I checked ProcessPagelet-Delete pipline. In there we first unassign (i.e. remove the assignment) Label objects from the Pagelet to be deleted. The PageletLabelAssingmentPO contains a dependency to Pagelet as you can see here:
orm class PageletLabelAssignmentPO extends LabelAssignmentPO
{
attribute pageletUUID : uuid;
dependency pagelet : PageletPO
{
foreign key(pageletUUID);
}
}

Related

Neo4j APOC trigger and Manual Index on Relationship Properties

I'd like to setup Neo4j APOC trigger that will add all relationship properties to manual index, something like the following:
CALL apoc.trigger.add('HAS_VALUE_ON_INDEX',"UNWIND {createdRelationships} AS r MATCH (Decision)-[r:HAS_VALUE_ON]->(Characteristic) CALL apoc.index.addRelationship(r,['property_1','property_2']) RETURN count(*)", {phase:'after'})
The issue is that I don't know the exact set of HAS_VALUE_ON relationship properties because I use the dynamic properties approach with Spring Data Neo4 5.
Is it possible to change this trigger declaration to be able to add all of the HAS_VALUE_ON relationship properties(existing and ones that will be created in future) to the manual index instead of the preconfigured ones( like ['property_1','property_2'] in the mentioned example) ?
If you do not know the set of properties in advance, then you can use the keys function to add all properties of the created relationships to the index:
CALL apoc.trigger.add(
'HAS_VALUE_ON_INDEX',
'UNWIND {createdRelationships} AS r MATCH (Decision)-[r:HAS_VALUE_ON]->(Characteristic)
CALL apoc.index.addRelationship(r, keys(r)) RETURN count(*)',
{phase:'after'}
)

Removing children entries from parent causes error on SaveChanges

I am getting the following error:
The association between entity types 'Docket' and 'DocketLine' has been severed but the foreign key for this relationship cannot be set to null. If the dependent entity should be deleted, then setup the relationship to use cascade deletes.
The issue comes about because I have a Docket (header) than has multiple children (DocketLines), and I am doing an update where I am adding new Lines to the docket header, and I am just adding those new DocketLines to the Docket.DocketLines collection (which works fine). But when I attempt to remove a DocketLine from the same collection using Docket.DocketLines.Remove(deletedLine), then this generates the error message above. Any idea why?
I had to change my code to remove the Lines directly from the _context.DocketLines.Remove/RemoveRange(...) collection in the end, and this works, but it seems odd that I would add new items to a child collection to insert new DocketLines, but couldn't remove items from that same collection to remove DocketLines.
Inside the DbContext/OnModelCreating method, comment the OnDelete behaviour of the entity that is giving you that problem:
That will allow you to avoid that problem.

Can I delete an entity that is not in cache?

I want to delete a record from the DB that hasn't been retrieved from a breeze query. The entity hasn't been retrieved so it's not in the cache, but I know the KEY of the record from another operation. Here's what I've tried:
create a new entity from the manager:
manager.createEntity(entityNames.book);
setting the ID :
bookToDelete().bookID(1); // bookToDelete is a ko observable from step 1
updating the state:
bookToDelete().entityAspect.setDeleted();
When I save changes, this transaction is not included in the JSON
You almost have it. Calling entityAspect.setDeleted on an 'Added' entity moves it directly to a 'Detached' state, which in effect removes it from the EntityManager, and hence it cannot be saved. This is deliberate. It handles the case where you create an entity and later delete it. In this case, there is no entity to save.
So, in your case, you have to make the entity either 'Modified' or 'Unchanged' before you call entityAspect.setDeleted. You can do this by either calling entityAspect.setUnchanged or entityAspect.setModified before calling entityAspect.setDeleted or you can call entityAspect.acceptChanges.
Note that you will also have to insure that the 'clone' entity passes validation and if you have a concurrency field on the entity, you will need to set this appropriately as well.
UPDATE Dec 7th
You can create the book entity in the marked-for-delete state in a single step as shown:
var book = manager.createEntity(entityNames.book,
{ BookID: 1 }, // use initializer to set the key
breeze.EntityState.Deleted); // creates the entity in the Deleted state
Be sure to initialize it with all other properties that are necessary for the entity to pass validation and optimistic concurrency checks on the server.
No problem if you don't have these checks. Not sure how you'd get those values without querying the server if you did have such checks.
got it. cant delete entity while still in added state. I first setModified. then setdeleted. didnt see any side affects.

Nhibernate foreign key constraint due to unexpected update of already inserted record

This is a strange one replicated in the following code:
using (ISession session = RepositoryTestHelper.SessionFactory.OpenSession())
{
//session.BeginTransaction();
UserRepo repo = new UserRepo(session);
CompanyRepo cRepo = new CompanyRepo(session);
var user = repo.FindByEmail("test.user#blah.com");
user.CompanyAssociations.Add(new CompanyUserAssoc()
{
User = user,
Company = cRepo.GetById(1)
});
repo.AddOrUpdate(user);
//session.Transaction.Commit();
}
And the relationship between user, company and CompanyUserAssoc is fairly straight forward:
For the company:
HasMany<CompanyUserAssoc>(x => x.UserAssociations).KeyColumn("User_id");
For the user:
HasMany<CompanyUserAssoc>(x => x.CompanyAssociations).KeyColumn("Company_id")
And for the association class itself:
References(x => x.Company).UniqueKey("CompanyId_UserId");
References(x => x.User).UniqueKey("CompanyId_UserId");
Now this is where I am baffled. Notice in my initial code that the begin and commit trans calls are commented out. This actually means the code will work! The CompanyUserAssoc is created and correctly references the user and the company with id of 1. Great!
But... sadly when I put this in a transaction i get this error:
{"The UPDATE statement conflicted with the FOREIGN KEY constraint \"FK3C47859753A62C6E\". The conflict occurred in database \"xxxx\", table \"dbo.Company\", column 'Id'.\r\nThe statement has been terminated."}
But why? Well that's my question. What i have see in the profiler is that it does this:
exec sp_executesql N'UPDATE [CompanyUserAssoc] SET Company_id = null WHERE Company_id = #p0',N'#p0 int',#p0=1
Wait... what? NULL? Why is it setting the company id to null? and why is it only doing this when in a transaction? What's "wrong" with my Nhibernate mapping?
Since both collections are mapped as non-inverse (the default) and the Company collection has no users, it will issue that update to clear the link table. Could you try setting the UserAssociations to inverse? Or, what I usually do when an explicit link table is involved, is set both HasManys to inverse and simply work with the link table directly.
This was a red herring in the end, a fish I have come to despise. I had not noticed in my haste and dependence on intellisense that the UserAssocations property of the Company was not an IList but an IList! yes I have stupidly chosen the Nhib map class instead of the domain class.. This weirdly didn't error in the way you would expect, and worked without a transaction, but why?
Well - Nhib was able to insert the assoc entry, but then believed it needed to update that same table with the id it had already inserted (because it somehow sees it as a different entity?). I would have expected a compilation error here in the map class itself as I was using the type, but no. And without a transaction the sql generated works.
I would investigate more as to why, but I've answered this just to highlight a rare and silly mistake which can lead to a lot of investigation if missed.

How to set "cascade delete" option to "Set Null" in Fluent NHibernate?

I am new to Fluent nHibernate and would like to know, if I have two classes Profile and Email mapped one-to-many as following... I want to define a nHibernate mapping fluently so when Profile is deleted, Email will remain in the DB, with a key set to Null. Or in other words to have "ON DELETE SET NULL"
ALTER TABLE [dbo].[Email] WITH CHECK ADD CONSTRAINT [FK4239B252F6539048] FOREIGN KEY([ProfileId])
REFERENCES [dbo].[Profile] ([Id])
ON UPDATE SET NULL
ON DELETE SET NULL
Any help is greately appreciated!
public sealed class ProfileMapping : ClassMap<Profile>
{
public ProfileMapping()
{
// Some other fields here ...
HasMany(x => x.Emails);
}
}
public class EmailMapping : ClassMap<Email>
{
public EmailMapping()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Address).Not.Nullable().UniqueKey("UX_EmailAddress").Length(254);
Map(x => x.Confirmed);
}
}
You won't be able to specify this behavior automatically in Fluent NHibernate AFAIK. Although the ON DELETE/ON UPDATE behavior specification is common to all DBs that NHibernate supports, NH/FNH control cascading with specific levels of cascade behavior:
none - do not do any cascades, let the users handles them by themselves.
save-update - when the object is saved/updated, check the assoications and save/update any object that require it (including save/update the assoications in many-to-many scenario).
delete - when the object is deleted, delete all the objects in the assoication.
delete-orphan - when the object is deleted, delete all the objects in the assoication. In addition to that, when an object is removed from the assoication and not assoicated with another object (orphaned), also delete it.
all - when an object is save/update/delete, check the assoications and save/update/delete all the objects found.
all-delete-orphan - when an object is save/update/delete, check the assoications and save/update/delete all the objects found. In additional to that, when an object is removed from the assoication and not assoicated with another object (orphaned), also delete it.
As you can see, "SET NULL" is not one of the available cascade behaviors.
The best you can do in this case is to not cascade at all, and instead to define the relationship as "Inverse" (E-mails "control" what profile they belong to; Profiles do not "own" their E-mails as such), and to implement logic either in your Repository or attached to the NHibernate Session that will remove all references of the child Emails to their parent Profile, then save all the children as "orphan" records before deleting the Profile.