I have an event subscriber for DocumentA. DocumentA has associated documents of type DocumentB. During the preUpdate lifecycle event hook for DocumentA, I'd like to refresh a value on its DocumentB. I have code like so:
public function preUpdate(LifecycleEventArgs $args)
{
$document = $args->getDocument();
if (!($document instanceof DocumentA) ||
return;
}
if ($documentsB = $document->getDocumentB()) {
$dm = $args->getDocumentManager();
foreach (iterator_to_array($documentsB) as $docB) {
$documentB = $dm->find(DocumentB::class, $docB->getId());
$documentB->setFooCode();
$dm->merge($documentB);
}
}
}
I've tried this with $dm->persist($documentB) instead of using merge(), I've set DocumentA's relationship to DocumentB to cascade: {all}, and I've tried $dm->getUnitOfWork()->recomputeSingleDocumentChangeSet($class, $document); for both DocumentA and each DocumentB, but I don't seem to be getting anywhere. I don't seem to be able to call flush() even for a single DocumentB without causing a segfault (I'm assuming it triggers an infinite loop of preUpdate events inside preUpdate events?)
How do I save the changes to my associated documents when the changes are made in the preUpdate method of DocumentA's event subscriber?
I elaborated on this further in one of your previous questions, but to reiterate from Doctrine's documentation:
Changes to associations of the updated entity are never allowed in this event
-Changes to associations of the passed entities are not recognized by the flush operation anymore.
With the level of complexity you are trying to handle in a listener, I think you would be better off making a service that handles some of this and call that instead.
Related
When event 'controller_action_predispatch_checkout_index_index' is executed I am trying to get order from database (not the one that will be created after checkout, the one that already exists in database and which is created before.). I am loading order through order repository and it works.
use Magento\Sales\Api\OrderRepositoryInterface as OrderRepository;
$this->orderRepository->get($approvalOrderId);
When I try to load the same order and with the same code sample when event 'sales_order_place_after' is executed it is not working. I am getting error 'No such entity with orderId = ..'
It might be that the order is not yet saved at that moment. But the event you are observing will already carry that order object. Can you try the below code in the observers execute function?
public function execute(EventObserver $observer)
{
$order = $observer->getEvent()->getOrder();
//do something with the $order
}
In trying to figure out how to delete entities from the database in Entity Framework, almost ever hit in google tells me that I need to use Remove(). As in:
ReportComment comment = report.ReportComments.LastOrDefault();
report.ReportComments.Remove(comment);
This is not true. This only orphans the entity. In the case of my ReportComment, it attempts to set the foreign key that points to the Report of which it is a child to null, and this causes the application to crash because the foreign key is set to non-null. They way I had to solve this was as follow:
First create GetContext in my service:
public IRiskAliveContext GetContext()
{
return _context;
}
Then I call this function in my controller:
IRiskAliveContext context = _projectService.GetContext();
Then I use the context to call Entry() and then set the state of the Entry to Deleted:
ReportComment comment = report.ReportComments.LastOrDefault();
report.ReportComments.Remove(comment);
context.Entry(comment).State = EntityState.Deleted;
Why do I need to do this? Why does Remove() not work like google says?
You are calling ICollection.Remove on the Navigation Property report.ReportComments, to delete from the database call DbSet.Remove.
Roughly you have "Remove this Comment from that Report", instead of "Remove this Comment from the Database"
So try something like:
context.ReportComments.Remove(comment);
instead of
report.ReportComments.Remove(comment);
I have a CellTable<UserProxy>. So in other words it manages directly entity proxies of my database entities. With that I use an AsyncDataProvider<UserProxy> that fetches the data using a request factory.
The cells of my columns are EditTextCell. And I added a FieldUpdater<UserProxy, String> to edit the values. Except here is my problem: if I update the value of the entity and save it immediately it works fine, but I don't know how I can differ the save to a click on a button later.
Basically, I want to implement the Apply-changes pattern (see: http://patterns.holehan.org/Review/ApplyChanges), so I want the user to be able to edit several values in the table and once he is done he can click the 'apply' button which will save all the changes.
So my idea for this was to change the value in the proxy entity without invoking save and then saving all modified entities in the clickhandler of the button.
But to make the change to a value in a proxy entity, I must call ctx.edit(user) first:
nameColumn.setFieldUpdater(new FieldUpdater<UserProxy, String>() {
#Override
public void update(int index, UserProxy object, String value) {
if (!value.equals(object.getName())) {
UserRequest ur = presenter.getClientFactory().getRequestFactory().getUserRequest();
ur.edit(object);
object.setName(value);
saveButton.setEnabled(true);
}
}
});
And this makes it impossible to save them afterwards in the clickhandler of the apply button:
private void saveModifications() {
List<UserProxy> items = cellTable.getVisibleItems();
for (UserProxy item : items) {
UserRequest ur = presenter.getClientFactory().getRequestFactory().getUserRequest();
ur.save(item).fire();
}
cellTable.setVisibleRangeAndClearData(cellTable.getVisibleRange(), true);
}
Because calling save(item) throws this exception: java.lang.IllegalArgumentException: Attempting to edit an EntityProxy previously edited by another RequestContext
How to avoid this without having to make yet another class representing the same entity?
You must use a single RequestContext instance where you edit() all your proxies. You can edit() several times the same proxy with no error and no overhead.
So:
store presenter.getClientFactory().getRequestFactory().getUserrequest() in a variable/field somewhere
in the FieldUpdaters, ctx.edit(object).setName(value) will enqueue the changes in the RequestContext; possibly put the UserProxy in a Set too for later reference
in saveModifications, loop over your proxies (possibly only those from the Set built on step 2) and ctx.save(item) and then at the end of the loop ctx.fire()
using the unit of work and repository patterns i recently came across the issue, that changes to the unit of work are not reflected to subsequent queries. Example:
var ctx = DIContainer.Current.Resolve<IB2bContext>();
var rep = DIContainer.Current.Resolve<IRepository<Word>>(
new DependencyOverride<IB2bContext>(ctx));
rep.Add(new Word () { "One" };
rep.Add(new Word () { "Two" };
rep.GetAll().ToList().ForEach(i =>
Console.Write(i.text)); // nothing seen here
So in other words, unless i call SaveChanges() to persist the objects into the Database, i dont see them. Well ofcause i can fiddle around with the ChangeTracker and/or do things like context.Entry(foo).Property(...).CurrentValue. But does that play with a ddd like decoupling of layers? I dont think so. And where is my consistent dataview that once was called a database transaction?
Please enlighten me.
Armin
Your repository exposes some GetAll method. The method itself executes database query. If you want to see local data not inserted to database you must add them to result set. For example like:
public IEnumerable<Word> GetAll()
{
DbSet<Word> set = context.Set<Word>();
return set.AsEnumerable().Concat(set.Local);
}
The query execution is only responsible for returning persisted (real) data.
To facilitate control reuse we created a solution with three separate projects: a control library, Silverlight client, and ASP.NET backend. The control library has no reference to the RIA Services-generated data model classes so when it needs to interact with it, we use reflection.
This has worked fine so far but I've hit a bump. I have a DataGrid control where the user can select a row, press the 'delete' button, and it should remove the entity from the collection. In the DataGrid class I have the following method:
private void RemoveEntity(Entity entity)
{
// Use reflection to remove the item from the collection
Type sourceType = typeof(System.Windows.Ria.EntityCollection<>);
Type genericType = sourceType.MakeGenericType(entity.GetType());
System.Reflection.MethodInfo removeMethod = genericType.GetMethod("Remove");
removeMethod.Invoke(this._dataGrid.ItemsSource, new object[] { entity });
// Equivalent to: ('Foo' derives from Entity)
// EntityCollection<Foo> ec;
// ec.Remove(entity);
}
This works on the client side but on the domain service the following error gets generated during the Submit() method:
"The UPDATE statement conflicted with
the FOREIGN KEY constraint
"********". The conflict occurred in
database "********", table "********",
column '********'. The statement has
been terminated."
One thing I noticed is the UpdateFoo() service method is being called instead of the DeleteFoo() method on the domain service. Further inspection shows the entity is going into the ModifiedEntities ChangeSet instead of the RemovedEntities ChangeSet. I don't know if that's the problem but it doesn't seem right.
Any help would be appreciated, thanks,
UPDATE
I've determined that the problem is definitely coming from the reflection call to the EntityCollection.Remove() method. For some reason calling it causes the entity's EntityState property to change to EntityState.Modified instead of EntityState.Deleted as it should.
Even if I try to remove from the collection by completely circumventing the DataGrid I get the exact same issue:
Entity selectedEntity = this.DataContext.GetType().GetProperty("SelectedEntity").GetValue(this.DataContext, null) as Entity;
object foo = selectedEntity.GetType().GetProperty("Foo").GetValue(selectedEntity, null);
foo.GetType().InvokeMember("Remove", BindingFlags.InvokeMethod, null, foo, new object[] { entity });
As a test, I tried modifying the UpdateFoo() domain service method to implement a delete and it worked successfully to delete the entity. This indicates that the RIA service call is working correctly, it's just calling the wrong method (Update instead of Delete.)
public void UpdateFoo(Foo currentFoo)
{
// Original update implementation
//if ((currentFoo.EntityState == EntityState.Detached))
// this.ObjectContext.AttachAsModified(currentFoo, this.ChangeSet.GetOriginal(currentFoo));
// Delete implementation substituted in
Foo foo = this.ChangeSet.GetOriginal(currentFoo);
if ((foo.EntityState == EntityState.Detached))
this.ObjectContext.Attach(foo);
this.ObjectContext.DeleteObject(foo);
}
I've been researching a similar issue.
I believe the issue is you are calling remove with a reference for an EntityCollections within the DomainContext as the root reference rather than using the DomainContext itself as the root.
So...
ParentEntityCollection.EntityCollectionForTEntity.Remove(TEntity);
Produces the EntityState.Modified instead of EntityState.Deleted
Try instead...
DomainContext.EntityCollectionForTEntity.Remove(TEntity);
I think this will produce the result you are seeking.
Hope this helps.
What is the "column" in the "FOREIGN KEY constraint" error? Is this a field in the grid row and collection that coorosponds to that column? Is it possible that the entity you are trying to remove is a column in the row rather than the row itself which is causing an update to the row (to null the column) rather than to delete the row?
I read your update and looks like you've determined that the problem is the reflection.
Have you tried to take the reflection out of the picture?
As in:
private void RemoveEntity(Entity entity)
{
// Use reflection to remove the item from the collection
Type sourceType = typeof(System.Windows.Ria.EntityCollection<>);
Type genericType = sourceType.MakeGenericType(entity.GetType());
// Make sure we have the right type
// and let the framework take care of the proper invoke routine
if (genericType.IsAssignableFrom(this._dataGrid.ItemsSource.GetType()))
((Object) this._dataGrid.ItemsSource).Remove(entity);
}
Yes, I know it's ugly, but some times...
Edited to add
I've updated the code to remove the is keyword.
Now about using the object to make the call to the Remove method, I believe it might work due the late binding of it.