Symfony joining tables - symfony

I'm completely new to Symfony and I'm struggling with the query builder.
I have card entity and transaction entity in manytomany relation.
card entity: (id, card_number, code)
transaction entity: (id, amount, source, destination)
card_transaction: (card_id, transaction_id)
I want to get all the transactions that have a given card number.

We'd typically need to see the source for both of those entity classes.
Have you made a repository class for the CardTransaction entity yet? You could create this to have a reusable query as follows:
<?php
namespace App\Repository;
class CardTransactionRepository extends \Doctrine\ORM\EntityRepository
{
public function findByCardNumber(int $cardNumber): array
{
if ($cardNumber < 1) {
throw new \InvalidArgumentException("Card ID invalid.");
}
$qb = $this->createQueryBuilder("ct")
->leftJoin("ct.card", "c")
->andWhere("c.cardNumber = :cardNumber")
->setParameter("cardNumber", $cardNumber)
;
return $qb->getQuery()->getResult();
}
}
That being said what you're doing is such a simple query/operation you shouldn't need a query builder to do it. This is precisely what entity classes make easy, including Doctrine's ArrayCollection class.
If you're just doing this in a controller or view and have already loaded the Card entity you don't need a query in a repository class. Just iterate $card->getCardTransactions() assuming you've created the appropriate data accessor functions in Card. -- Then again, without seeing the Card and CardTransaction entity sources I couldn't tell you exactly what method names you have (or need).
You can retrieve a Card entity by its card_number property in a controller as follows:
$card = $this->getRepository(Card::class)->findOneByCardNumber($cardNumber);
Ideally you should check it exists too though kind of like this:
if (!($cardNumber = intval($cardNumber))) { // Do something better to validate the card number that has been specified, e.g. a `preg_match()`
throw $this->createNotFoundException("Card not specified.");
}
if (!($card = $this->getRepository(Card::class)->findOneByCardNumber($cardNumber))) {
throw $this->createNotFoundException("Card not found.");
}

Related

Where to put function/method to test result of SQL Query

I continue to struggle with Symfony in regards to where to put some logic and functions.
In this case, I have a simple query. I want to know how many "Profiles" depend on an address.
the query:
SELECT count(*)
FROM beneficiary_profile AS bp
JOIN person AS p ON bp.beneficiary_id = p.id
JOIN contact_address AS ca ON p.contact_address_id = ca.id
WHERE ca.id = 2108 -- address id
the poor way of doing this [in my controller] with entity methods is this
$dependant = 0;
foreach ($address->getPeople() as $person) {
if ($person->getBeneficiaryProfile() !== null) {
$dependant++;
}
}
ultimately, this serves as a flag to print a warning about editing a dependent address record.
My first thought was to add a new method to the entity, $address->isDependent() that would return bool based on ($count > 1) but this would require me to get the entity repository from the entity.
Soon there will be a whole host of logic to go with unlinking then deletion (or not) to prevent orphan records. The logic is not as simple as cascading as there can be many people between the address and profile. there is a house record in the mix as well.
Should I just build my query in the address repository, then set the flag in the controller? (twig reads the flag and displays the warring or not)
Entity repository is the class which holds all your queries to database related to some entity, in your case it is address. So, yes, you should create new method in AddressRepository and use where you need.
Not sure what you mean by code reuse, you can get repository almost everywhere in symfony classes. So, you write something like
$count = $this->getDoctrine()
->getRepository(ContactAddress::class)
->getDependencyCount($address->getId());
And use this $count variable in your code. As I already said it is simple and clear.
And in symfony4 you can inject even the repository in your controller, so code can be reduced to something like:
public function __construct(AddressRepository $repo)
{
$this->repo = $repo;
}
public function someAction()
{
$count = $this->repo->getDependencyCount($address->getId());
}

How to replace EntityManager::merge in Doctrine 3?

