Will Doctrine2 select all fields on all associations (JOINS from a query) to populate the full aggregate object? - doctrine-orm

I'm researching whether to try Doctrine2 or not. One thing that scares me is the over SELECTing of columns I don't need (ie. consider lots of varchars being selected unnecessarily).
You might ask: but don't you want your full entity object filled? Yes, unless I'm looking for an array hydration. However, many times I don't need the full aggregation filled. Take the association shown below. If I query the Users table with a JOIN on Address, will all the columns from the address table be SELECTed as well (and therefore populated into an address object inside of users object)? Now imagine we have more JOINs. This could get really bad. What if I only want the fields from User populated in just a users only object? I guess I'm a little confused at what Doctrine is doing behind the scenes with associations and query JOINs.
/** #Entity **/
class User
{
// ...
/**
* #ManyToOne(targetEntity="Address")
* #JoinColumn(name="address_id", referencedColumnName="id")
**/
private $address;
}
/** #Entity **/
class Address
{
// ...
}
So does Doctrine2 populate all the fields of all the objects within the aggregate after a query (unless I specifiy partial)?

It depends on your query, but generally it is not implicit.
Using the query builder, you can fetch the associated record like this:
<?php
$qb = $em->createQueryBuilder();
$query = $qb->select(array("u", "a"))
->from("User", "u")
->innerJoin("u.address", "a")
->getQuery();
In the select() statement you specify what to fetch, in this case you get both.
If you only fetch the User records, then when you get the associated record with $user->getAddress(), Doctrine will make the query on the fly and hydrate the Address record for you.
That said, performance wise it is better to select both entities so Doctrine will make only one query and not 1+N queries

Related

OData action in data entity for table without natural key

In Dynamics 365 Finance and Operations, I created data entity for WMSOrderTrans table. Let's say it's SalesOrderPickingListLines. I've added EntityKey with two fields: orderId and RecId (the same index exists in the table itself, named OrderIdx).
The data entity works without an issue, key fields are in the metadata, I can get a record using data/SalesOrderPickingListLines(dataAreaId='inpl',orderId='myorder',RecId1=myrecordid)
The problem is, that now I need to create OData action for it, but it seems that that the key fields are not seen in the action. I am using Power Automate to test it.
I created a function in the entity's class with this code:
[SysODataActionAttribute('SetPickQuantity', true)]
public real setPickQuantity(InventQty _qty)
{
WMSOrderTrans _wmsOrderTrans;
select firstonly forupdate _wmsOrderTrans
where _wmsOrderTrans.orderId == this.orderId && _wmsOrderTrans.RecId == this.RecId1;
// some code to proceed
return _wmsOrderTrans.Qty;
}
The OData action is seen by Power Automate, but there are no key fields (except Company), so I am unable to perform this action for any record.
Usually it works without doing any other steps so that's why I'm here. The only difference with this and my previous work is that the source table (WMSOrderTrans) does not have a natural key, its index is RecId field. But I am not sure if this is the issue here. Any help will be appreciated.

Doctrine - QueryBuilder, select query with where parameters: entities or ids?

I have a Product entity and a Shop entity.
A shop can have 0 to n products and a product can be in only one shop.
The product entity table is thus refering to the Shop entity through a shop_id table field.
When querying for the products of a given shop using doctrine query builder, we can do this:
$products = $this->getDoctrine()->getRepository('MyBundle:Product')
->createQueryBuilder('p')
->where('p.shop = :shop')
->setParameter('shop', $shop) // here we pass a shop object
->getQuery()->getResult();
or this:
$products = $this->getDoctrine()->getRepository('MyBundle:Product')
->createQueryBuilder('p')
->where('p.shop = :shopId')
->setParameter('shopId', $shopId) // we pass directly the shop id
->getQuery()->getResult();
And the both seem to work... I'm thus wondering: can we always pass directly entity ids instead of entity instances in such cases (ie: on a doctrine entity field that refers to another entity)?
I initially thought that only the first example would work...
According to the Doctrine documentation, you can pass the object to setParameter() if the object is managed.
extract from the documentation:
Calling setParameter() automatically infers which type you are setting as value. This works for integers, arrays of strings/integers, DateTime instances and for managed entities.
for more information, please see:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html#binding-parameters-to-your-query
I might be wrong, but I think that if you pass object instance instead of id number, Doctrine will automatically call $instance->getId() making your two queries the same when translated into SQL (even DQL).

Delete associated/referenced entity

