3

I have created a custom injection class for ValueInjecter that does recursive injection for generic child collections, but that works with the existing target objects rather than cloning like the "CloneInjection" sample on the VI site. However, currently I am casting to a known type (ICollection<MyTargetType>), so the injection class is only good for the one hard coded type. I can't seem to figure a way to cast a generic dynamically, any suggestions? Here's the SetValue code for my "RecursiveInjection" class.

//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
  return c.SourceProp.Value;

if (c.SourceProp.Type.IsGenericType)
  {
  //handle IEnumerable<> also ICollection<> IList<> List<>
  if (c.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
    {
      var t = c.TargetProp.Type.GetGenericArguments()[0];
      if (t.IsValueType || t == typeof(string)) return c.SourceProp.Value;

      //get enumerable object in target
     var targetCollection = c.TargetProp.Value as ICollection<MyTargetType>;
     //possible to cast dynamically?

     foreach (var o in c.SourceProp.Value as IEnumerable)
     {
       //get ID of source object
       var sourceID = (int)o.GetProps().GetByName("ID").GetValue(o);
       //find matching target object if there is one
       var target = targetCollection.SingleOrDefault(x => x.ID == sourceID);
       if (target != null)
       {
         target.InjectFrom<RecursiveInjection>(o);
       }
     }
    return targetCollection;
  }
}

Thanks, DanO

Omu
  • 69,856
  • 92
  • 277
  • 407
DanO
  • 911
  • 1
  • 8
  • 16
  • could you tell me what are you trying to achieve ? – Omu May 03 '11 at 11:00
  • @Omu - take a look at my posts on the VI "Deep Cloning" page - http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home - I'm updating EF POCO entities, so have to update the target object properties directly rather than creating clones – DanO May 04 '11 at 13:40
  • I understood that you are trying to do something like the clone injection but without cloning, and also that you succeeded with but you need it more dynamic ( probably you could also try to make a generic injection), what I would like to know is why/what are trying to do, what's your initial source and target objects and what you want to inject from one to another – Omu May 04 '11 at 13:53
  • @Omu - I'm trying to create a reusable injection that will inject POCO entities loaded from the EF context with values from similar viewmodel objects posted via an MVC form, and I want the injection to recursively handle all child collections and their properties (for instance a Customer entity that has a child collection of Orders). Customer.Orders = CustomerViewModel.Orders does not work with EF, which is why the clone injection does not work. Make sense? – DanO May 04 '11 at 20:24
  • this is doable but there is a problem with the collections, if for example on the source side an item from the middle of the collection was removed, how would you know from which object from the source collection to which object of the target collection to inject, you can't just take them one by one thinking that they are in the same order, because they could be not – Omu May 05 '11 at 07:20

3 Answers3

1

I would just use ICollection and override Equals( object ) for your comparisons.

public class MyTargetType
{
    public override bool Equals( object obj )
    {
        return ( obj is MyTargetType )
                    ? this.ID == ( ( MyTargetType ) obj ).ID
                    : false;
    }
}

Then in your loop, you would match the object directly instead of using reflection.

var target = targetCollection.SingleOrDefault( x => x.Equals( o ) );
AnteSim
  • 1,265
  • 1
  • 12
  • 11
  • thanks for the suggestion - I think this will only solve part of the problem. If I just cast targetCollection as ICollection, SingleOrDefault is not a valid method on the object – DanO May 04 '11 at 14:42
1

Although I haven't been able to figure a way to dynamically cast a generic collection, I did discover a solution to the underlying problem I was trying to solve, which was to inject EF POCO objects that contain child collections (one-to-many "navigation properties").

I just upgraded from EF 4.0 to EF 4.1, and hallelujah! EF 4.1 actually manages object merging on child collections now. After the upgrade, I am able to use the CloneInjection as is.

DanO
  • 911
  • 1
  • 8
  • 16
  • so you don't need this anymore :) ? – Omu May 05 '11 at 06:37
  • @Omu - looks like it's a problem I no longer need to solve, although it still would be an interesting exercise. You are right that the code I posted only handles one-to-one updates. Ultimately I had hoped to work out the logic to handle added and removed items as well. – DanO May 05 '11 at 15:12
  • well, you can, but you have to do it not for any object but for IEntity that would have Id property, this way you can compare objects by Id – Omu May 05 '11 at 16:05
0

I think this is what you're looking for:

    public class M1
    {
        public string Name { get; set; }
    }

    public class M2
    {
        public string Name { get; set; }
    }

    [Test]
    public void Cast()
    {
        object source = new List<M1> {new M1 {Name = "o"}};
        object target = new List<M2>();


        var targetArgumentType = target.GetType().GetGenericArguments()[0];

        var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(targetArgumentType));
        var add = list.GetType().GetMethod("Add");

        foreach (var o in source as IEnumerable)
        {
            var t = Activator.CreateInstance(targetArgumentType);
            add.Invoke(list, new[] { t.InjectFrom(o) });
        }

        target = list;

        Assert.AreEqual("o", (target as List<M2>).First().Name);
    }

with .NET 4 you can make this a bit shorter by using dynamic

Omu
  • 69,856
  • 92
  • 277
  • 407