views:

1314

answers:

2

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);
}
+1  A: 

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?

fupsduck
The error regarding the Foreign Key constraint happens because the Update routine is being called instead of the Delete routine so this error isn't really the root cause. As I pointed out in the update, substituting the Delete method code into the Update method successfully deletes the record so the issue isn't with the underlying SQL call, it's with how the Remove method is mis-labeling the EntityState
Nick Gotch
I get that - my point is this - is it possible that the entity that you think is a row is actually a column? That would explain why the update is being called insead of the delete. Because you would be trying to null out a column instead of deleting a row. By column here I mean a field in the row.
fupsduck
The column it's referring to is the ID column of the container type for each entity in the model. This column is stored within the entity and managed by the EF but inaccessible directly.Under non-reflection circumstances I've had no issues getting the CurrentItem of the DataGrid and calling Remove() from the parent collection. In this case, I'm still using CurrentItem to get the entity, only I can't directly cast it because the control assembly doesn't have access to this type.In answer to your last question: when I debug I see that the entity type I'm passing is the expected runtime type.
Nick Gotch
YES!! Finally solved this problem! Credit goes to this answer since it led me in the right direction. All I did was turn off "Enforce Foreign Key Constraint" on the ID column in the parent type's table in the database and suddenly the method succeeds. It still gets flagged as Modified instead of Removed (which I'd still like fixed) but at least it successfully deletes and modifies records now. Thanks!!
Nick Gotch
A: 

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.

Paulo Santos
I don't see any way to make this call from within the control assembly without using reflection. Your method won't work because the 'is' keyword requires an explicit type name be passed and also "Object" doesn't contain a "Remove" method. If you can think of another way to bypass reflection I'd more than love to try it
Nick Gotch
I've updated the code and added some clarification.
Paulo Santos
That solves the 'is' problem but late binding still requires a common interface to work. IEntityCollection would be perfect but it looks like MS scoped it as 'internal' so there's no access to it outside reflection
Nick Gotch