Пример #1
0
        // AutoMapper has its quirks, it works fine going from Entity to Dto, other way around not so much, hence we'll
        // do a bit of a condensed version
        public virtual void MapToEntity(DbContextBase context, IEntity entity)
        {
            var scalarProps = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                              .Where(p => p.PropertyType.GetInterface(typeof(IEntityDto).Name) == null &&
                                     !p.PropertyType.GetGenericArguments().Any(ga => ga.GetInterface(typeof(IEntityDto).Name) != null) &&
                                     !p.PropertyType.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any()
                                     );

            // first loop through our non parent/children fields
            foreach (var scalarProp in scalarProps)
            {
                var entityProp = this.GetType().GetProperty(scalarProp.Name, BindingFlags.Public | BindingFlags.Instance);
                if (entityProp == null || !entityProp.CanWrite)
                {
                    continue;
                }

                entityProp.SetValue(entity, scalarProp.GetValue(this));
            }

            #region PARENTS
            var parentProps = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                              .Where(p => p.PropertyType.GetInterface(typeof(IEntityDto).Name) != null);

            foreach (var prop in parentProps)
            {
                // make sure we find a matching prop on the entity itself
                var entityProp = this.GetType().GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance);
                if (entityProp == null || entityProp.PropertyType.GetInterface(typeof(IEntity).Name) == null)
                {
                    continue;
                }

                var entityVal = entityProp.GetValue(entity) as IEntity;

                var dtoVal = prop.GetValue(this, null) as IEntityDto;

                // if our dto value is blank then we need to blank out the entity value
                if (dtoVal == null)
                {
                    if (entityVal != null)
                    {
                        entityProp.SetValue(entity, null);
                    }
                    continue;
                }

                // we only want to change the entity's value if it was blank initially or if the parent (and thus parent id) has changed
                if (entityVal == null || entityVal.ID != dtoVal.ID)
                {
                    // this is an existing parent so get from database
                    if (dtoVal.ID != 0)
                    {
                        // entityframework is not nice when it comes to retrieving an entity dynamically, so we'll do some
                        // tricky tricky reflection to get the GetEntity method and to invoke it
                        var method  = context.GetType().GetMethod("GetEntity", BindingFlags.Public | BindingFlags.Instance);
                        var generic = method.MakeGenericMethod(entityProp.PropertyType);
                        entityVal = generic.Invoke(context, new object[] { dtoVal.ID }) as IEntity;
                    }
                    else
                    {
                        // the dto object is a new object so we need to make our entity value new too
                        entityVal = Activator.CreateInstance(entityProp.PropertyType) as IEntity;
                    }

                    entityProp.SetValue(this, entityVal);
                }

                // we only want to save parents if they've explicitly been configured to be saved, otherwise we just
                // need the foreign keys and we can set them to unchanged
                if ((entity as EntityBase).SaveParentChild(entityProp.Name) || entityVal.ID == 0)
                {
                    (dtoVal as IEntityDto).MapToEntity(context, entity);
                }
                else if (entityVal.ID != 0)
                {
                    context.Entry(entityVal).State = EntityState.Unchanged;
                }
            }
            #endregion

            #region CHILDREN
            var childProps = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                             .Where(p => p.PropertyType.GetGenericArguments().Any(ga => ga.GetInterface(typeof(IEntityDto).Name) != null));

            foreach (var prop in childProps)
            {
                // make sure we find a matching prop on the entity itself. Note the prop has to be a collection with a
                // generic argument with interface IEntity, we're assuming it will be a collection, might need to do some
                // safeguarding against that
                var entityProp = this.GetType().GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance);
                if (entityProp == null || !entityProp.PropertyType.GetGenericArguments().Any(ga => ga.GetInterface(typeof(IEntity).Name) != null))
                {
                    continue;
                }

                var dtoVal    = prop.GetValue(this, null);
                var entityVal = entityProp.GetValue(entity) as IEnumerable;

                // if our dto value is blank then we need to blank out the entity value
                if (dtoVal == null)
                {
                    entityVal = null;
                    continue;
                }

                var entityType = entityProp.PropertyType.GetGenericArguments().First();

                // this should never be true, EF will always initialize a child list as empty, but just in case
                if (entityVal == null)
                {
                    var genericType = typeof(List <>).MakeGenericType(entityType);
                    entityVal = Activator.CreateInstance(genericType) as IEnumerable;
                    entityProp.SetValue(this, entityVal);
                }

                // only do this
                if ((entity as EntityBase).SaveParentChild(entityProp.Name))
                {
                    // entityframework collections are of type HashSet (which implements ICollection). The add method is only
                    // on the generic definitions and thus cannot be dynamically accessed, so we need to use tricky tricky
                    // reflection again to get to the add method
                    var addMethod = entityVal.GetType().GetMethod("Add");

                    // we'll keep track of current keys so we can remove orphans later
                    var dtoKeys = new List <int>();

                    foreach (IEntityDto childDto in dtoVal as IList)
                    {
                        IEntity childEntity = null;

                        // if our child has an ID 0 it is a new item to be added to our collection, otherwise
                        // we need to find the existing child, need to do some testing to see if we'd ever
                        // have orphaned dto IDs without a matching entity ID, should only happen on stale objects
                        // in which case we would've failed on an earlier check
                        if (childDto.ID == 0)
                        {
                            childEntity = Activator.CreateInstance(entityType) as IEntity;
                            addMethod.Invoke(entityVal, new object[] { childEntity });
                        }
                        else
                        {
                            childEntity = (entityVal as IEnumerable).OfType <IEntity>().FirstOrDefault(e => e.ID == childDto.ID);

                            // can't think of another scenario where an entity will not be in the db anymore other than someone
                            // else removing it at the same time
                            if (childEntity == null)
                            {
                                throw new Exception("Item no longer exists. Please refresh the page.");
                            }
                            dtoKeys.Add(childDto.ID);
                        }

                        childDto.MapToEntity(context, childEntity);
                    }

                    // now lets delete our orphans
                    var copiedList = entityVal.Cast <IEntity>().ToList();
                    foreach (var copied in copiedList)
                    {
                        if (copied.ID == 0)
                        {
                            continue;
                        }
                        if (!dtoKeys.Contains(copied.ID))
                        {
                            context.Entry(copied).State = EntityState.Deleted;
                        }
                    }
                }
                else
                {
                    // TODO can't think of a situation right now where child collections wouldn't want to be saved
                }
            }
            #endregion
        }