I am working an Symfony 2.8 based web app project which currently uses Doctrine 2. The project is basically a simple ToDo list application which can be synced with a mobile app (iOS/Android).
While reading the Update notes of Doctrine 3 I discovered, that EntityManager::merge will no longer be supported.
An alternative to EntityManager#merge() is not provided by ORM 3.0,
since the merging semantics should be part of the business domain
rather than the persistence domain of an application. If your
application relies heavily on CRUD-alike interactions and/or PATCH
restful operations, you should look at alternatives such as
JMSSerializer.
I am not sure what is the best/correct way to replace EntityManager::merge?
Where do I use merge:
During the sync of the mobile apps with the web app the data is transferred as serialized JSON which is than de-serialized by JMSSerializer to an entity object. When the web app receives a ToDoEntry object this way, it can be a new ToDo-Entry (not known in the web app yet) or an updated existing entry. Either way, the received object is not managed by the EntityManager. Thus $em->persist($receivedObject) will always try to insert a new object. This will fail (due to the unique constraint of the id) if the ToDo-Entry already exists in the web app and needs to be updated.
Instead $em->merge($receivedObject) is used which automatically checks wether an insert or update is required.
Hot wo solve this?
Of course I could check for every received objects if an entity with the same ID already exists. In this case could load the existing object and update its properties manually. However this would be very cumbersome. The real project of course uses many different entities and each entity type/class would need its own handling to check which properties needs to be updated. Isn't there a better solution?
You can try to use registerManaged() method of Doctrine\ORM\UnitOfWork.
// $this->em <--- Doctrine Entity Manager
// $entity <--- detached Entity (and we know that this entity already exists in DB for example)
$id = [$entity->getId()]; //array
$data = $entity->toArray(); //array
$this->em->getUnitOfWork()->registerManaged($entity, $id, $data);
Of course, You can check the state of Your Entity using getEntityState() of Doctrine\ORM\UnitOfWork before/after perfoming needed actions.
$this->eM->getUnitOfWork()->getEntityState($entity, $assert = 3)
$assert <-- This parameter can be set to improve performance of entity state detection by potentially avoiding a database lookup if the distinction between NEW and DETACHED is either known or does not matter for the caller of the method.
While I have posted this question quite a while ago, it is still quite active. Until now my solution was to stick with Doctrine 2.9 and keep using the merge function. Now I am working on new project which should be Doctrine 3 ready and should thus not use the merge anymore.
My solution is of course specific for my special use case. However, maybe it is also useful for other:
My Solution:
As described in the question I use the merge method to sync deserialized, external entities into the web database where a version of this entity might already exist (UPDATE required) or not (INSERT required).
#Merge Annotation
In my case entities have different properties where some might be relevant for syncing and must be merged while others are only used for (web) internal housekeeping and must not be merged. To tell these properties appart, I have created a custom #Merge annotation:
use Doctrine\Common\Annotations\Annotation;
/**
* #Annotation
* #Target("PROPERTY")
*/
final class SyncMerge { }
This annotation is then be used to mark the entities properties which should be merged:
class ToDoEntry {
/*
* #Merge
*/
protected $date;
/*
* #Merge
*/
protected $title;
// only used internally, no need to merge
protected $someInternalValue;
...
}
Sync + Merge
During the sync process the annotation is used to merge the marked properties into existing entities:
public function mergeDeserialisedEntites(array $deserializedEntities, string $entityClass): void {
foreach ($deserializedEntities as $deserializedEntity) {
$classMergingInfos = $this->getMergingInfos($class);
$existingEntity = $this->entityManager->find($class, $deserializedEntity->getId());
if (null !== $existingEntity) {
// UPDATE existing entity
// ==> Apply all properties marked by the Merge annotation
foreach ($classMergingInfos as $propertyName => $reflectionProperty) {
$deserializedValue = $reflectionProperty->getValue($deserializedEntity);
$reflectionProperty->setValue($existingEntity, $deserializedEntity);
}
// Continue with existing entity to trigger update instead of insert on persist
$deserializedEntity = $existingEntity;
}
// If $existingEntity was used an UPDATE will be triggerd
// or an INSERT instead
$this->entityManager->persist($deserializedEntity);
}
$this->entityManager->flush();
}
private $mergingInfos = [];
private function getMergingInfos($class) {
if (!isset($this->mergingInfos[$class])) {
$reflectionClass = new \ReflectionClass($class);
$classProperties = $reflectionClass->getProperties();
$propertyInfos = [];
// Check which properties are marked by #Merge annotation and save information
foreach ($classProperties as $reflectionProperty) {
$annotation = $this->annotationReader->getPropertyAnnotation($reflectionProperty, Merge::class);
if ($annotation instanceof Merge) {
$reflectionProperty->setAccessible(true);
$propertyInfos[$reflectionProperty->getName()] = $reflectionProperty;
}
}
$this->mergingInfos[$class] = $propertyInfos;
}
return $this->mergingInfos[$class];
}
That's it. If new properties are added to an entity I have only to decide whether it should be merged or not and add the annotation if needed. No need to update the sync code.
Actually the code to handle this can be just a few lines. In background Doctrine will issue a query to search for your entity if not already in memory, so you can do the same by doing the query yourself with result cache enabled, and then just use PropertyAccessor to map the data.
https://symfony.com/doc/current/components/property_access.html
See this gist for a POC https://gist.github.com/stevro/99060106bbe54d64d3fbcf9a61e6a273

