New Posts New Posts RSS Feed: PropertyMetadata on injected base type throws invalid cast exceptions
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

PropertyMetadata on injected base type throws invalid cast exceptions

 Post Reply Post Reply
Author
pk55 View Drop Down
Senior Member
Senior Member


Joined: 22-Jul-2009
Location: CA
Posts: 105
Post Options Post Options   Quote pk55 Quote  Post ReplyReply Direct Link To This Post Topic: PropertyMetadata on injected base type throws invalid cast exceptions
    Posted: 17-Jan-2011 at 9:43am

I'm using an injected base type for an entity's ancestor to hold common properties for all descendants. If I try to declare and use PropertyMetadata for any property on either the developer version of the injected ancestor class or in the model designer.cs declaration of the injected base type (either by adding it in the T4 or just hacking it after the code gen); with of course, the IsNative bool set to false on the DataEntityProperty since it's not an data-store backed property, I get an exception from an invalid cast unless I have at least one property on the descendant concrete class also declare PropertyMetadata in its developer class (this is from your SL PI App that I reproduced the error in):

A first chance exception of type 'System.InvalidCastException' occurred in IdeaBlade.EntityModel.SL
A first chance exception of type 'System.InvalidCastException' occurred in IdeaBlade.Core.SL
A first chance exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
System.Windows.Data Error: Cannot get 'TestWithMetadata' value (type 'System.String') from 'DomainModel.Customer' (type 'DomainModel.Customer'). BindingExpression: Path='TestWithMetadata'

DataItem='DomainModel.Customer' (HashCode=64497130); target element is 'System.Windows.Controls.TextBlock' (Name=''); target property is 'Text' (type 'System.String')..

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Unable to cast object of type 'System.Guid' to type