I have a table product, the items in these table are referenced in tables such as cart_item and order_item as well as shipping_item etc.
All these references are optional (the product_id is set to nullable in those tables).
I need to have a way to delete a product and still keeping the other tables's records. One way I can think of is to go into all those tables, set the product_id to null, then go back to the product table to delete. However, since I may not know all the tables that are referencing to product (many other bundles can have entities that are referencing to this product), is there a way that I can know all these association to loop through and set null?
(Or perhaps there is a better way?)
PS: the idea that this is a shopping cart and the owner may want to remove expired products to clean up but for ordered, shipped items they still need to keep records.
Edit1:
This is the definition of the product reference in the OrderItem entity:
/**
* #var \Product
*
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* })
*/
private $product;
The error I'm getting:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1451
Cannot delete or update a parent row: a foreign key constraint fails
(test.order_item, C ONSTRAINT fk_order_item_product1 FOREIGN KEY
(product_id) REFERENCES product (id) ON DELETE NO ACTION ON
UPDATE NO ACTION)
Edit2:
I initially set onupdate="SET NULL" to the order_item entity and thought that was enough, it was not:
/**
* #var \Product
*
* #ORM\ManyToOne(targetEntity="Product")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* })
*/
private $product;
After that, I had to update db schema as well.
Assuming you have the proper relations set up between the owning entity product and the other entities e.g. cart_item that should have a foreign_key, your wanted behaviour is the default for doctrine 2.
Take a look here in the manual
As an example they show the deletion of a User entity and its corresponding Comments
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
The example states:
Without the loop over all the authored comments Doctrine would use an UPDATE statement only to set the foreign key to NULL and only the User would be deleted from the database during the flush()-Operation.
This suggests to me that in your case you actually want that behaviour. So just remove the product entity and doctrine 2 will automatically find all other entities with a foreign_key belonging to that product and will set it to NULL
Edit
Your error message suggests that upon attempted removal of the product entity there are still foreign_keys present, i.e. they have not been set to null properly by Doctrine.
You need to be sure to add the cascade property, specifically remove to your entity relationship. It would look something like the following:
<?php
class Product
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Cart", mappedBy="product", cascade={"remove"})
*/
private $carts;
//...
}

Open JPA how do I get back results from foreign key relations

Good morning. I have been looking all over trying to answer this question.
If you have a table that has foreign keys to another table, and you want results from both tables, using basic sql you would do an inner join on the foreign key and you would get all the resulting information that you requested. When you generate your JPA entities on your foreign keys you get a #oneToone annotation, #oneToMany, #ManyToMany, #ManyToOne, etc over your foreign key columns. I have #oneToMany over the foreign keys and a corresponding #ManyToOne over the primary key in the related table column I also have a #joinedON annotation over the correct column... I also have a basic named query that will select everything from the first table. Will I need to do a join to get the information from both tables like I would need to do in basic sql? Or will the fact that I have those annotations pull those records back for me? To be clear if I have table A which is related to Table B based on a foreign key relationship and I want the records from both tables I would join table A to B based on the foreign key or
Select * From A inner Join B on A.column2 = B.column1
Or other some-such non-sense (Pardon my sql if it is not exactly correct, but you get the idea)...
That query would have selected all column froms A and B where those two selected column...
Here is my named query that I am using....
#NamedQuery(name="getQuickLaunch", query = "SELECT q FROM QuickLaunch q")
This is how I am calling that in my stateless session bean...
try
{
System.out.println("testing 1..2..3");
listQL = emf.createNamedQuery("getQuickLaunch").getResultList();
System.out.println("What is the size of this list: number "+listQL.size());
qLaunchArr = listQL.toArray(new QuickLaunch[listQL.size()]);
}
Now that call returns all the columns of table A, but it lack's the column's of table B. My first instinct would be to change the query to join the two tables... But that kind of makes me think what is the point of using JPA then if I am just writing the same queries that I would be writing anyway, just in a different place. Plus, I don't want to overlook something simple. So what say you stack overflow enthusiasts? How does one get back all the data of joined query using JPA?
Suppose you have a Person entity with a OneToMany association to the Contact entity.
When you get a Person from the entityManager, calling any method on its collection of contacts will lazily load the list of contacts of that person:
person.getContacts().size();
// triggers a query select * from contact c where c.personId = ?
If you want to use a single query to load a person and all its contacts, you need a fetch in the SQL query:
select p from Person p
left join fetch p.contacts
where ...
You can also mark the association itself as eager-loaded, using #OneToMany(lazy = false), but then every time a person is loaded (vie em.find() or any query), its contacts will also be loaded.

Restrict query results based on attributes of 1 item in 1-n association

I have an entity 'Order', and a one-to-many associated entity 'OrderStatus' (so 1 order can have many statuses). The current status of an order is defined by the last status that was added to that order.
Now I want to create a DQL query which selects all orders with a certain status. However, because this is a one-to-many relation, I have no idea how to accomplish this in DQL. I only know of querying the collection of statuses as a whole.
Does anyone have an idea if this is even possible, or do I have to use a workaround?
We had this very same problem in a project we're working on. The query you're trying to perform is very similar to this question, except that you're trying to do this in DQL, which makes it even harder. I think (but I might be wrong) that DQL does not allow this kind of query, and you may achieve the result you're expecting with a native SQL query, with all the caveats this implies.
What we ended up with, and I strongly suggest to you, is to make the current status a property of your Order. This allows easy & fast querying, with no joins required. The change is really painless:
class Order
{
/**
* #ManyToOne(targetEntity="OrderStatus")
*/
protected $status;
/**
* #OneToMany(targetEntity="OrderStatus")
*/
protected $statuses;
public function setStatus($status)
{
$orderStatus = new OrderStatus($this, $status);
$this->statuses->add($orderStatus);
$this->status = $orderStatus;
}
}
$status can also be a simple string property, if your OrderStatus is basically composed of a status string and a date; the code would then become:
class Order
{
/**
* #Column(type="string")
*/
protected $status;
// ...
public function setStatus($status)
{
// ...
$this->status = $status;
}
}