alpha pecap
alpha pecap

Reputation: 353

Read the value of a referenced entity's PropertyInfo using EF4

I would like to dynamically read the values of the PropertyInfos of EntityObjects that I come across when looping through the PropertyInfos of a parent entity (the column values of the instance of ImageType that is connected to the current instance of Image, f.i.).

The main entity's type is only known at runtime, so I'm looking for a generic way of reading the PropertyInfo values of any referenced entity object.

I can loop through the PropertyInfos of the sub entity, but when I try to get a value I get a TargetException: Object does not match target type.

// loop through the main entity's properties
foreach (PropertyInfo pi in entityType.GetProperties())
{
    // if the main entity's property is an entity
    if (pi.PropertyType.BaseType == typeof(System.Data.Objects.DataClasses.EntityObject))
    {
        // loop through the sub entity's properties
        foreach(PropertyInfo mychildren in pi.PropertyType.GetProperties())     
        {   
            // the loop works fine but when i try to get a value I get a
            // TargetException: Object does not match target type.
            object test = mychildren.GetValue(pi, null);
        }
    }
}

How can I do this?

Edit:

Entity Framework 4.0 doesn't seem to allow you to dynamically retrieve the instances of an entity's related entities. But with EF 4.1 and up you can, by using their class name as a string identifier. So I upgraded to EF 4.2 and got it working.

The reason I wanted this code is to use it in my DTO translation routine. My DTO's can have string properties that correspond to the name properties of related entities and this way I can access those without having to hard code the related entities' types.

In EF 4.1 and up, the ObjectContext is wrapped by a class named DbContext, which provides navigational properties with which to get instances of related entities using strings. To dynamically retrieve a singular related entity, you can use:

dynamic refObject = Activator.CreateInstance(refObjectType);

refObject = context.Entry(currentObject).Reference(refObjectType.Name).CurrentValue;

For those upgrading from 4.0: The recommended way to work with DbContext is not with EntityObjects but with POCOs. These can be made manually or they can be generated via the edmx context menu.

My current implementation is as follows:

// Loop through the propertyinfos of the dto's type
foreach (PropertyInfo pf in dtoType.GetProperties().Where(p => p.CanWrite))
{
    // Use the name of the dto property to get the corresponding property from the POCO's type. If it doesn't exist, pi will be null
    PropertyInfo pi = pocoType.GetProperty(pf.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

    if (pi != null)
    {
        // Check if the current propertyinfo of the POCO has a subproperty named Id
        // If this is the case we treat the propertyinfo as a referenced POCO
        if (pi.PropertyType.GetProperty("Id") != null)
        {
            // skip referenced POCOs if their data is not needed
            if (!includeRelated) continue;

            // Get the type of the referenced POCO
            Type refObjectType = pi.PropertyType;
            // Create an instance of the referenced POCO
            dynamic refObject = Activator.CreateInstance(refObjectType);

            // Create a type of GenericRepository<objectType>
            Type refObjectRepositoryType = typeof(GenericRepository<>).MakeGenericType(refObjectType);
            // Create an instance of GenericRepository<objectType>
            dynamic refObjectRepository = Activator.CreateInstance(refObjectRepositoryType);

            // Fill the dynamic POCO instance with the values of the referenced POCO instance
            refObject = refObjectRepository._context.Entry(poco).Reference(refObjectType.Name).CurrentValue;

            try
            {
                // Set the dto property with the name value of the referenced POCO instance
                // (i.e. dtoImage.ImageType = pocImage.ImageType.Name)
                pf.SetValue(dto, refObject.Name, null);
            }
            catch (RuntimeBinderException)
            {
                // this happens when the related entity is null, ie. in one to zero-or-one relationships
                continue;
            }

            continue;
        }

        // If the propertyinfo's propertytype does not have an Id property, just set the value of  
        // the dto property to that of the POCO's propertyinfo directly
        pf.SetValue(dto, pi.GetValue(poco, null), null);

    }
}

For now this code will only work for referenced entities that have both an Id and a Name property. Also, there is likely to be a performance penalty for this approach so I have implemented the flag includeRelated to toggle whether to query the related objects or not.

Upvotes: 0

Views: 3202

Answers (1)

WarHog
WarHog

Reputation: 8710

You're trying to get value from parent's PropertyInfo, but GetValue expected object of pi.PropertyType type. You should use something like this:

using (var context = new MyContext())
{
    var cust = context.Customer.First();
    var custType = cust.CustomerType;

    var pi = typeof (Customer).GetProperty("CustomerType");
    var child = pi.PropertyType.GetProperty("CustomerTypeID");

    var res = child.GetValue(custType, null);
    // this returns value of Customer.CustomerTypeID
}

Upvotes: 1

Related Questions