Doctrine Nothing to update after adding onDelete="CASCADE" - doctrine-orm

I want to add the option onDelete="CASCADE" on one of my attributes via the #JoinColumn annotation:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Product",mappedBy="category",fetch="EAGER")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $products;
But when I try to update with php bin/console doctrine:schema:update --force , I always get:
nothing to uptade - database already sync.
I tried to add some other attributes and I got the same issue. However, if I intentionally add a mistake I get an error as expected.
How can I fix this?

The #OneToMany annotation is the one you use on the inverse side of your many-to-one association. The table storing the entities on this side of the association does not hold any foreign key pointing to the table storing your Product entities, thus there is no "join column" there.
The documentation states the following about #JoinColumn:
This annotation is used in the context of relations in #ManyToOne, #OneToOne fields and in the Context of #JoinTable nested inside a #ManyToMany.
In your case, the annotation does not apply to any column at all and consequently, your database does not need to be updated.
If you wish to have Product entities related to a given Category removed through cascade operations by your database, you have to add a #JoinColumn(onDelete="CASCADE") on the owning side of the association, next to the #ManyToOne annotation of the category attribute of Product.

Related

Entity Framework Cascade Deleting, even though no CASCADE set up in database

I have a foreign key relationship between two tables UserProducts and Users in my database, with UserProducts having a UserID referencing the UserID in the Users table.
ALTER TABLE [dbo].[UserProducts] WITH CHECK ADD CONSTRAINT [FK_UserProducts_Users] FOREIGN KEY ([UserID])
REFERENCES [dbo].[Users] ([UserID])
GO
ALTER TABLE [dbo].[UserProducts] CHECK CONSTRAINT [FK_UserProducts_Users]
GO
The UserID column in the UserProducts table is part of a composite primary key with another column ProductID. There are also two additional DateTime columns, so Entity Framework does not treat UserProducts as a link table.
There is NO cascade delete on that foreign key above, nor did I set up anything to handle OnDelete on the Entity Framework foreign key association. Yet, when I delete a User entity from code, Entity Framework is taking the liberty of deleting the UserProducts associated with it by UserID. It is also generating a lot of SQL to do it: there is a separate DELETE per related record in the UserProducts table.
The code to perform the entity deletion is as follows:
using (var context = new LicensingRegistrationContext(_csb))
{
context.Database.Log = a => _logger.Trace(a);
var dbUser = GetUserDbSetWithIncludes(context)
.Where(a => a.UserID == user.Id).Single();
context.DbUsers.Remove(dbUser);
//TODO(MRL): Um...how are the dbUserProducts being removed???
context.SaveChanges();
}
How is this happening? In EF 4 I am pretty sure EF never took this liberty of doing this: you HAD to load and then delete all related entities manually in code.
Thanks
Entity framework by default has a
OneToManyCascadeDelete
convention. Here is the link http://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.conventions.onetomanycascadedeleteconvention(v=vs.113).aspx
So entity framework cascade deletes the one to many relationship by default.
You can disable this by disabling the convention or explicitly disabled it for this relationship via the fluent API.
I found this on MSDN and I believe this is what is happening:
When a primary key of the principal entity is also part of the primary key of the dependent entity, the relationship is an identifying relationship. In an identifying relationship the dependent entity cannot exist without the principal entity. This constraint causes the following behaviors in an identifying relationship: Deleting the principal object also deletes the dependent object. This is the same behavior as specifying OnDelete Action="Cascade" in the model for the relationship. Removing the relationship deletes the dependent object. Calling the Remove method on the EntityCollection marks both the relationship and the dependent object for deletion.
This is what is happening in my model where the UserComponent table has a composite primary key: UserID, ComponentID and the UserID column is a foreign key to the UserID in the User table.

Adding Entity with Many-To-One association and inverse side already existing in the database

