Tim
Tim

Reputation: 1249

Reflection To Update Source Entity with Children To Target With Children

For all you Reflection experts I need some help. I am working on a gneric method to be able to use hierarchical data assignments in an MVC application using CodeFirst and EF5. A user would apply changes to the parent object (source) and this method would populate the child object(s) (target). It's not working as I would expect. Any pointers would be greatly appreciated.

The below method "CopyEntity" is intended to take a source entity that has child entities and update a target entity of the same type and structure. We are only looking to update those entities that inherit a base type of 'Association' and also do not have a 'NoInherit' attribute on the members. Not every property would be updated from the 'Association'

public class ClassCode : Inheritable<ClassCode>
{

    // Base Properties ----------------------------------
    public int LobId { get; set; }
    public virtual LOB LOB { get; set; }
    public bool IsVirtual { get; set; }
    //public string Name { get; set; }
    public string Description { get; set; }

    [Required(ErrorMessage = "Select Code")]
    public string Code { get; set; }
    //enable to force recreation of the database
    public string IID { get; set; }

    public virtual ICollection<ClassSubjectivityAssoc> Subjectivities{ get; set; }
}

public class ClassSubjectivityAssoc : Association
{
    [Column("SubjectivityId")]
    public override int AssociationId { get; set; }

    [ForeignKey("AssociationId")]
    public virtual Subjectivity Subjectivity { get; set; }

    public int ClassCodeId { get; set; }
    [ForeignKey("ClassCodeId")]
    public virtual ClassCode ClassCode { get; set; }
}

public abstract class Association  : BaseEntity
{
    public DateTime Start { get; set; }
    public DateTime? End { get; set; }
    public bool IsCurrent { get; set; }
    public int Order { get; set; }
    public bool BreakInheritance { get; set; }
    public int? InhId { get; set; }
    public virtual ClassCode InhClass { get; set; }
    public int CodeId { get; set; }
    [ForeignKey("ClassCodeId")]
    public virtual Code ClassCode { get; set; }
    public abstract int AssociationId {get; set;}
}

public class BaseEntity
{
    private static DateTime CREATE_DATE
    {
        get { return DateTime.Now; }
    }

    [NoInherit]
    public int Id { get; set; }

    [NoInherit]
    public string CreatedBy { get; set; }
    [NoInherit]
    public DateTime Created { get; set; }

    [NoInherit]
    public string LastModifiedBy { get; set; }
    [NoInherit]
    public DateTime LastModified { get; set; }

    public bool IsActive { get; set; }
}

protected void CopyEntity(Inheritable<T> source, Inheritable<T> target)
{
    Type sourceType = source.GetType();
    Type targetType = target.GetType();
    // ensure that the types can be assigned to one another
    //if (!targetType.IsAssignableFrom(sourceType)) return;

    // loop through the properties of the source system
    foreach (PropertyInfo sourceMember in sourceType.GetProperties().Where(sourceMember => sourceMember.GetCustomAttribute(typeof(NoInheritAttribute)) == null))
    {
        var targetMember = targetType.GetProperty(sourceMember.Name);
        // if the property doesn't exist in the target, let's get out
        if (targetMember == null) continue;

        // check if this property is a Collection
        bool isCollection = (sourceMember.PropertyType.IsGenericType && sourceMember.PropertyType.GetInterfaces().Any(x =>
                                      x.IsGenericType &&
                                      x.GetGenericTypeDefinition() == typeof(IEnumerable<>)));

        // if we are not dealing with associations, let's get outta herre
        if (!(isCollection
                  ? sourceMember.PropertyType.GetGenericArguments().Single()
                  : sourceMember.PropertyType)
                 .IsSubclassOf(typeof(Association))) continue;

        if (isCollection)
        {
            IEnumerable<Association> targetCollection = (IEnumerable<Association>) targetMember.GetValue(target);
            // Loop through the collection and evaluate
            foreach (Association sourceAssociation in (IEnumerable) sourceMember.GetValue(source))
            {
                Association targetAssociation =
                    targetCollection.SingleOrDefault(t => t.AssociationId == sourceAssociation.AssociationId);
                CopyAssociation(sourceAssociation, targetAssociation);
            }
        }
        else
        {
            Association sourceAssociation = (Association) sourceMember.GetValue(source);
            Association targetAssociation = (Association) targetMember.GetValue(target);
            CopyAssociation(sourceAssociation, targetAssociation);
        }
    }
}

The problem is: It does not seem to be traversing the child objects properly and does not update the target as I would have expected. Looking for input and suggections as I am not as proficient in Reflection as I would like.

UPDATE: 12/06/2012

Ok, So I believe I've found the problem but still looking for a solution. This problem came to me from another dev on my team with the assumption that the object model was sound. However, it seems that the entity that we are currently testing does not retrieve the seeded values from the db.

There is nothing defined in the OnModelCreating() method defining this association although the tables are being properly created from the entity model and the data is being loaded properly from the dbSeed() method. I can run a select statement from the db and get the proper data. It seems that the entity model foreign key associations might be invalid and thus preventing the correct data retrieval. Any help would be greatly appreciated.

UPDATE: Ok, finally retrieving the correct data. This finally did it.

    modelBuilder.Entity<ClassSubjectivityAssoc>()
                .HasRequired(c => c.ClassCode)
                .WithMany(u => u.Subjectivities)
                .HasForeignKey(f => f.ClassCodeId);

Upvotes: 1

Views: 362

Answers (2)

Tim
Tim

Reputation: 1249

See original post "UPDATES" for solution.

Upvotes: 0

SWeko
SWeko

Reputation: 30902

I'm not really sure if I understand the question, but it seems that you just need to make sure that sourceMember is either an Association (or descendant) or a collection of Association.

You could do that in a quick and dirty way with casting the resulting value to Association, and checking if the cast succeeded, like this for simple properties:

Association sourceAssociation = sourceMember.GetValue(source) as Association;
if (sourceAssociation != null) // the source is a valid Association
{
   Association targetAssociation = targetMember.GetValue(target) as Association;
   if (targetAssociation != null) // the destionation too
   {
        CopyAssociation(sourceAssociation, targetAssociation);
   }
}

and with similar code for the collections as well


The method you have shown will not descend a object tree, so it will only work on Association objects that are directly attached to the Entity.

Upvotes: 1

Related Questions