Using Reflection to Remove Entity from RIA Services EntityCollection? - entity-framework

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.

Related

Why does Remove() not delete entities from the database?

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);

Entity framework reload entity from context

I have the following scenario:
I am using EF with Repository pattern and Unit Of Work from here
Then I extended my partial class and have created a Finder class which have GetAll and other methods.
You can this see below:
Below you can see that I am using a unit of work class with repositories of all classes to get the instance from the generic repository:
protected Repository<Category> _CategoryRepository;
public Repository<Category> CategoryRepository
{
get { return _CategoryRepository ?? (_CategoryRepository = new Repository<Category>(context)); }
}
So in this way I had different repositories and when getting an entity from db and updating it from a different context caused problems. So I used this method to use for context lifetime management. And it resolved that issue.
Now the problem I am facing is in the following code:
var cat = Instance.Category.GetSingle(c => c.CategoryID == 7);
var orignal = cat.CategoryName;
var expected = cat.CategoryName + " Test Catg Update";
cat.CategoryName = expected;
cat.Update(); //This doesn't actually update due to a validation in place (which is correct)
cat = Instance.Category.GetSingle(c => c.CategoryID == 7);
Assert.AreEqual(cat.CategoryName, expected);
When I use Update and I have some validators in it, and the Update fails (for example due to size of the string exceeds 15 characters). When I try to call GetSingle (2nd last line of above code) again, it brings me the same old record which is [cat.CategoryName + " Test Catg Update"]. Is this a normal way? If not how can this be fixed so I can reload the object from database.
Let me know if you need any other code or reference.
in your Update() method, if the validation fails, simply set the state of the entity to EntityState.Unchanged. Under the covers, changing the state of an entity from Modified to Unchanged first sets the values of all properties to the original values that were read from the database when it was queried, and then marks the entity as Unchanged. This will also reject changes to FK relationships since the original value of the FK will be restored.
You may be able to take advantage of the ObjectContext.Refresh() method to handle this as well. However, this is on the ObjectContext, so you would need a method to handle it in your UoW. The refresh method would be something like context.Refresh(RefreshMode.ServerWins, entity);

ObjectContext.Attach generates incorrect entity key

I'm using .NET 4.0 with EF POCO's. I have a conceptual model from which a part is shown in the image. Everything works fine, except one thing which I have been almost busy with for 2 days and still don't get it to work!
In my conceptual model, a WebShop can have one or more related WebShopCategory instances. Each WebShopCategory associates a WebShop with a Category and enables me to specify additional properties for a WebShop/Category combination.
See image here: http://postimage.org/image/djrn4xh37/
When creating a new WebShop instance of course I need to save it, which I do in an Update method of a WebShopRepository which uses the EF ObjectContext as shown in the code below:
using (MyEntities entities = new MyEntities()) {
if (webShop.ID == 0) {
entities.Countries.Attach(webShop.Country);
foreach (PaymentMethod paymentMethod in webShop.PaymentMethods) {
entities.PaymentMethods.Attach(paymentMethod);
}
foreach (QualityMark qualityMark in webShop.QualityMarks) {
entities.QualityMarks.Attach(qualityMark);
}
foreach (WebShopCategory category in webShop.Categories) {
entities.Categories.Attach(category.Category);
}
entities.WebShops.AddObject(webShop);
}
entities.SaveChanges();
}
One thing to note is that all related items already exist in the data source. As they are not yet in the ObjectContext, I've attached them before adding the created WebShop to the ObjectContext. This seems to work as expected: all entities that have been attached have an EntityState.Unchaged state which is correct. The added WebShop is in the Added state which also is correct.
However, it can happen that when I attach the Category instances (category.Category) EF gives an exception stating that there is already an item in the object context with the same key. I have found this ObjectStateEntry instance and what I noticed was that the EntityKey only consists of the EntitySet name, and not with the ID also in the key. Other EntityKeys that have been added to the context are all in the form of EntitySetName;ID=value
To me it seems that the entity key is somehow not being determined correctly by the EF, because at the point where it goes wrong I've checked and made sure that the added category is indeed valid: it is a category already known in the sytem and has its primary key (ID) value set to 1 (which is the first added category in the system).
I've tried many ways to fix this but cannot get this to work. Any help would be greatly appreciated!
Thank you all very much in advance!

Problem with EF STE and Self-Referencing tables

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!

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key

I have following code to add or update the Entity object. finding the object by primary key, based on the response I am adding or updating the object.
Adding record works, but during update its giving this error message "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key"
In my MSSQL database I have only one record.
var v = db.Envelopes.Find(model.ReportDate, model.Service);
if (v == null)
{
db.Envelopes.Add(model);
db.SaveChanges();
ViewBag.status = "Record Add successfully";
ModelState.Clear();
}
else
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
}
How can I fix this error message?
As mentioned by #anon you can't attach model once you loaded the entity with the same key. The changes must be applied to attached entity. Instead of this:
db.Entry(model).State = EntityState.Modified;
use this:
db.Entry(v).CurrentValues.SetValues(model);
If an earlier query read the entity to be updated and that's why you're getting the error, you can change that query to AsNoTracking. See the AsNoTracking example in:
http://www.asp.net/entity-framework/tutorials/advanced-entity-framework-scenarios-for-an-mvc-web-application
I assume you are saying that your error occurs here:
db.Entry(model).State = EntityState.Modified;
Once you execute Find(), your Envelope is already being tracked by your context. This means that if you need to change a property, just change it on v, and then call SaveChanges(). Don't worry about setting the state to Modified.
If you set your context to AsNoTracking() this will stop aspmvc tracking the changes to the entity in memory (which is what you want anyway on the web). Don't forget the using statement.
using System.Data.Entity;
db.Envelopes.AsNoTracking().Find(model.ReportDate, model.Service);
I got this from this forum post ->
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application
I'm using this because I have already created a new instance, and populated the properties I need to update.
var key=this.CreateEntityKey("Envelopes",model);
ObjectStateEntry ose;
if(this.ObjectStateManager.TryGetObjectStateEntry(key, out ose)){
var entity=(Page)ose.Entity;
Envelopes.Detach(entity);
}
this.Envelopes.Attach(model);
And another approach to solve the issue is detaching tracked entity and re-attaching the modified one. See my solution here.