EntityDataSource: To wrap or not to wrap

Note:  Somehow the <TEntity> generic argument had disappeared from the extension method definition. I am fixing it today after several months. Sorry for the inconvenience!

Fresh from the forums today: A customer asks how to get the real entity object in the RowDataBound event of a GridView.

We made some complex design decisions during the development of the EntityDataSource, and I guess that will give us plenty of material for blogging :)

With the EntityDataSource, we faced the interesting problem of building a bridge between the existing ASP.NET databinding infrastructure and the world of EDM. One of the techniques we use to solve the problem is wrapping entities in smart databinding objects. As Colin explains it in his answer posted to the forums:

Why are you seeing a wrapper instead of the entity? Some of the unique features of the Entity Data Model prevent us from directly binding the entity. For instance, when inserting a Product I also need to insert a relationship to a Category. The wrapper adds the relationship to the entity, basically as a foreign key value.

The wrapper object implements the ICustomTypeDescriptor interface, which makes it work well with databinding, but when you try to get your original entity object, for instance form the arguments of the RowDataBound, you will get a wrapper object instead of the entity object you are expecting.

I don't want to make things much more complicated than they need to be, but I think the generic solution may be useful for some customers.

First of all, here are the rules for wrapping:

  • The wrapping mechanism only takes place if you initialize your EntityDataSource using EntitySetName.

  • When you instead set CommandText to a query that returns entities (i.e. "SELECT VALUE c FROM Northwind.Customers AS c", then you get normal entities.

  • When you instead set CommandText to a query that returns a projection of properties (i.e. "SELECT c.CustomerID, c.CustomerName FROM Northwind.Customers AS c"), then you get a DbDataRecord.

  • Finally, if you set the Select property to do a projection (i.e. "it.CustomerID, it.CustomerName", you get DbDataRecord regardless of how you start your query.

If you use the RowDataBound event very often in your code, then, I would suggest having around some code similar to this (thanks David for coming up with this code first):

static class EntityDataSourceExtensions

{

    public static TEntity GetItemObject<TEntity>(object dataItem)

        where TEntity : class

    {

        var entity = dataItem as TEntity;

        if (entity != null)

        {

            return entity;

        }

        var td = dataItem as ICustomTypeDescriptor;

        if (td != null)

        {

            return (TEntity)td.GetPropertyOwner(null);

        }

        return null;

    }

}

And this is the usage:

protected void GridView_RowDataBound(object sender, GridViewRowEventArgs e)

{

   var entity = EntityDataSourceExtensions.GetItemObject<Product>(e.Row.DataItem);

   //...

}

 

I hope this will help some.