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
Related
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());
}
I have an entity called foo which has an OneToMany association with an entity called bar that is accessible as $foo->getBar() (an ArrayCollection). Normally calling $foo->getBar() would trigger a Lazy Loading of associated bar entities (if they weren't joined originally).
How can I check if bar has been loaded without triggering a Lazy Load? I don't need the associated entities, if they weren't loaded originally, and I don't want them to load, I just want to know IF they were loaded.
Example
In the fooRepository I have a method called getFooWithBar() and that has a join which loads all the bars as an ArrayCollection and returns foo with all the associated bar entities. But if I just call a simpler method like getFooById() with a simple query, the bar entities were not loaded with a join, so they are not contained in $foo.
So in another controller I have $foo, and I want to check if getBar() has associated entities loaded yet, but I do not want to trigger the Lazy Loading. If it doesn't have associated entities, I don't want them. I just need to know IF they have been loaded.
NOTE: I also do not want to turn off Lazy Loading on the entity association for all instances.
Method that Doesn't Work for Inverse side of OneToMany
I put this magic getter method in my entity:
public function __get($property) {
return isset($this->$property) ? $this->$property : null;
}
Which theoretically lets me check if the property is set (or if it's still the default private declaration). And this works when my entity is the owning side. But if it's the inverse side, $this->property is never set. Doctrine does some fancy stuff so that when you do getProperty() it's looking at the data somewhere else. I figured this out because this function works when it's the owning side (it returns a proxy of the associated entity), but it returns null when the associated entity is owned by the other entity.
After years of testing our code (responding to Doctrine changes) the following is the best solution we could come up with to check if an association has been loaded, WITHOUT trigger LazyLoad. None of this stuff is documented in Doctrine (unfortunately), so you have to look at the source code and/or play with the code.
The Solution
In the end there are many different types of different associations that could be loaded from *ToMany (PersistentCollection) or *ToOne associations (Proxy or direct entity). This means we need to create a method that checks for all the possibilities (that we are currently aware of in our app). We created a trait that we add to all our entities, so we can call $entity->isLoaded($propertyName) to check if it's loaded.
public function isLoaded($property)
{
// *ToMany Association are PersistentCollection and will have the isInitialized property as true if it's loaded
if ($this->{$property} instanceof PersistentCollection) {
return $this->{$property}->isInitialized();
}
// *ToOne Associations are (sometimes) Proxy and will be marked as __isInitialized() when they are loaded
if ($this->{$property} instanceof Proxy) {
return $this->{$property}->__isInitialized();
}
// NOTE: Doctrine Associations will not be ArrayCollections. And they don't implement isInitalized so we really
// can tell with certainty whether it's initialized or loaded. But if you join entities manually and want to check
// you will need to set an internal mapper that records when you've loaded them. You could return true if count > 0
if ($this->{$property} instanceof ArrayCollection) {
// NOTE: __isLoaded[$property] is an internal property we record on the Setter of special properties we know are ArrayCollections
return (!empty($this->__isLoaded[$property]) || $this->{$property}->count() > 0);
}
// NOTE: there are never any Collections that aren't ArrayCollection or PersistentCollection (and it does no good to check because they won't have isInitialized() on them anyway
// If it's an object after the checks above, we know it's not NULL and thus it is "probably" loaded because we know it's not a Proxy, PersistentCollection or ArrayCollection
if (is_object($this->{$property})) {
return true;
}
// If it's not null, return true, otherwise false. A null regular property could return false, but it's not an Entity or Collection so indeed it is not loaded.
return !is_null($this->{$property});
}
When you load your foo object, bar will be an instance of Doctrine\ORM\PersistentCollection. You can call the isInitialized() method on this collection to find out if has been initialized.
For Associations that are an ArrayCollection:
$initialized = $foo->getBar()->isInitialized();
If you have newest version of Doctrine, you can try extra lazy load on column.
More Extra lazy associations
I'll preface this question with the following: I know there are a million posts on the internet about the old "An object with the same key already exists in the ObjectStateManager" issue. My scenario is a bit more complicated, I think.
I have a UnitOfWork class which creates a DbContext and passes it to any repository which is called. The pattern I'm using closely follows the Unit of Work tutorial on the ASP.NET site. Unlike the tutorial, my repositories take in Business entities, map them to data entities, and perform some CRUD action. My Business logic only works with Business entities. Here is what I'm trying to do in a sample Business Manager class:
_unitOfWork.Repository.Add(entity);
_unitOfWork.Save(); // context.SaveChanges() under the hood
...Perform some operations on the model...
_unitOfWork.Repository.Update(entity);
_unitOfWork.Save();
Here is a sample Update method from the repository:
public virtual void Update(entity)
{
var dataEntity = // map from business entity to data;
_context.Entry(dataEntity).State = EntityState.Modified;
}
It obviously fails on the last line. Here is where my confusion sets in:
The entity's State is Detached
When I attempt to change the State to Modified or Unchanged, it gives me the ObjectStateManager exception above.
When I attempt to detach the entity from the context (((IObjectContextAdapter)_context).ObjectContext.Detach(entity);) I get an exception about how the entity is not attached to the context, therefore, it cannot detach it. Very confusing (something fundamental I'm missing, for sure).
Many other posts suggest I make a database call, update that entity in the repository, then _unitOfWork.Save(). I don't like this approach. I shouldn't need to make an unnecessary network call to update an entity.
The Update method in the repository needs to handle two scenarios: 1) updating an entity which is not currently tracked by the context, and 2) updating an entity which IS currently tracked by the context. The second piece is what I'm struggling with.
Any help or insight is appreciated.
Thanks!
This means that there already is an object attached to the context with the same key as the new dataEntity. The existing object and the new entity both represent the same entry in the database but they are two different objects.
This may indicate that the lifespan of your _context is too long, but that's hard to judge from your code. It is certain though that the context was previously used to fetch an entity from the database that is subsequently duplicated by var dataEntity = ....
You may have to shorten the lifespan of the context, I can't tell. If you think it's OK you may want to use the Local collection to check whether the entity is already there. That will save the database round trip that Find may still make.
I found a hybrid solution which appears to work:
public virtual void Update(TB entity)
{
var dataEntity = Mapper.Map<TB, TD>(entity);
var pkey = _dbSet.Create().GetType().GetProperty("Id").GetValue(dataEntity);
var entry = _context.Entry(dataEntity);
if (entry.State == EntityState.Detached)
{
var attachedEntity = _dbSet.Find(pkey);
if (attachedEntity != null)
{
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(dataEntity);
}
else
{
entry.State = EntityState.Modified;
}
}
else
{
entry.State = EntityState.Modified;
}
}
I am develloping a web form that has a wizard with 4 steps:
On each step I'me creating new entities generated from a database.
The problem is that being a wizzard, the user can change the properties of the controls that will originate the values to be stored.
So I need to release the created entity objects or return that entity values to the original rows stored on the database.
How can I do this.
Should'n it work if I set each created entity object to null?
By the way this is how I'm doing it:
entities = new Entities();
...
Client client = new Client();
client.name = tbxName.text
...
entities.SaveChanges();
entities.Connection.Close();
So If this code is executed on the 2nd wizard part of a wizard of 3 parts and I go back and fowrward through this set more the once the client creating runs more than once, so there's my problem.
So how can I unCreate it :-P
Thannks!!!
If you are building wizard you must manage it as single operation. It means that you have to store built entity graph in the session and save it only if whole wizard is completed and confirmed. Your step logic also must check if related data are already present in the entity graph and use them instead of creating new one.
If your using Entity Framework, why not implement the Unit Of Work pattern? Each part of your wizard builds the UoW and the "final step" commits the unit of work.
There was an article called "The Unit Of Work Pattern And Persistence Ignorance" in MSDN magazine a few years ago that explains the concept.
This is the way I do it:
1- Create a place where you can manage your Session variables :
public class SessionObjects { }
2- I save my ObjectContext in the Session so I create a property to manage it in the mentioned class :
public static ObjectContextEntities ObjectContextEntities
{
get
{
return (ObjectContextEntities)HttpContext.Current.Session["ObjectContextEntities"];
}
set
{
HttpContext.Current.Session["ObjectContextEntities"] = value;
}
}
3- Initialize the ObjectContext on the wizard's start and dispose it on its end:
void StartWizard()
{
SessionObject.ObjectContextEntities = new ObjectContextEntities();
}
void StartWizard()
{
SessionObject.ObjectContextEntities = new ObjectContextEntities();
}
void EndWizard()
{
((ObjectContextEntities)SessionObject.ObjectContextEntities).Dispose();
}
4- To save wizard result to the database you can call:
void SaveWizard()
{
((ObjectContextEntities)SessionObject.ObjectContextEntities).SaveAllChanges();
}
5- To reset wizard simply call EndWizard then StartWizard .
I guess you know how to manage your ObjectEntity objects and in the ObjectContext so you can continue from here by your self ..
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.