Multiplicity constraint violated. The role '...' of the relationship '...' has multiplicity 1 or 0..1

I'm getting the following error from my DbContext: "Multiplicity constraint violated. The role 'MyEntity' of the relationship 'MyModel.FK_ChildEntities_MyEntities' has multiplicity 1 or 0..1."
using ASP.NET, Entity Framework 4
Working with a detached entity
The error happens the second time I try to reattach an entity to the dbcontext. The scenario is an unsuccessful save followed by a reattempt.
I have a detached entity in session. The user changes properties in a form, add things, removes things and finally clicks save. I get an attached copy of the entity from a new instance of the dbcontext, apply changes from the detached entity to the attached entity, validate, find an error and abort. The user changes whatever and saves again.
On the second save, the whole save process repeats, only this time it all goes to hell. Pretty much everything is duplicated, causing one error or another or all of them. Values from views and lookup tables that are only supposed to be references are created new and reassigned id's. Most of those issues I've been able to resolve, but I'm left with the multiplicity error. Child elements are being created as exact copies of other child elements, down to the unique id, only in the Added state. Or, if I reference certain properties, instead of cloning an unmodified child, it drops the new one. Either way, none of the code is executing as it did the first time around.
I'm discarding the instance of the dbcontext and the attached entity each save attempt. I thought that would be enough to revert any changes but something must be sticking around. The only thing not discared or reset is the detached entity, which is in session, but I dont make any changes to it. At least not directly.
The code (very simplified) is something like this:
void Save()
{
using (var context = new MyContext())
{
// detached entity from session
MyEntity detachedEntity = (MyEntity)Session["DetachedEntity"];
// attached entity from context
MyEntity attachedEntity = context.MyEntities.Single(x=>x.id == detachedEntity.id);
// <remove children representing lookup table elements from detachedEntity to prevent duplicates>
// <remove children representing view elements from detachedEntity to prevent duplicates>
// <apply changes from detachedEntity to attachedEntity>
// <add new children>
// <remove deleted children>
// <update modified children>
// <set entity state to unchanged on view and lookup elements of attachedEntity to ensure no duplicates...>
// <validate>
if (errors.count>0)
// <report errors>
else
context.SaveChanges();
}
}
as an example, this generates a multiplicity error:
// represents first save:
using (var context = new MyContext())
{
// detached entity from session
MyEntity detachedEntity = (MyEntity)Session["DetachedEntity"];
// attached entity from context
MyEntity attachedEntity = context.MyEntities.Single(x=>x.id == detachedEntity.id);
int debug1 = context.ChangeTracker.Entries<ChildEntity>().Count(); // debug1 == 0;
attachedEntity.ChildEntities.Add(detachedEntity.ChildEntities.First());
int debug2 = context.ChangeTracker.Entries<ChildEntity>().Count(); // debug2 == 1;
}
// represents second save:
using (var context = new MyContext())
{
// detached entity from session
MyEntity detachedEntity = (MyEntity)Session["DetachedEntity"];
// attached entity from context
MyEntity attachedEntity = context.MyEntities.Single(x=>x.id == detachedEntity.id);
int debug1 = context.ChangeTracker.Entries<ChildEntity>().Count(); // debug1 == 0;
attachedEntity.ChildEntities.Add(detachedEntity.ChildEntities.First());
int debug2 = context.ChangeTracker.Entries<ChildEntity>().Count(); // multiplicity error;
}
somehow the dbcontext remembers what objects were added to it. if the exact same object shows up twice, it... blows
instead of adding child entities from my detached entity to the attached one, i should've been creating new copies of each child
ChildEntity detachedChild = detachedEntity.ChildEntities.First();
attachedEntity.ChildEntities.Add(new ChildEntity {
propertyA = detachedChild.propertyA,
propertyB = detachedChild.propertyB
});
instead of
attachedEntity.ChildEntities.Add(detachedEntity.ChildEntities.First());
The problem is that detachedChild.parent should be assigned attachedParent.
foreach(var detachedEntity in detachedEntities)
{
attachedEntity.ChildEntities.Add(detachedEntity);
detachedEntity.ParentEntity = attachedEntity;
}
What you are trying to do is something like:
ChildEntity childEntity = new ChildEntity()
{
//do mapping or provide data EXCEPt THE PRIMARY KEY
}
foreach(ParentEntity parentEntity in parentEntities)
{
parentEntity.Add(childEntity);
}
_dbContext.SaveChanges();
Result
Multiplicity constraint violated. The role '…' of the relationship '…' has multiplicity 1 or 0..1
The reason of the error message is
that everytime the _dbContext adds the childEntity to some parentEntity, it sets the generated primary key to the childEntity, so in the second loop of the foreach the primary key will be duplicated
The fix is - Method #1 - for simple scenarios
foreach(ParentEntity parentEntity in parentEntities)
{
//Make a new object every time
ChildEntity childEntity = new ChildEntity()
{
//do mapping or provide data EXCEPt THE PRIMARY KEY
}
parentEntity.Add(childEntity);
}
_dbContext.SaveChanges();
The fix is - Method #2 - for complex scenarios
using YOUR_PROJECT.ANY_FOLDER.DeepCopyExtensions;
ChildEntity childEntity = new ChildEntity()
{
//do mapping or provide data EXCEPt THE PRIMARY KEY
}
foreach(ParentEntity parentEntity in parentEntities)
{
//makes a copy of the childEntity object and pass it to the _dbContext, after saving each copy will be separated and the original object childEntity wont be touched
parentEntity.Add(DeepCopyByExpressionTrees.DeepCopyByExpressionTree(childEntity));
}
_dbContext.SaveChanges();
What is this method "DeepCopyByExpressionTrees.DeepCopyByExpressionTree(childEntity)" ?
Check this project here, download the source code, and only include the class file "DeepCopyByExpressionTrees.cs" to your project as a helper class and start using it any where.
Thanks
Make sure to inspect the properties of the object you are trying to add. In my case it was mistakenly referencing the same invalid object on each add which it didn't like and thus threw the same error you have here.
EF 6 Update
For me setting object state to added worked on sounds logical also
ChildEntity detachedChild = detachedEntity.ChildEntities.First();
var newChild = new ChildEntity {
propertyA = detachedChild.propertyA,
propertyB = detachedChild.propertyB
});
// Mark all added entity entity state to Added
attachedEntity.ChildEntities.Add(newChild );
db.Entry(newChild ).State = EntityState.Added;
http://www.entityframeworktutorial.net/EntityFramework4.3/update-one-to-many-entity-using-dbcontext.aspx
I experienced this error when I had navigation properties that had not been set or navigation properties that belonged to the wrong Code First DBContext
I fixed this by making the child collections in the parent entity virtual. This allows one to easily Update the entity when its child collections don't change, which, for me, was most of the time.
I had a similar issue, but mine arose from a AsNoTracking() after my query.
I had something like this
var myObject = dbContext.GetRepo<myType>().Query().AsNoTracking().SingleOrDefault()
And then later on I use that object to set anther object.
var myChild = new Child { parent = myObect }
and apparently EntityFramework tries to create a brand new object and hence causes a multiplicity error.

