In my application I need to bind RadGridViews to tabular data exposed via web services where the columns may not be known at compile time. In ASP.NET the solution was to return a DataSet and bind to a DataTable, and this worked very well. In Silverlight, there is no DataSet (DataSets are proxied as instances of ArrayOfXElement which contains a couple of XML fields), so to compensate, I parse the ArrayOfXElement object and extract the columns and type data from the XML contained therein. I take this data and build a dynamic type using System.Emit namespace. The result is a type which contains a public get/set property corresponding to each column in the source datatable, with a matching type. I then populate a collection of these types using the xml data. I end up with IEnumerable<object> which I then set to the RadGridView.ItemSource, and everything works fine. The problem is, the GroupDescriptor.Version field cannot seem to be used to point to one of these dynamic properties, whereas the GridViewDataColumn.DataMemberPath has no issues in this regard. When the name of a dynamic property is specified in the GroupDescriptor.Version field, it results in an exception being thrown when ItemSource is set. See below error (note that Version is the name of the dynamic property I'm trying to group on). The source code used to generate the dynamic type is included below for interest, although the code works perfectly. The key question is why does binding the actual data to the grid columns work fine with dynamic types, but grouping does not? They should both use reflection, right?
Thanks
Greg
Webpage error details
User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.0.30618; .NET CLR 3.5.30729; MS-RTC LM 8; MS-RTC EA 2)
Timestamp: Thu, 30 Jul 2009 17:31:20 UTC
Message: Unhandled Error in Silverlight 2 Application Invalid property or field - 'Version' for type: Object at Telerik.Windows.Data.Expressions.MemberAccessTokenExtensions.CreateMemberAccessExpression(IMemberAccessToken token, Expression instance)
at Telerik.Windows.Data.Expressions.ExpressionFactory.MakeMemberAccess(Expression instance, String memberName)
at Telerik.Windows.Data.Expressions.ExpressionFactory.MakeMemberAccess(Expression instance, String memberName, Boolean liftMemberAccessToNull)
at Telerik.Windows.Data.Expressions.PropertyAccessExpressionBuilder.CreateMemberAccessExpression()
at Telerik.Windows.Data.Expressions.MemberAccessExpressionBuilderBase.CreateLambdaExpression()
at Telerik.Windows.Data.Expressions.GroupDescriptorExpressionBuilder.CreateGroupByExpression()
at Telerik.Windows.Data.Expressions.GroupDescriptorExpressionBuilderBase.CreateQuery()
at Telerik.Windows.Data.Expressions.GroupDescriptorCollectionExpressionBuilder.CreateQuery()
at Telerik.Windows.Data.QueryableExtensions.GroupBy(IQueryable source, GroupDescriptorCollection groupDescriptors)
at Telerik.Windows.Data.QueryableCollectionView.CreateView()
at Telerik.Windows.Data.QueryableCollectionView.get_QueryableView()
at Telerik.Windows.Data.QueryableCollectionView.CreateInternalList()
at Telerik.Windows.Data.QueryableCollectionView.get_InternalList()
at Telerik.Windows.Data.QueryableCollectionView.GetEnumerator()
at Telerik.Windows.Data.RecordFactory.<CreateRecordsForGroupRecord>d__0.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Telerik.Windows.Controls.GridView.GridViewDataControl.PopulateRecords()
at Telerik.Windows.Controls.GridView.GridViewDataControl.LoadData()
at Telerik.Windows.Controls.GridView.GridViewDataControl.<>c__DisplayClass3c.<Bind>b__3b()
at Telerik.Windows.Controls.CursorManager.PerformTimeConsumingOperation(FrameworkElement frameworkElement, Action action)
at Telerik.Windows.Controls.GridView.GridViewDataControl.Bind(Object newValue)
at Telerik.Windows.Controls.GridView.GridViewDataControl.OnItemsSourceChanged(Object oldValue, Object newValue)
at Telerik.Windows.Controls.DataControl.OnItemsSourcePropertyChanged(DependencyObject origin, DependencyPropertyChangedEventArgs args)
at Telerik.Windows.PropertyMetadata.<>c__DisplayClass1.<Create>b__0(DependencyObject d, DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.RaisePropertyChangeNotifications(DependencyProperty dp, Object newValue, Object oldValue)
at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet, Boolean isSetByStyle, Boolean isSetByBuiltInStyle, PropertyInvalidationReason reason)
at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value)
at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
at Telerik.Windows.Controls.DataControl.set_ItemsSource(Object value)
at GFSChangeSilverlight.ChangeUI.reportSvc_ExecuteQueryCompleted(Object sender, ExecuteQueryCompletedEventArgs e)
at GFSChangeSilverlight.ReportService.ReportServiceSoapClient.OnExecuteQueryCompleted(Object state)
Line: 1
Char: 1
Code: 0
URI: http://localhost/gfschange/Change2.aspx?mode=rfc&id=24436
private
static Type CreateType(IEnumerable<XElement> columnDefinitions)
{
AppDomain myDomain = AppDomain.CurrentDomain;
AssemblyName myAsmName = new AssemblyName("MyAssembly");
System.Reflection.Emit.
AssemblyBuilder myAssembly = myDomain.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.Run);
ModuleBuilder myModule = myAssembly.DefineDynamicModule(myAsmName.Name);
TypeBuilder myType = myModule.DefineType("DataSource", TypeAttributes.Public);
foreach (XElement col in columnDefinitions)
{
XAttribute typeAttribute = col.Attributes().First<XAttribute>(a => a.Name.LocalName == "type");
Type propertyType;
switch (typeAttribute.Value)
{
case "xs:dateTime":
propertyType =
typeof(Nullable<DateTime>);
break;
case "xs:string":
propertyType =
typeof(String);
break;
case "xs:int":
propertyType =
typeof(Nullable<int>);
break;
default:
throw new Exception("Unexpected type attribute value: " + typeAttribute.Value);
}
XAttribute nameAttribute = col.Attributes().First<XAttribute>(a => a.Name.LocalName == "name");
string propertyName = nameAttribute.Value;
FieldBuilder exField = myType.DefineField("_" + propertyName, propertyType, FieldAttributes.Public);
PropertyBuilder exProperty = myType.DefineProperty(propertyName, PropertyAttributes.None, propertyType, Type.EmptyTypes);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
MethodBuilder exGetMethod = myType.DefineMethod("get_" + propertyName,
getSetAttr,
propertyType,
Type.EmptyTypes);
ILGenerator getIlgen = exGetMethod.GetILGenerator();
getIlgen.Emit(
OpCodes.Ldarg_0);
getIlgen.Emit(
OpCodes.Ldfld, exField);
getIlgen.Emit(
OpCodes.Ret);
exProperty.SetGetMethod(exGetMethod);
MethodBuilder exSetMethod = myType.DefineMethod("set_" + propertyName,
getSetAttr,
null,
new Type[] { propertyType });
ILGenerator setIlgen = exSetMethod.GetILGenerator();
setIlgen.Emit(
OpCodes.Ldarg_0);
setIlgen.Emit(
OpCodes.Ldarg_1);
setIlgen.Emit(
OpCodes.Stfld, exField);
setIlgen.Emit(
OpCodes.Ret);
exProperty.SetSetMethod(exSetMethod);
}
return myType.CreateType();
}