'System.String'.
   at IdeaBlade.EntityModel.DataEntityProperty`2.<.ctor>b__0(DataEntityPropertyGetInterceptorArgs`2 args)
   at IdeaBlade.Core.PropertyInterceptor`3.HandleAction(Action`1 action, TArgs args)
   at IdeaBlade.Core.PropertyInterceptor`3.<BuildTargetedCombinedAction>b__3(TArgs args)
   at IdeaBlade.Core.PropertyInterceptor`3.ExecuteActions(TArgs args)
   at IdeaBlade.Core.PropertyInterceptor`3.GetValue(TInstance instance, Object context)
   at IdeaBlade.EntityModel.DataEntityProperty`2.GetValue(TInstance instance, EntityVersion version)
   at IdeaBlade.EntityModel.DataEntityProperty`2.GetValue(TInstance instance)
   at DomainModel.BaseCustomer.get_TestWithMetadata()
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at System.Windows.CLRPropertyListener.get_Value()
   at System.Windows.PropertyAccessPathStep.ConnectToPropertyInSource(Boolean isSourceCollectionViewCurrentItem).

Notice it's trying to cast the primary key of the Customer class (GUID) to the STRING property that's declared in the ancestor class.

To reproduce this, use your SilverlightPIApp from the 050_PropertyInterceptors DRC_Samples.zip.

Create this simple injected base class with two string properties  (and link to it in the SL version of course).


BaseCustomer.cs (it's partial since the EDMX declares it when you specify an injected base type):

using System;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IdeaBlade.Core;
using IdeaBlade.EntityModel;
namespace DomainModel
{
    public abstract partial class BaseCustomer
    {
        public String Test
        {
            get { return "Test"; }
            set { }
        }
        public String TestWithMetadata
        {
            // set a debug breakpoint on the getter (assuming you haven't declared a Customer.PropertyMetadata partial class
            // in the Customer developer class) and then hover over TestWithMetadatda or look at the Output window from debug
            // and you'll see it throws an exception trying to cast GUID to STRING (the primary key of Customer is GUID)
            // instead of just null.  It seems the abstract partial ancestor class can't use PropertyMetadata unless the concrete descendant
            // class has at least one declaration.
            get { return PropertyMetadata.TestWithMetadata.GetValue(this); }
            set { PropertyMetadata.TestWithMetadata.SetValue(this, value); }
        }
        public partial class PropertyMetadata
        {
            public static readonly DataEntityProperty<BaseCustomer, string> TestWithMetadata =
                new DataEntityProperty<BaseCustomer, string>("TestWithMetadata", true, false, ConcurrencyStrategy.None, false, false);
        }
    }
}
 
Change the NorthwindIB.edmx to set the injected base type to "BaseCustomer" for all entities.
 
In the Customer.cs class, change it to inherit from BaseCustomer, add a property (I just added another string) and add a partial class for PropertyMetadata with the declaration of the PropertyMetadata for that added string:

using System;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using IdeaBlade.EntityModel;
using IdeaBlade.Core;
using IdeaBlade.Validation;
namespace DomainModel {
  // Customer class with sample property interceptors.
  public partial class Customer : BaseCustomer {
      public String CustomCity
      {
          get { return City + " ***"; } set { }
      }
      public partial class PropertyMetadata
      {
          // comment this out (you can leave the class declaration and the CustomCity string property) and BaseCustomer.TestWithMetadata will throw an exception
          public static readonly DataEntityProperty<Customer, string> CustomCity =
              new DataEntityProperty<Customer, string>("CustomCity", true, false, ConcurrencyStrategy.None, false, false);
      }
...the rest of this class are the same as in the sample app's class with some getter/setter interceptors that have nothing to do with the issue...
}
 
I also added the columns to the Page.xaml data:DataGrid to force the getters to be hit:

          <data:DataGridTextColumn Binding="{Binding CustomCity}" Header="Customer.City + ***"/>
          <data:DataGridTextColumn Binding="{Binding Test}" Header="BaseCustomer.Test"/>
          <data:DataGridTextColumn Binding="{Binding TestWithMetadata}" Header="BaseCustomer.TestWithMetadata (null)"/>

At first, I thought using PropertyMetadata on an ancestor class wasn't supported but since everything works fine if the child class has one property metadata declared, I assume it's a bug.
 
 
Back to Top
pk55 View Drop Down
Senior Member
Senior Member


Joined: 22-Jul-2009
Location: CA
Posts: 105
Post Options Post Options   Quote pk55 Quote  Post ReplyReply Direct Link To This Post Posted: 17-Jan-2011 at 12:09pm
More info:  using version 6.0.7 (since I'm sure you'd ask).
 
And, the child concrete class can't just have one property/PropertyMetadata declared in the developer class.  It has to match the type of the property on the ancestor or it won't work; so you'd need one concrete property/PropertyMetadata per ancestor property/PropertyMetadata.
 
So if the ancestor had an System.Nullable<int> or decimal or double or...., the child needs a matching data type property/PropertyMetadata.
 
It also doesn't work if you put these child declarations into the generated code (again, either via T4 or hacking the designer.cs file after it's generated).  They need to be in a developer class.
Back to Top
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Posted: 18-Jan-2011 at 4:55pm
As a followup, I worked with Phil on this issue and we (think we) resolved it.  The problem was that the descendent's PropertyMetadata nested class wasn't inheriting from the base class's PropertyMetadata nested class.  This meant that the base class one was being hidden.  Phil's attempt at a hack worked but a better solution is to just make sure the inheritance was setup.  The code for Customer should look like this:
 
namespace DomainModel 
{
  public partial class Customer : BaseCustomer
  {
      public partial class PropertyMetadata : BaseCustomer.PropertyMetadata
      {
      }
}
 
And of course to keep from cluttering the Developer partial class with such a useless nested partial declaration, we tweaked the T4 generation to make this change in the designer files.
Back to Top
DenisK View Drop Down
IdeaBlade
IdeaBlade


Joined: 25-Aug-2010
Posts: 715
Post Options Post Options   Quote DenisK Quote  Post ReplyReply Direct Link To This Post Posted: 18-Jan-2011 at 5:36pm
Thanks for sharing the solution guys.

You have to explicitly define the PropertyMetadata inheritance because the 2 different PropertyMetadata inner classes are not related to each other and that makes it difficult to automatically look for the right one when using injected base type. The documentation will be improved to specify this. 
Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down