Breeze: How can I create a GUID key for new entities on the client?

Using Breeze, what is the simplest way to populate a GUID key when an entity is created?
I'll assume that your entity is configured such that the client is responsible for setting the Guid key for new entities. That's the default for the Guid key of an Entity Framework Code First entity; it is as if the key property were adorned with [DatabaseGenerated(DatabaseGeneratedOption.None)]
The obvious approach is to set the key after creating the entity and before adding it to the manager, e.g.:
function createFoo() {
var foo = fooType.createEntity();
foo.id(breeze.core.getUuid()); // Knockout implementation
manager.addEntity(foo);
}
This may be all you ever need.
On the other hand, you may find that you're creating new Foos in many places and for some strange reason you can't use the createFoo function. You certainly don't want to repeat that code.
You can extend the Foo entity type with id-setting behavior after which you'd be able to write:
function createFoo() {
var foo = fooType.createEntity(); // foo.id is set for you
manager.addEntity(foo);
}
There are two approaches to consider - custom constructor and type initializer; both are described in "Extending Entities"
Constructor
You can initialize the key inside a custom constructor. Breeze calls the constructor both when you create the entity and when it materializes a queried entity. Breeze will replace the initial key value when materializing.
Here's an example that assumes the Knockout model library.
function Foo() {
foo.id(breeze.core.getUuid()); // using KO
}
// one way to get the MetadataStore
var store = manager.metadataStore;
// register the ctor with the Foo type
store.registerEntityTypeCtor("Foo", Foo);
Pretty simple. The only downside is that Breeze will generate a Guid every time it makes an entity, whether creating a new one or materializing one from a query. It's wasted effort during materialization but so what? Well, I suppose that might become a performance issue although I wouldn't assume so until I had measured it.
Initializer
Suppose you measured and the repeated Guid generation is a serious problem (really?). You could set the key in a type initializer instead and only call the Guid generator when creating a new entity.
Breeze calls a type initializer after the entity has been created or materialized from query just before returning that entity to the application. Clearly you don't want to overwrite a materialized key from the database so you'll test the key value to make sure it's not real (i.e. to make sure you're fixing a created entity) before assigning it. Here's an example.
function fooInitializer(foo) {
var emptyGuid = "00000000-0000-0000-0000-000000000000";
if (foo.id() !=== emptyGuid) {
foo.id(breeze.core.getUuid());
}
}
var store = manager.metadataStore;
// register the initializer; no ctor in this example
store.registerEntityTypeCtor("Foo", function(){}, fooInitializer);
Assuming you have a Guid surrogate Key on all your entities like we have in our case, you could code a createInstance factory that does the following in a very generic approach:
function createInstance(breezeEntityManager, typeName) {
var keyProperty = breezeEntityManager.metadataStore.getEntityType(typeName, false).dataProperties.filter(function (p) {
return p.isPartOfKey;
})[0];
var config = {};
config[keyProperty.name] = breeze.core.getUuid();
return breezeEntityManager.createEntity(typeName, config);
}
This way, you won't have to create an initializer for all your entities.

