We're dealing with a situation where a user is presented with a form to add/remove some simple OneToMany entities of a Parent entity.
After form submission we've replaced the array collection of the parent with a new one based on the form data.
Using cascade={"persist", "remove"} means that any new OneToMany entities are successfully persisted and added to the database when we persist and flush the Parent.
What we would like is for entities which are no longer in the OneToMany ArrayCollection to be automatically removed from the database during the Parent's persist and flush, thereby allowing us to delete the ugly code we wrote to remove them one by one.
Is this possible?
Related
I have a model that retrieves data from a table in a database from a certain SQL query, and shows the items in a QTreeView. The characteristics are:
the data comes from a table, but has an underlying tree structure (some rows are parents that have rows below them as children)
this tree structure is shown in the QTreeView
the children are selectable in the QTreeView (not so the parents)
the table in the database gets updated continuously
in the updates, a children can be added to any existing parent
periodically (with a QTimer) the QTreeView is updated with the contents of the table
Since the children are added at any time to any parent, the first silly approach when updating the QTreeView is clearing it all, and append all the rows again, in form of parent or children, to the QTreeView. This is a 0-order approximation, and it is indeed terrible inefficient. In particular, the following problems appear:
Any existing selection is gone
Any expanded parent showing its children is collapsed (unless ExpandAll is active)
The view is reset to show the very first row.
What is the best solution to this problem? I mean, the first solution I will try will be not to clear the QTreeView, but instead parse all the returned rows from the table, and check for each of them whether the corresponding item in the QTreeView exists, and add it if not. But I wonder if there is a trickiest solution to engage a given table in a database with a QTreeView (I know this exists for a QTableView, but then the tree structure is gone).
This thread mentions a general approach, but this might get tricky quickly, but I am not sure how this would work if the underlying model is changing constantly (i.e. the QModelIndex becoming invalid).
Worst case is that you will have to write your own mechanism to remember the selection before updating and then re-applying it.
I assume you use some model/view implementation? You could enhance your model with a safe selection handling, in case the example mentioned above does not work for you.
I guess this is the case for a self-answer.
As I presumed, after a careful analysis of what data is retrieved from the database, I had to do the following "upgrades" to the retrieval code:
I retrieve, along with the fields I want to show in the view, two identifiers, one for grouping rows and one for sorting items into groups
I also retrieve the internal record ID (an increasing integer number) from the table in the database, in order to ask only for new records in the next retrieval.
In the model population code, I added the following:
I first scan the initial records that may belong to existing groups in the model
When, in the scanning, I reach the last group in the model, this implies that the rest of retrieved records belong to new groups (remember the records are retrieved sorted such that items that belong to the same group are retrieved together)
Then start creating groups and adding items to each group, until we use all the records retrieved.
Finally, it is very important:
the use beginInsertRows() and endInsertRows() before and after inserting new items in the model
capture the sorting status of the view (with sortIndicatorSection() and sortIndicatorOrder()) and re-apply this sorting status after updating the model (with sortByColumn())
Doing that the current position and selection in the QTreeView receiving the model updates are preserved, and the items in the view are added and the view updated transparently for the user.
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.
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']));
I have two entities User and Department, There is a bidirectional relation between the two entities (OneToMany and ManyToOne), I manage the relationship in a service class that handles removing and adding users to department in the following:
List<User> originalUserList = userJpaRepository.findAllByDepartment(department);
for (Iterator<User> iterator = originalUserList.iterator(); iterator.hasNext();) {
User user = iterator.next();
if (!department.getDepartmentEmployees().contains(user)) {
user.setDepartment(null);
userJpaRepository.save(user);
}
}
for (User user : department.getDepartmentEmployees()) {
user.setDepartment(department);
userJpaRepository.save(user);
}
Department savedDepartmetn = jpaRepository.save(department);
return savedDepartmetn;
Everything works fine adding and removing users gets reflected to the database, but the only problem is in the second to last line, the isntance savedDepartment will still contain removed Users because hibernate populates its data from a select that happens before the changes done to user is actually flushed to the database. What is the best approach here,
Should I flush before saving the department (and if saving department fails will the transaction be rolled back)
Should I refresh the savedInstance before returning it to the caller
any other Ideas.
There is no need to save anything since you're using attached entities. JPA automatically persists the changes you do to attached entities.
And when you have a bidirectional association, it's your responsibility to make the changes at both sides to ensure that your object model is consistent. If you set user.department to null, then you should also remove the user from department.users.
I don't understand what the above code is doing, but remove the calls to save, and maintain both sides of the association, and everything will be fine.
I have a couple of entities that will be deleted. Those entities will have a couple Many-To-Many links. When updating a many-to-many link I am thinking I will just delete the original links in the joining table for that entity and just replace the link with a new link. But I am really confused on how that would work. How do you update a many-to-many relationship? Same thing goes for delete. If you have cascade for deletes set, then you would essentially delete that entity and the entities (collection, multiple entities I believe) that are linked to it. How would that work?
#Entity
#Table(name="QUICK_LAUNCH")
public class QuickLaunch implements Serializable {
...
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="QUICK_LAUNCH_PROVIDER",
joinColumns=#JoinColumn(name="QUICK_LAUNCH_ID"),
inverseJoinColumns=#JoinColumn(name="RESOURCE_ID"))
private List<Provider> providerList;
}
The thinking was that I would delete any links to a provider and just add new links. I could do that programatically, just delete links in the linking table, but I feel as though that should be handled by the ORM (is that an unreasonable feeling?).
Does anybody have any general words of wisdom for Deleteing Many-To-Many relationships,
Maybe I could just delete and update relationships using the actual entities...
Like say I have a quickLaunch with a list of providers... I set that list of providers to null (effectively removing that list of providers from that entity I would hope) I would then set that list of providers to a new list of providers... I of course would have to set up that list programatically. Does that sounds feasable or just freaking stupid?
That's the way to do:
removing a provider from the list will remove the association between the QuickLaunch and the provider (and thus delete the corresponding row from the join table);
adding a provider to the list will create the association between the QuickLaunch and the provider (and thus insert a row in the join table).