Possible Bug - Import entities
Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce 2010
Forum Discription: For .NET 4.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=2596
Printed Date: 30-Aug-2025 at 10:53pm
Topic: Possible Bug - Import entities
Posted By: smi-mark
Subject: Possible Bug - Import entities
Date Posted: 30-Mar-2011 at 3:01pm
I'm using multiple EntityManagers in my app and I notice when I import an entity that has data changed, as long as it's not a navigation property, it updates fine so it's firing PropertyChanged. When I use a navigation property, it doesn't get fired properly.
It seems like the PropertyChanged for a navigation property is executed before it is changed.
If you need more info I can put a demo together.
Essentially:
This does not work:
parent.Repository.ImportEntities(Repository.ExportEntities(EntityState.AnyAddedModifiedOrDeleted));
This does work:
parent.Repository.ImportEntities(Repository.ExportEntities(EntityState.AnyAddedModifiedOrDeleted)); parent.Repository.ImportEntities(Repository.ExportEntities(EntityState.AnyAddedModifiedOrDeleted));
|
Replies:
Posted By: smi-mark
Date Posted: 31-Mar-2011 at 6:41am
I found another issue, if an entity is in the Added state and has a Int32 primary key, it will add a new copy of the entity with a new auto generated key, rather than overwriting the existing one.
I haven't tested it with a guid yet, but I imagine it will work fine.
|
Posted By: DenisK
Date Posted: 31-Mar-2011 at 12:28pm
Hi smi-mark;
Thanks for the info. I'm going to do a repro and file a bug report as necessary.
|
Posted By: DenisK
Date Posted: 31-Mar-2011 at 7:11pm
Hi smi-mark;
I was able to repro the issue with PropertyChanged, but unable to repro the one on your 2nd post about the Added state and Int32 primary key. Here's my repro.
public void ImportingEntityWithIntPrimaryKey() { var mgr1 = new NorthwindIBEntityManager(); var mgr2 = new NorthwindIBEntityManager();
//Fetch entities var anOrderFromMgr1 = mgr1.Orders.FirstOrNullEntity();
var aNewOrder2 = new Order(); mgr2.AddEntity(aNewOrder2); aNewOrder2.OrderID = anOrderFromMgr1.OrderID; aNewOrder2.ShipCountry = "Italy";
//Set Order to Added state anOrderFromMgr1.EntityAspect.SetAdded();
//Import to mgr2 mgr2.ImportEntities(new[] { anOrderFromMgr1 }, MergeStrategy.OverwriteChanges);
var orders = mgr1.FindEntities<Order>(EntityState.AllButDetached); var count = orders.Count(); }
|
The count is still 1 and aNewOrder2 was updated with all the values from anOrderFromMgr1. Please let me know what's different with your repro.
|
Posted By: smi-mark
Date Posted: 31-Mar-2011 at 7:52pm
Hi Denis,
This how you can reproduce it:
var mgr1 = new TestEntities(); var mgr2 = new TestEntities();
var customer = mgr1.CreateEntity<Customer>(); customer.Name = "Test";
mgr1.AddEntity(customer);
mgr2.ImportEntities(mgr1.FindEntities<Customer>(EntityState.AllButDetached), MergeStrategy.OverwriteChanges);
Console.WriteLine("Mgr1 count {0} Mgr2 count {1}", mgr1.FindEntities<Customer>(EntityState.AllButDetached).Count(), mgr2.FindEntities<Customer>(EntityState.AllButDetached).Count());
mgr1.ImportEntities(mgr2.FindEntities<Customer>(EntityState.AllButDetached), MergeStrategy.OverwriteChanges);
Console.WriteLine("Mgr1 count {0} Mgr2 count {1}", mgr1.FindEntities<Customer>(EntityState.AllButDetached).Count(), mgr2.FindEntities<Customer>(EntityState.AllButDetached).Count());
foreach (var c in mgr1.FindEntities<Customer>(EntityState.AnyAddedModifiedOrDeleted)) { Console.WriteLine("{0} {1}", c.Id, c.Name); }
You will see mgr1 now has the same entity twice, but with a different id
|
Posted By: DenisK
Date Posted: 01-Apr-2011 at 10:01am
Posted By: smi-mark
Date Posted: 01-Apr-2011 at 3:51pm
I have found a workaround for both problems.
For the first problem, as above, I just call it twice.
For the second problem, I do it like this:
public static void ImportFrom(this EntityManager manager, EntityManager fromManager) { var addedEntities = fromManager.FindEntities(EntityState.Added).OfType<Entity>().ToList(); fromManager.RemoveEntities(addedEntities); manager.ImportEntities(fromManager.FindEntities(EntityState.AllButDetached), MergeStrategy.OverwriteChanges); manager.AddEntities(addedEntities); }
|
Posted By: DenisK
Date Posted: 01-Apr-2011 at 4:38pm
Thanks again smi-mark. This is really helpful.
|
Posted By: DenisK
Date Posted: 19-Apr-2011 at 1:08pm
Hi smi-mark;
I've discussed the issue regarding importing entity with an Added state and temp id resulting in a duplicate entity with the lead developer and confirmed that this is the correct behavior.
The reason is that, when the entity is still in the Added state and has a temp id, EntityManager still considers it as a new entity and so when another same entity with the same temp id is also imported, EM will consider that entity a "new" entity and assigns it a new temp id.
The workaround by setting that entity to Modified, (calling AcceptChanges will work as well), is correct because we're telling the EntityManager that the entity is no longer new as we have "touched" it.
Please let me know if you still have further questions. I will post more updates on the PropertyChanged issue when I have some.
|
Posted By: smi-mark
Date Posted: 19-Apr-2011 at 1:33pm
Hi Denis,
I need to test this, but in a "modified" state, won't they try to update in the database rather than inserting them? These are new entities that have not yet been saved.
This is being used in a sandbox scenario such as this:
Customer - EM 1 Customer Address Edit - EM 2
When EM 2 "saves" it does not really save, it simply merges back into EM 1. EM 1 is responsible for saving of any changes.
I am doing it like this to simulate the old checkpointing ability in DevForce classic.
|
Posted By: DenisK
Date Posted: 20-Apr-2011 at 1:53pm
Hi smi-mark;
If the new "modified" entity still has a temp id, upon call to SaveChanges, EntityManager will do an id fixup and insert that new entity into the database. It will not update an existing entity in the database.
As I've mentioned before, you could also call AcceptChanges on CustomerAddress before EM2 saves/merges the entity back into EM1. I believe this will work better since you can call RejectChanges to roll back any Modified entity back to its Unchanged state.
|
Posted By: smi-mark
Date Posted: 20-Apr-2011 at 3:46pm
Hi Denis,
Perfect, if that is the case then that will work fine.
Thanks for looking into it.
|
Posted By: smi-mark
Date Posted: 22-Apr-2011 at 11:20am
Hi Denis,
Using AcceptChanges or SetModified by it self didn't work, but I do seem to have a solution:
I create a list of entities from EM2 with a state of Added, call AcceptChanges, import to EM1
For each Added Entity in EM2 i find the Entity using FindEntity EntityKey in EM1 and call SetAdded on it, this keeps the temporary Id and seems to work.
var addedEntities = fromManager.FindEntities(EntityState.Added)
.OfType<Entity>()
.ToList();
addedEntities.ForEach(e => e.EntityAspect.AcceptChanges());
toManager.ImportEntities(addedEntities, MergeStrategy.OverwriteChanges);
addedEntities.Select(entity => toManager.FindEntity(entity.EntityAspect.EntityKey) as Entity)
.ForEach(e => e.EntityAspect.SetAdded());
|
|
Posted By: DenisK
Date Posted: 25-Apr-2011 at 11:48am
Thanks for sharing your solution smi-mark.
Could you clarify what you meant by "Using AcceptChanges or SetModified by it self didn't work" ?
What do you mean by itself and what didn't work?
|
Posted By: DenisK
Date Posted: 11-May-2011 at 12:36pm
Hi smi-mark;
I just want to follow up again on this to make sure that there isn't any other bug.
The code snippet in your last post seems to be similar to what I'm doing in my testing but I'm still having a hard time understanding when you said ""Using AcceptChanges or SetModified by it self didn't work".
I would appreciate it if you can clarify that to me so I know all the bugs related to this that could be discovered are discovered.
Thanks!
|
Posted By: smi-mark
Date Posted: 11-May-2011 at 12:45pm
Hi Denis,
I have had to work on a couple other projects recently, I will get back to this and create an example showing what I mean.
Thanks,
Mark
|
Posted By: smi-mark
Date Posted: 16-May-2011 at 11:38am
Hi Derick,
I have prepared a sample. I have put the zip on our webserver:
http://71.164.192.3/ImportSL.zip
I have not used any frameworks with this sample, so it's not all best practices!
You will see two 'screens'
the top is the customer screen that lets you add/edit an address, when you click one of these buttons you will be able to update the address screen.
If you edit the address and save it, then save and edit again, you will see it starts duplicating the data. You will see it is not merging it in properly.
If you have any questions on it please let me know.
Thanks,
Mark
|
Posted By: DenisK
Date Posted: 16-May-2011 at 12:02pm
Thank you Mark. Appreciate the help. I'll get back in touch once I take a look at the sample.
|
Posted By: DenisK
Date Posted: 26-May-2011 at 3:12pm
Hi Mark;
Sorry it took so long to get back. Since you've already found a workaround, this has probably become a non issue for you.
Anyway, I just want to follow up what I found. It may or may not be useful.
The sample you sent does indicate that calling .SetModified() doesn't work. But the strange thing is that, when the EntityState is first Added, then SetModified(), the ImportEntities works. It will update the temp id from -100 to -101 and update the original CustomerAddress. However, when I modify the address again, ImportEntities stops working, i.e. it will keep the -101 CustomerAddress and create a new -102 CustomerAddress. What's even stranger is that I can't repro it outside the solution.
If I modify the code slightly to the following, using AcceptChanges(), then ImportEntities will keep the original temp id and not create duplicate CustomerAddress.
vm.EntityManager .FindEntities(EntityState.Added | EntityState.Modified) //.FindEntities(EntityState.Added) .OfType<Entity>() .ForEach(entity => entity.EntityAspect.AcceptChanges()); //.ForEach(entity => entity.EntityAspect.SetModified());
var entities = vm.EntityManager //.FindEntities(EntityState.AnyAddedModifiedOrDeleted) .FindEntities(EntityState.AllButDetached) .OfType<Entity>(); _primaryEntityManager.ImportEntities(entities, MergeStrategy.OverwriteChanges);
|
I'll continue to investigate this and provide updates when I've found something useful. But, again, as you've found a workaround, I think this issue can be considered closed.
|
Posted By: smi-mark
Date Posted: 26-May-2011 at 3:14pm
Hi Denis,
Thanks for the update. I'm not sure if my workaround works in all cases, but I will retest it again when I get chance.
The only problem with AcceptChanges(), is that now they are unmodified, so a call to SaveChanges() won't pick those entities up will it?
|
Posted By: DenisK
Date Posted: 26-May-2011 at 3:36pm
Yes, you're correct. SaveChanges will not pick those entities up when you use AcceptChanges. So in the broader scenario, SetModified is probably preferable. All the more reason for me to figure out why I can't repro the above issue outside the solution. It should be as simple as importing the same modified entity again but it doesn't generate duplicate in my test project when I do that.
|
Posted By: smi-mark
Date Posted: 26-May-2011 at 7:13pm
I can try and come up with a console based example that reproduces it. This was originally happening in another project and I made this to reproduce it.
|
Posted By: DenisK
Date Posted: 27-May-2011 at 12:02pm
I appreciate the help Mark. Thanks!
|
Posted By: smi-mark
Date Posted: 31-Oct-2011 at 3:17pm
Hey Denis,
I'm back on this project again and need some help. I have it "working" by doing this:
Calling EntityAspect.AcceptChanges() then EntityAspect.SetModified()
Then in the server save interceptor
protected override bool ExecuteSave()
{
var entities = this.EntityManager
.FindEntities(EntityState.Modified)
.OfType<Entity>()
.ToList();
foreach (var entity in entities)
{
var key = (int) entity.EntityAspect.EntityKey.Values[0];
if (key < 0)
entity.EntityAspect.SetAdded();
}
return base.ExecuteSave();
}
If I take out the accept/set modified code, when I merge the additions from one EM into another it duplicates.
Has there been any progress on resolving these issues?
|
Posted By: smi-mark
Date Posted: 01-Nov-2011 at 4:52pm
This is what I've had to do to make things work. It's not pretty, but it works.
When I load a sandboxed editor up I merge the data in like this:
OriginalManager.FindEntities<Entity>(EntityState.Added)
.ForEach(e =>
{
e.EntityAspect.AcceptChanges();
e.EntityAspect.SetModified();
});
var restoreStrategy = new RestoreStrategy(false, false, MergeStrategy.OverwriteChanges);
var data = repositoryBase.Manager.CacheStateManager.GetCacheState();
NewManager.CacheStateManager.RestoreCacheState(data, restoreStrategy);
var entities = Manager.FindEntities<Entity>(EntityState.Modified)
.Where(e => (int)e.EntityAspect.EntityKey.Values[0] < 0).ToList();
entities.ForEach(e => e.EntityAspect.SetAdded());
To stop the new manager from assigning a new Id, I have to call AcceptChanges and then SetModified.
Since I am using integer primary keys, I can find the true "Added" entities by finding the ones with a negative key. I then set these back to Added in the new manager. Also, to get around duplicating the same negative key, I have had to alter the default ID generator. It now uses a static _nextId variable so that all instances use the same id counter.
This seems to work, the only issue is on saving where even though they are set to added, they get duplicated in the EM after saving, the original Added entities, and the new saved unchanged entities.
I have had to do this to get around it:
private void OnSaveSuccess()
{
var addedEntities = Manager.FindEntities(EntityState.Added)
.OfType<Entity>()
.ToList();
for (var i = addedEntities.Count-1; i >= 0; i--)
{
addedEntities.EntityAspect.Delete();
}
}
If there is a better/cleaner way of doing this, I'm all ears.
|
Posted By: DenisK
Date Posted: 02-Nov-2011 at 1:21pm
Hi smi-mark;
It's been a while since we last discussed on this issue. I've read the whole posts again so let me try to summarize the issue(s).
I think we're dealing with 2 different issues here.
1. Best practices of using sandbox editor with 2 EntityManagers via ImportEntities.
As I recall, it is by design that if you have a new "Added" entity with a negative temp id and you're importing it to a second EM and importing it back to the original EM, it will create a duplicate entity because an "Added" entity with a negative temp id is always considered a new entity. The workaround is to set this entity to a "Modified" state before importing it back to the original EM.
The workaround that you show above seems pretty reasonable except for one thing. Why did you have to set entities.ForEach(e => e.EntityAspect.SetAdded()); after importing it back to the original EM? If you leave it as "Modified" it will still insert a new entity (after id fixup) to the database.
For best practices suggestions, you might want to read this blog post by Ward on using DevForce with Sandbox editors, if you haven't. http://neverindoubtnet.blogspot.com/2010/12/sandbox-editors-with-clientui-contacts.html - http://neverindoubtnet.blogspot.com/2010/12/sandbox-editors-with-clientui-contacts.html
2. The new "Added" entities get duplicated in the EM after saving.
I think this is the bug that I tried to track down last we talked. You had sent me an SL solution that showed this bug but I couldn't repro it outside that solution. Would you be able to provide a console repro for this?
|
Posted By: smi-mark
Date Posted: 02-Nov-2011 at 2:43pm
If I don't set it back to Added, it causes an error, at least under fake context, saying it can't find the entity.
"Unable to locate entity within the 'Fake' entity server: DealerAddress: -101"
|
Posted By: DenisK
Date Posted: 03-Nov-2011 at 3:49pm
This sounds like a bug. Does this happen upon SaveChanges? Also, what version are you using?
|
Posted By: smi-mark
Date Posted: 03-Nov-2011 at 4:07pm
Yes, this happens during SaveChanges and using the latest 6.1.3.1
|
Posted By: DenisK
Date Posted: 03-Nov-2011 at 6:38pm
Ok, this is not a bug. And my statement
"If you leave it as "Modified" it will still insert a new entity (after id fixup) to the database."
is completely false. I had mentioned this as well in my earlier post. I was pretty sure I tested this which is why I posted it in the first place. But I guess between the importing and changing of EntityState, I must have confused myself. My apologies.
On the other hand, I was able to duplicate issue #2 where after calling save, the original "Added" entity with the negative temp id still exists. I've filed a bug report. Please let me know if you require a patch. I apologize again if this takes longer than anticipated.
|
Posted By: smi-mark
Date Posted: 03-Nov-2011 at 7:26pm
I don't need a patch right away, my workaround seems to do the trick for now. I simply delete any modified entities after a save.
Thanks for looking into it, much appreciated. Will this and the the deletion merging issue be fixed in 6.1.4?
|
Posted By: DenisK
Date Posted: 03-Nov-2011 at 7:29pm
Sounds good.
Both issues should be fixed in 6.1.4. They're currently marked as priority.
|
Posted By: smi-dan
Date Posted: 19-Jan-2012 at 1:42pm
Following on from this thread - the code we ended up using for our Merge method is as follows;
sourceRepository.Manager.FindEntities<Entity>(EntityState.Added) .ForEach(e => { e.EntityAspect.AcceptChanges(); e.EntityAspect.SetModified(); });
var restoreStrategy = new RestoreStrategy(false, false, MergeStrategy.OverwriteChanges); var data = sourceRepository.Manager.CacheStateManager.GetCacheState(); Manager.CacheStateManager.RestoreCacheState(data, restoreStrategy); Manager.FindEntities<Entity>(EntityState.Modified) .Where(e => (int)e.EntityAspect.EntityKey.Values[0] < 0) .ForEach(e => e.EntityAspect.EntityState = EntityState.Added);
We have a Note entity which has a complex type Audit. After GetCacheState() the EntityCacheState preserves the state of Audit however after the call to RestoreCacheState() Audit's properties are null.
Is this a bug with RestoreCacheState or intended behavior? Is there a way to preserve the state of Audit?
|
Posted By: DenisK
Date Posted: 19-Jan-2012 at 3:28pm
Hi smi-dan,
No, that's not the intended behavior. We have tests here that cover this scenario and they all pass. If you can send me your edmx containing only the Note entity and its Audit complex type, I'll try to repro it here. Please let me know as well the version of DevForce you're using.
Thank you.
|
Posted By: smi-dan
Date Posted: 19-Jan-2012 at 9:12pm
Hi DenisK,
I've gone ahead and created a sample project that demonstrates the issue.
uploads/1059/RestoreCacheStateExample.zip - uploads/1059/RestoreCacheStateExample.zip
You'll note that the Note.Text makes it through the RestoreCacheState but none of the ComplexType properties make it through.
I am using DevForce 6.1.4.0
|
Posted By: DenisK
Date Posted: 20-Jan-2012 at 2:19pm
Thanks for the sample smi-dan. That's why I couldn't repro it. This issue is fixed in 6.1.5.
|
|