Using Doctrine 2 with Zend Framework 2. Been thinking of how I am meant to persist an entity with a field having a Many-To-One association with another entity that already exists in the database. Would I have to fetch the inverse side from the database first and then adding it to the owning Entity before persisting to the database as in the code below.
//$data = $_POST;
$book = new Book();
$author = $em->getRepository('Application\Entity\Book')->find($data['author_id']);
$book->setTitle($data['title'])
->setISBN($data['title'])
->setAbstract($data['abstract'])
->setYear($data['year'])
->setAuthor($author);
$em->persist($book);
$em->flush();
Normally, without using doctrine, all I have to do is update the author_id field of the Book entity above and persist to the Database. But now I have to make a trip to the Database to fetch the author details to create the entity and thus the association and then persist.
Is this the way it should be done or there is another way that doesnt involve fetching the author's details.
As you can read here
The method EntityManager#getReference($entityName, $identifier) lets
you obtain a reference to an entity for which the identifier is known,
without loading that entity from the database. This is useful, for
example, as a performance enhancement, when you want to establish an
association to an entity for which you have the identifier.
You could simply do this:
$book = new Book();
$book->setAuthor( $em->getReference('Application\Entity\Author',$data['author_id']));

ManyToMany self-referenced with extra column in Doctrine

I have problem which is easy to solve with pure SQL but I need to solve it with Doctrine in YAML.
I have well known db tables users and friends. Where users table has primary key user_id and friends has friend_id and friend_with_id which uses user_id from table user. My problem starts when I need to add there one more column mood. With SQL I add to table friends onemore column named mood and it's done with doctrine I can't find any solution.
For better understanding I add db scheme:
I dont see any self-reference relationship here. Both friend_id and friend_with_id points to users.user_id. What I see is, two one-to-many relationships from the user side OR two many-to-one relationships from the friends side.
So, there will be two ArrayCollection objects (ManyToOne) in the Friend entity corresponding to friend_id and friends_with_id. Similarly two ArrayCollections (OneToMany) on the User entity, namely, myFriends and friendsWith.

Zend2 Doctrine2 One-To-Many uni-directional with join table, delete cascade issue

I'm having some problems with the following...
I have a table with phone numbers. I want to use the same table for both users and companies. A user can have several phone numbers and a company too. So i want a One to many unidirectional relationship with two different join tables. One linking phone numbers to users, the other linking phone numbers to companies.
This is solution following the doctrine2 manual chapter 5.9 found here: (click)
My users entity holds this code:
/** #ORM\ManyToMany(targetEntity="Application\Entity\PhoneNumber")
* #ORM\JoinTable(name="user_phone_number_linker",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="phone_number_id", referencedColumnName="id")}
* )
*/
protected $phone_numbers;
I use a unidirectional one to many because the thing is I can't make a bidirectional one because if I refer back to the user I cannot use the same phone number entity class for the company. Now it all works fine, but when I delete a phone number I get the following error:
An exception occurred while executing 'DELETE FROM phone_number WHERE id = ?' with params {"1":1}:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (database/user_phone_number_linker, CONSTRAINT user_phone_number_linker_ibfk_11 FOREIGN KEY (phone_number_id) REFERENCES phone_number (id))
If I set the ON DELETE CASCADE value manually in the database it works fine, but this is not the idea of using doctrine2 and I think I should be able to solve it within the code without going to my phpMyAdmin panel. Somehow the cascading from the phone number towards the join table should be initiated on deletion, but without making a reference back to the join table from the phone_number entity.
Hope someone smart can help me solve this.
EDIT
In the meantime I learned a lot more about Doctrine2 and reviewing my old question made me realize that this is not a correct way to store several phoneNumbers in one table in the first place. To be able to store user phone numbers and company phone numbers in the same table I should use table inheritance with a discriminator column. The column should hold some user/company discriminator.
Because of this column the doctrine ORM will "know" if that phoneNumber is actually a user or a company phone-number. I need to make two different entity definitions following the single table inheritance mapping principles from the doctrine 2 specs.
One class UserPhoneNumber will have a many-to-one relationship with User the other called CompanyPhoneNumber a one-to-many relationship with Company. I don't necessarily need a join column, the user_id or company_id columns can be in the phone-number table. In the User class the Company association is omitted and in the Company class the User association is omitted (database should allow null values for those columns).
If I do use a join table it is according to the one-to-many unidirectional with join table description in the Doctrine2 specs
READ MORE
Otherwise you can also read more on associations and cascade issues here on this elaborate Doctrine2 in depth website.
As you said, your relation is unidirectional. You've defined a relation from Users to PhoneNumbers. The cascade delete will work when you delete a User, it will remove all rows in user_phone_number_linker because that's the relation you've defined.
If you want to do it the other way, you've got to create a relation from PhoneNumbers to Users. Doctrine needs it to work for you. But you have the problem that the entity is shared by two other entities, Users and Companies.
Keep in mind that entities are objects, not tables. So you could try to create two entities to the same table, one named PhoneNumberUsers and the other PhoneNumberCompanies. This way you'll be able to create the needed relation to do the cascade delete. I haven't tested by myself, but I think it could work.
By the way, you can remove the oncascade parameter on the Users' entity join table. I've the same scenario as you with users and roles, and I haven't used it. I think it's only needed when you want to cascade from entity to entity. I'm not sure about that, but that's what I've been experiencing until now.
My bad,
The phone number user relationship is regarded a Many-To-Many relationship, so if want to remover the phone number I should not only remove the phone number itself, but I have to explicitly remove the phone number from the user as well. So in the Controller like this:
// Remove the phone number user connection from the database
$user->removePhoneNumber($phone_number);
// Remove the phone number from the database
$em->remove($phone_number);
I just thought the unique restriction which makes the relationship to a unidirectional One-To-Many would be enough to make doctrine take care of it. That was not correct.

