How to get the enity object i RowDataBound of a EntitydataSource bound gridview
-
Tuesday, May 13, 2008 11:16 AMHi,
It's exciting with the new EntityDataSource!
But for some reason I can't get to the entity object in the RowDataBound event, when the gridview is bound to the EntityDataSource. This was easy with the ObjectDataSouce, with a simple cast. Now however, the e.row.DataItem is of the type: EntityDataSourceWrapper which isn't a public member.
Is there another way?
All Replies
-
Tuesday, May 13, 2008 2:16 PM
The "wrapper" implements ICustomTypeDescriptor, which allows you to get at the underlying entity:
Code Snippetprotected void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
ICustomTypeDescriptor descriptor = e.Row.DataItem as ICustomTypeDescriptor;
if (null != descriptor)
{
var prop = descriptor.GetProperties().Cast<PropertyDescriptor>().First();
Product owner = (Product)descriptor.GetPropertyOwner(prop);
}
}
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. In this case, you'll see:
Code Snippet<asp:BoundField DataField="Categories.cid" HeaderText="Categories.cid"
SortExpression="Categories.cid" />
Thanks,
-Colin
-
Tuesday, May 13, 2008 3:28 PMModerator
I don't want to make things much more complicated here, but I think the generic solution may be useful for some customers. First of all, here are the rules for wrapping:
-
First of all, the wrapping mechanism that Colin refers to 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, you get t 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:
Code Snippetstatic 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:
Code Snippetprotected void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
var entity = EntityDataSourceExtensions.GetItemObject<Product>(e.Row.DataItem);
//...
}
-
-
Tuesday, May 13, 2008 4:23 PMThanks guys!
This is exactly what I was hoping for. I was indeed setting the EntitySetName, since I need to be able to update/delete.
Correct me if I'm wrong, but I believe using CommandText and Select gives me a read only result.
I probably won't use the rowdatabound event very often, but I do like the Extension.
Looking forward to dive deeper into the entity framework universe
-
Tuesday, May 13, 2008 5:24 PMModerator
You are correct, read-write databinding is only enabled if you start with an EnittySetName and not Select.
Thanks,
-Diego
-
Wednesday, May 21, 2008 6:24 PM
I am having a similar issue in that I am trying to access an associated entity acquired through the basic LinqDataSource in the Gridview's RowDataBound event.
It is a one to many association and for some reason does not return the associated entity set unless I specifically use the SELECT declaration in the LinqDataSource. (I must specifically select the association, or no association results returned if I use *)
So, I am left with trying to use the data in the RowDataBound event with e.row.dataitem.
Of course, it now creates a DynamicClass and your wrapper looks like it would do the job. Otherwise, I get a Cast exception of "DynamicClass1 cannot be cast...blah blahh".
I tried your code, but translated to VB.NET and getting an intellisense error about the TryCast from DataItem to TEntity because TEntity has no class constraint.
Could you look at my code below and see if I did something wrong or missed a point somewhere? TIA (trouble marked bold)
Code SnippetImports
System.LinqImports
System.Data.LinqImports
System.Data.Linq.MappingImports
Microsoft.VisualBasicImports
System.ComponentModelNamespace
LinqHelpers
Public NotInheritable Class EntityDataSourceExtensions
Private Sub New() End Sub
Public Shared Function GetItemObject(Of TEntity)(ByVal dataItem As Object) As TEntity
Dim entity As TEntity = TryCast(dataItem, TEntity) If entity IsNot Nothing Then
Return entity End If Dim td = TryCast(dataItem, ICustomTypeDescriptor) If td IsNot Nothing Then
Return DirectCast(td.GetPropertyOwner(Nothing), TEntity) End If Return Nothing End Function
End ClassEnd
NamespaceThanks again,
Ken Krickbaum
-
Wednesday, May 21, 2008 6:37 PMModerator
I wasn't aware of the fact that LinqDataSource also wraps instances. This is interesting. Thanks!
You are right that your definition is missing a generic argument constraint (that is the "TEntity:class" in the C# version). Here is how you express that in VB.
Code SnippetImports System.ComponentModel
Namespace LinqHelpers
Public NotInheritable Class EntityDataSourceExtensions
Public Shared Function GetItemObject(Of TEntity As Class)(ByVal dataItem As Object) As TEntity
Dim entity As TEntity = TryCast(dataItem, TEntity)
If entity IsNot Nothing Then
Return entity
End If
Dim td = TryCast(dataItem, ICustomTypeDescriptor)
If td IsNot Nothing Then
Return DirectCast(td.GetPropertyOwner(Nothing), TEntity)
End If
Return Nothing
End Function
End Class
End Namespace
Hope this helps,
Diego -
Wednesday, May 21, 2008 8:37 PM
Thanks Diego.
It turns out that this wrapper fails to Cast the DataItem to the respective Entity class. (returns nothing)
From SQL Books:
If you retrieve a subset of properties from the data object, the LinqDataSource control dynamically creates a class that contains only the properties that you specify in the Select property. A class is also created dynamically if you calculate properties that are not properties in the data object. In these cases, the object returned from the query is not an instance of the class that is specified in the TableName property.
Then it occurred to me, the stupe that I am, that the LinqDS must create a dynamic class because it is of an entirely different structure than that of the Table Entity.
So, I guess my question, whether to be asked here or not, is how do I Type the e.row.dataitem to the Dynamically generated LinqDS class entity type? Still looking.
Then the side question is WHY the LinqDataSource will not return the "associated" 1 to many data (setup with association in the dbml) when using * (no Select Property specified in the LinqDataSource). 1 to 1 returns the associated class\data just fine. It is only the 1 to many it will not return.
Thanks again,
Ken Krickbaum
-
Wednesday, September 03, 2008 7:18 PM
Diego Vega - MSFT wrote: Code Snippetstatic class EntityDataSourceExtensions
{
public static TEntity GetItemObject(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:
Code Snippetprotected void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
var entity = EntityDataSourceExtensions.GetItemObject<Product>(e.Row.DataItem);
//...
}
How do you get TEntity to load? What namespace to you need to load in (sorry if this is an obvious question, with an obvious answer).
Also, setting a requirement to make this a wrapped class that you need to "extract" out to get the entity details makes extending Dynamic Data a bit more difficult because the columns there are dynamically loaded not statically specified, so calling it becomes a nasty mix of var entity = EntityDataSourceExtensions.GetItemObject<typeof(...)>(e.Row.DataItem);.
Woudn't it be at least better if you could cast e.Row.DataItem to an interface of some form that would have both the wrapper details and the entity object? Maybe I do not understand the architecture enough to comment on this.
-
Thursday, September 04, 2008 10:13 AMModerator
I get the wrapper is a pain point for your scenario, but I am not sure I understand when you refer to properties that are dynamically loaded. Could you please expand a little bit more on how the code you have to write to handle this looks like?
Also, I am not sure I understand your question about the namespace. An Entity Framework model and the corresponding entity classes need to be available in order for the EntityDataSource to work. Those usually specify the namespaces.
Thanks,
Diego -
Friday, September 05, 2008 8:53 PM
A typical asp.net scenario with a databound item would look like this :
Code Snippetprotected void DataGrid1_OnDataBinding(object sender, DataGridItemEventArgs e)
{
object item = e.Item.DataItem;
}
Then you could easily take that data and insert it back into the ((DataGridItem)sender).Cells[0].Text (or .Controls method) in an object that would replace the cell output.
In the asp.net dynamic data templates the DataGrid is setup with AutoGenerateRows="True" and the entity object that is bound to the DataGrid object is bound dynamically from the url http://localhost/Artists/List.aspx for instance. So when you unwrap the DataSourceWrapper class you find yourself in a situation where you have to know the object type in order to pass it into TEntity in the above function listed.
That is not really too much of a major complaint of mine, just a minor road block really. The bigger issue is that when I attempt to add TEntity to my custom static class it does not recognize what TEntity is. I know this is largely because I am doing something wrong, but I really do not know what I am doing wrong
Code Snippetstatic
class EntityDataSourceExtensions{
public static TEntity GetItemObject(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;
}
}
In this code, Visual studio underlines TEntity and where...
The namespaces I have are :
using
System;using
System.Data;using
System.Configuration;using
System.Collections;using
System.ComponentModel.Design;using
System.Linq;using
System.Web;using
System.Web.Security;using
System.Web.UI;using
System.Web.UI.WebControls;using
System.Web.UI.WebControls.WebParts;using
System.Web.UI.HtmlControls;using
System.Xml.Linq;using
System.Web.DynamicData;Thank you for your help with all of this Diego, I am still attempting to get caught up with everything that Entity Framework can handle.
-
Friday, September 05, 2008 10:20 PMModerator
Oops, actually the software that runs the forums sometimes swallows angle bracket tags.
There should be an 'opening angle bracket' TEntity 'closing angle bracket' just before the name of the method. I have just corrected it in the original post.
Also, if you are using our code-generated classes, they all derive from the same base class. You could probably use:
Code SnippetGetObjectItem<System.Data.Objects.DataClasses.EntityObject>(e.Row.DataItem)
Thanks for the heads up and sorry for the inconvenience,
Diego

