i'm currently using GXT 3 to display elements in a Tree.
These elements are retrieved from database and identified in the Tree by their id (by that, I mean that the id is the ModelKeyProvider of my store).
I also made it possible for users to create objects locally in the tree with the following code:
private Tree<EntityDAO, String> tree;
private TreeStore<EntityDAO> store;
int count = 1;
// instanciation and irrelevant stuff
...
EntityDAO sel = tree.getSelectionModel().getSelectedItem();
EntityDAO child = new EntityDAO();
child.setId((long) count);
store.add(store.getParent(sel), child);
count++;
tree.setExpanded(sel, true);
tree.getSelectionModel().select(child, false);
As you can see, i set a temporary id (count) to my local object.
The issue occurs when I save my object in database. A permanent id is then set to my EntityDAO but when i try to set this id to my local object to sync it with the database, it doesn't work.
I've tried to modify the child id directly
child.setId(result);
tree.update(child);
I've tried to add a copy of my object with the proper id, and then to remove my object from the tree
EntityDAO newPR = child;
newPR.setId(result);
store.add(store.getParent(child), newPR);
store.remove(child);
But the display is never updated. Any clue?
Let's discuss about the first way you tried, the update method:
child.setId(result);
tree.update(child);
From the update method API state this :
Replaces the item that matches the key of the given item, and fires a
StoreUpdateEvent to indicate that this change has occurred. Any
changes to the previous model via it's record instance will be lost
and the record will be removed. This will not cause the sort or filter
to be re-applied to the object. Overrides: update(...) in Store
Parameters: item the new item to take its place in the Store.
So basically, the update method will replace the item inside the store that have the same key with your parameter. Your data have a new key that doesn't exist inside the store, that's why it doesn't effected anything to your tree display.
Second, let's discuss the create a copy of your object and set it with the proper id:
EntityDAO newPR = child;
newPR.setId(result);
store.add(store.getParent(child), newPR);
store.remove(child);
This way actually will work, but you only have one small problem. The first line of your code actually just give you a variable that have a reference to your old object (the child object), so whenever you remove the child, the newPR also removed. You should really create a new object using the constructor, here how I think you should do it:
EntityDAO newPR = new EntityDAO();
newPR.setId(result);
newPR.setOtherProperty(child.getOtherProperty());
// just copy all property of child to newPR
store.add(store.getParent(child), newPR);
store.remove(child);
Hope this can help you.
Related
I want to clone an object obtained from the db context using EF. Such object contains a collection that in turn contains another one.
For the copy or clone of the object, I have a method that creates a new instance, copies all the fields and finally marks the original one as EntityState.Unchaged.
Availability newAvailability = new Availability();
newAvailability = availabilityRepository.Copy(sourceAvailability, user);
newAvailability.RemoveTag(img, tagToRemove);
In the example, I have an availability which contains a set of images where each of them can have tags. What I want to do is to create a clone of the Availability object along with the images, then associate the tags to each image by using a join table.
In a next step, I want to remove a tag from one of the copied images, from the copied Availability.
The result is that the tag is being removed on both arrays.
What is weird, is that RemoveTag extension method doesn't use EF at all. It looks like this:
var filterExpression = new Func<Image, bool>(y => y.Url == img.Url));
var image = availability.Images.FirstOrDefault(filterExpression);
image.Tags.Remove(tag);
return availability;
Now, if I have an Availability like this (assume this the result of joining tables, an availability can have many images and an image can have many tags):
A = 1 ; Image = 1; Tag = 6
and then when I create its copy it looks like:
A = 2 ; Image = 2; Tag = 6
the result should be that the Tag 6 in Image 2 from the Availability gets removed and the source Availability is kept unchanged. But that's not what is happening.
Note: the Copy method is doing at some point
this.context.Entry(availability).State = EntityState.Unchanged;
What could I be doing wrong? How should I avoid EF from removing this child object from the source parent?
Thanks in advance
EDIT:
Found the solution, although I'm not entirely sure what changes internally on doing this. I wrote
imgCopy.Tags = img.Tags;
but when I changed to
img.Tags.ToList().ForEach((_) =>
{
imgCopy.Tags.Add(_);
});
the problem was solved.
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()
I have CellTable with MultipleSelectionModel attached to it. After some modification of data the table has to be refreshed and new data has to be reloaded from server.
However I need to update checkboxes state for newly loaded data. So I am able to query selection boxes with selectionModel.getSelectedSet() - but now I need to find these objects in table and "check" them.
Because content of objects changes and since they are used as keys in Maps internally in GWT components- I was forced to write "wrapper" over these objects which uses only ID in equals/hashCode.
So basically I save selectedSet before firing event, then iterate over it and invoke setSelected method:
Set<T> selectedSet = selectionModel.getSelectedSet();
RangeChangeEvent.fire(table,...)
if (selectedSet != null)
for (T obj : selectedSet) {
selectionModel.setSelected(obj,true);
}
}
Is there any better approach?
This is what the ProvidesKey is for: create a ProvidesKey instance that returns the ID of your objects to be used as their keys, and pass that instance to your selection model when you build it:
MultiSelectionModel<X> selectionModel = new MultiSelectionModel<X>(new ProvidesKey<X>() {
#Override
public Object getKey(X item) {
return item.getId();
}
});
That way, you shouldn't have anything special to do with your selection model after retrieving updated data: push it to your table and it'll ask the selection model for each object whether it's selected or not, and the selection model will be able to answer based solely on the object's ID, therefore reusing the same selected set as before.
This is my first post here, so I hope everything is fine.
Here is my problem:
I have a table in my database called UserTypes. It has:
ID;
IsPrivate;
Parent_ID;
The relevant ones are the first and the third one.
I have another table called UserTypes_T which has information for the different types, that is language specific. The fields are:
Language_ID;
UserType_ID;
Name;
What I'm trying to achieve is load the entire hierarchy from the UserTypes table and show it in a TreeView (this is not relevant for now). Then, by selecting some of the user types I can edit them in separate edit box (the name) and a combo box (the parent).
Everything works fine until I try to persist the changes in the database. EF has generated for me two entity classes for those tables:
The class for the user types has:
ID;
IsPrivate;
Parent_ID;
A navigational property for the self-reference (0..1);
A navigational property for the child elements;
Another navigational property for the UserTypes_T table (1..*);
The class for the translated information has:
UserType_ID;
Language_ID;
Name;
A navigational property to the UserTypes table (*..1);
A navigational property to the Languages table (*..1);
I get the data I need using:
return context.UserTypes.Include("UserTypes_T").Where(ut => ut.IsPrivate==false).ToList();
in my WCF Web service. I can add new user types with no problems, but when I try to update the old ones, some strange things happen.
If I update a root element (Parent_ID==null) everything works!
If I update an element where Parent_ID!=null I get the following error:
AcceptChanges cannot continue because the object’s key values conflict with another object in the ObjectStateManager.
I searched all over the internet and read the blog post from Diego B Vega (and many more) but my problem is different. When I change a parent user type, I actually change the Parent_ID property, not the navigational property. I always try to work with the IDs, not the generated navigational properties in order to avoid problems.
I did a little research, tried to see what is the object graph that I get and saw that there were lots of duplicate entities:
The root element had a list of its child elements. Each child element had a back reference to the root or to its parent and so on. You can imagine. As I wasn't using those navigational properties, because I used the IDs to get/set the data I needed, I deleted them from the model. To be specific I deleted points 4 and 5 from the UserTypes entity class. Then I had an object graph with each element only once. I tried a new update but I had the same problem:
The root element was updated fine, but the elements, that had some parents, threw the same exception.
I saw that I had a navigational property in the UserTypes_T entity class, pointing to a user type, so I deleted it too. Then this error disappeared. All the items in the object graph were unique. But the problem remained - I could update my root element with no problems, but when trying to update the children (with no exclusions) I got a null reference exception in the generated Model.Context.Extensions class:
if (!context.ObjectStateManager.TryGetObjectStateEntry(entityInSet.Item2, out entry))
{
context.AddObject(entityInSet.Item1, entityInSet.Item2);//here!
}
I tried to update only the name (which is in UserTypes_T) but the error is the same.
I'm out of ideas and I've been trying to solve this problem for 8 hours now, so I'll appreciate if someone gives me ideas or share their experience.
PS:
The only way I succeeded updating a child object was using the following code to retrieve the data:
var userTypes = argoContext.UserTypes.Include("UserTypes_T").Where(ut => ut.IsPrivate==false).ToList();
foreach (UserType ut in userTypes)
{
ut.UserType1 = null;
ut.UserTypes1 = null;
}
return userTypes;
where UserType1 is the navigational property, pointing to the parent user type and UserTypes1 is the navigational property, holding a list of the child element. The problem here was that EF "fixups" the objects and changes the Parent_ID to null. If I set it back again, EF sets the UserTypes1, too... Maybe there is a way to stop this behavior?
OK everybody, I just found what the problem was and I'm posting the answer if anybody else encounters the same issue.
The problem was that I was making some validation on the server in order to see if there isn't a circular reference between the user types. So, my method on the server looked something like:
using (MyEntities context = new MyEntities())
{
string errMsg = MyValidator.ValidateSomething(context.UserTypes,...);
if (!string.IsNullOrEmpty(errMsg)) throw new FaultException(errMsg);
//some other code here...
context.UserTypes.ApplyChanges(_userType);//_userType is the one that is updated
context.UserTypes.SaveChanges();
}
The problem is that when making the validation, the context is filled and when trying to save the changes, there are objects with the same key values.
The solution is simple - to use different context for validating things on the server:
using (MyEntities validationContext = new MyEntities())
{
//validation goes here...
}
using (MyEntities context = new MyEntities())
{
//saving changes and other processing...
}
Another one can be:
using (MyEntities context = new MyEntities())
{
using (MyEntities validationContext = new MyEntities())
{
//validation
}
//saving changes and other processing...
}
That's it! I hope it can be useful to somebody!
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.