ASP.Net Entity Framework Repository & Linq

My scenario:
This is an ASP.NET 4.0 web app programmed via C#
I implement a repository pattern. My repositorys all share the same ObjectContext, which is stored in httpContext.Items. Each repository creates a new ObjectSet of type E. Heres some code from my repository:
public class Repository<E> : IRepository<E>, IDisposable
where E : class
{
private DataModelContainer _context = ContextHelper<DataModelContainer>.GetCurrentContext();
private IObjectSet<E> _objectSet;
private IObjectSet<E> objectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this._context.CreateObjectSet<E>();
}
return _objectSet;
}
}
public IQueryable<E> GetQuery()
{
return objectSet;
}
Lets say I have 2 repositorys, 1 for states and 1 for countrys and want to create a linq query against both. Note that I use POCO classes with the entity framework. State and Country are 2 of these POCO classes.
Repository stateRepo = new Repository<State>();
Repository countryRepo = new Repository<Country>();
IEnumerable<State> states = (from s in _stateRepo.GetQuery()
join c in _countryRepo.GetQuery() on s.countryID equals c.countryID
select s).ToList();
Debug.WriteLine(states.First().Country.country)
essentially, I want to retrieve the state and the related country entity. The query only returns the state data... and I get a null argument exception on the Debug.WriteLine
LazyLoading is disabled in my .edmx... thats the way I want it.
You're doing a join without retrieving anything from it. There are multiple solutions to your problem:
Use Include to load the dependent entities: from s in ((ObjectSet<State>) _stateRepo.GetQuery).Include("Country"). The problem with this approach is that you should expose the ObjectSet directly rather than as a IQueryable if you want to avoid casting.
Use context.LoadProperty(states.First(), s => s.Country) to explicitly load the Country from the database for a given state.
Select both entities in the query: from s in ... join c ... select new { s, c }. You won't be able to access directly the state's Country property but you have it in the anonymous type.
Enable lazy loading.
Your repository implementation is very similar to mine, especially the way you are storing the ObjectContext. It works fine for me, so I don't think it's a conceptual problem.
Try using a static objectcontext (no wrapper) just to see if that fixes the problem. Perhaps there is a bug in your ContextHelper which causes your context to get disposed and recreated.

Resources