Cannot make #ManyToOne relationship nullable

I have a many-to-one relationship that I want to be nullable:
#ManyToOne(optional = true)
#JoinColumn(name = "customer_id", nullable = true)
private Customer customer;
Unfortunately, JPA keeps setting the column in my database as NOT NULL. Can anyone explain this? Is there a way to make it work? Note that I use JBoss 7, JPA 2.0 with Hibernate as persistence provider and a PostgreSQL 9.1 database.
EDIT:
I found the cause of my problem. Apparently it is due to the way I defined the primary key in the referenced entity Customer:
#Entity
#Table
public class Customer {
#Id
#GeneratedValue
#Column(columnDefinition="serial")
private int id;
}
It seems that using #Column(columnDefinition="serial") for the primary key automatically sets the foreign keys referencing it to NOT NULL in the database. Is that really the expected behavior when specifying the column type as serial? Is there a workaround for enabling nullable foreign keys in this case?
Thank you in advance.
I found the solution to my problem. The way the primary key is defined in entity Customer is fine, the problem resides in the foreign key declaration. It should be declared like this:
#ManyToOne
#JoinColumn(columnDefinition="integer", name="customer_id")
private Customer customer;
Indeed, if the attribute columnDefinition="integer" is omitted the foreign key will by default be set as the source column: a not-null serial with its own sequence. That is of course not what we want as we just want the to reference the auto-incremented ID, not to create a new one.
Besides, it seems that the attribute name=customer_id is also required as I observed when performing some testing. Otherwise the foreign key column will still be set as the source column. This is a strange behavior in my opinion. Comments or additional information to clarify this are welcome!
Finally, the advantage of this solution is that the ID is generated by the database (not by JPA) and thus we do not have to worry about it when inserting data manually or through scripts which often happens in data migration or maintenance.
I came across this problem but I was able to solve it this way:
#ManyToOne
#JoinColumn(nullable = true)
private Customer customer;
Maybe the problem emerged from declaring #ManyToOne(optional = true)
That is very weird.
In JPA nullable parameter is true by default. I use this kind of configuration all the time and it works fine. If you try to save entity it should be successful.
Did you try to delete table that is created for this relationship? Maybe you have legacy table with that column?
Or maybe you should try to find solution on other chunks of code, because this is proper configuration.
Note: I have tried this configuration on PostgreSQL with JPA2 and Hibernate.
EDIT
In that case maybe you can try a little bit different definition of primary key.
For example you can use definition like this:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column()
private Long id;
and postgresql will generate
id bigint NOT NULL
-- with constraint
CONSTRAINT some_table_pkey PRIMARY KEY (id)
If this is good enough you can try this solution.
within transaction but before the save operation, explicitly set the foreign key column value as null. By this hibernate ,never perform select queries for this foreign key related table and don't throw the exception "save the transient instance before flushing". if you want to set "null value " conditionally, then perform 1. fetch & set the value using repo call get/ find 2. then check the fetched value for the condition and set it to null accordingly .pasted the code below which is tested and found working
// Transaction Start
Optional<Customer> customerObject = customerRepository.findByCustomerId(customer.getCustomerId())
if(customerObject.isPresent())yourEnclosingEntityObject.setCustomer(customerObject)}
else {yourEnclosingEntityObject.setCustomer(null)}
yourEnclosingEntityObjectRepository.save(yourEnclosingEntityObject)
// Transaction End