public static void Expand <IParentEntity, IUniqueEntity>(IModel model, Func <IParentEntity, ICollection <IUniqueEntity> > accessor) where IParentEntity : IPersistEntity where IUniqueEntity : IPersistEntity { //get duplicates in one go to avoid exponential search var candidates = new Dictionary <IUniqueEntity, List <IParentEntity> >(); foreach (var entity in model.Instances.OfType <IParentEntity>()) { foreach (var val in accessor(entity)) { List <IParentEntity> assets; if (!candidates.TryGetValue(val, out assets)) { assets = new List <IParentEntity>(); candidates.Add(val, assets); } assets.Add(entity); } } var multi = candidates.Where(a => a.Value.Count > 1); var map = new XbimInstanceHandleMap(model, model); foreach (var kvp in multi) { var value = kvp.Key; var entities = kvp.Value; //skip the first for (int i = 1; i < entities.Count; i++) { //clear map to create complete copy every time map.Clear(); var copy = model.InsertCopy(value, map, null, false, false); //remove original and add fresh copy var entity = entities[i]; var collection = accessor(entity); collection.Remove(value); collection.Add(copy); } } }
/// <summary> /// Inserts deep copy of an object into this model. The entity must originate from the same schema (the same EntityFactory). /// This operation happens within a transaction which you have to handle yourself unless you set the parameter "noTransaction" to true. /// Insert will happen outside of transactional behaviour in that case. Resulting model is not guaranteed to be valid according to any /// model view definition. However, it is granted to be consistent. You can optionaly bring in all inverse relationships. Be carefull as it /// might easily bring in almost full model. /// /// </summary> /// <typeparam name="T">Type of the copied entity</typeparam> /// <param name="model">Model to be used as a target</param> /// <param name="toCopy">Entity to be copied</param> /// <param name="mappings">Mappings of previous inserts</param> /// <param name="includeInverses">Option if to bring in all inverse entities (enumerations in original entity)</param> /// <param name="keepLabels">Option if to keep entity labels the same</param> /// <param name="propTransform">Optional delegate which you can use to filter the content which will get coppied over.</param> /// <param name="getLabeledEntity">Functor to be used to create entity with specified label</param> /// <returns>Copy from this model</returns> public static T InsertCopy <T>(IModel model, T toCopy, XbimInstanceHandleMap mappings, PropertyTranformDelegate propTransform, bool includeInverses, bool keepLabels, Func <Type, int, IPersistEntity> getLabeledEntity) where T : IPersistEntity { try { var toCopyLabel = toCopy.EntityLabel; XbimInstanceHandle copyHandle; var toCopyHandle = new XbimInstanceHandle(toCopy); //try to get the value if it was created before if (mappings.TryGetValue(toCopyHandle, out copyHandle)) { return((T)copyHandle.GetEntity()); } var expressType = model.Metadata.ExpressType(toCopy); var copy = keepLabels ? getLabeledEntity(toCopy.GetType(), toCopyLabel) : model.Instances.New(toCopy.GetType()); copyHandle = new XbimInstanceHandle(copy); //key is the label in original model mappings.Add(toCopyHandle, copyHandle); var props = expressType.Properties.Values.Where(p => !p.EntityAttribute.IsDerived); if (includeInverses) { props = props.Union(expressType.Inverses); } foreach (var prop in props) { var value = propTransform != null ? propTransform(prop, toCopy) : prop.PropertyInfo.GetValue(toCopy, null); if (value == null) { continue; } var isInverse = (prop.EntityAttribute.Order == -1); //don't try and set the values for inverses var theType = value.GetType(); //if it is an express type or a value type, set the value if (theType.GetTypeInfo().IsValueType || typeof(ExpressType).GetTypeInfo().IsAssignableFrom(theType) || theType == typeof(string)) { prop.PropertyInfo.SetValue(copy, value, null); } else if (!isInverse && typeof(IPersistEntity).GetTypeInfo().IsAssignableFrom(theType)) { prop.PropertyInfo.SetValue(copy, InsertCopy(model, (IPersistEntity)value, mappings, propTransform, includeInverses, keepLabels, getLabeledEntity), null); } else if (!isInverse && typeof(IList).GetTypeInfo().IsAssignableFrom(theType)) { var itemType = theType.GetItemTypeFromGenericType(); var copyColl = prop.PropertyInfo.GetValue(copy, null) as IList; if (copyColl == null) { throw new Exception(string.Format("Unexpected collection type ({0}) found", itemType.Name)); } foreach (var item in (IList)value) { var actualItemType = item.GetType(); if (actualItemType.GetTypeInfo().IsValueType || typeof(ExpressType).GetTypeInfo().IsAssignableFrom(actualItemType)) { copyColl.Add(item); } else if (typeof(IPersistEntity).GetTypeInfo().IsAssignableFrom(actualItemType)) { var cpy = InsertCopy(model, (IPersistEntity)item, mappings, propTransform, includeInverses, keepLabels, getLabeledEntity); copyColl.Add(cpy); } else if (typeof(IList).GetTypeInfo().IsAssignableFrom(actualItemType)) //list of lists { var listColl = (IList)item; var getAt = copyColl.GetType().GetTypeInfo().GetMethod("GetAt"); if (getAt == null) { throw new Exception(string.Format("GetAt Method not found on ({0}) found", copyColl.GetType().Name)); } var copyListColl = getAt.Invoke(copyColl, new object[] { copyColl.Count }) as IList; if (copyListColl == null) { throw new XbimException("Collection can't be used as IList"); } foreach (var listItem in listColl) { var actualListItemType = listItem.GetType(); if (actualListItemType.GetTypeInfo().IsValueType || typeof(ExpressType).GetTypeInfo().IsAssignableFrom(actualListItemType)) { copyListColl.Add(listItem); } else if (typeof(IPersistEntity).GetTypeInfo().IsAssignableFrom(actualListItemType)) { var cpy = InsertCopy(model, (IPersistEntity)listItem, mappings, propTransform, includeInverses, keepLabels, getLabeledEntity); copyListColl.Add(cpy); } else { throw new Exception(string.Format("Unexpected collection item type ({0}) found", itemType.Name)); } } } else { throw new Exception(string.Format("Unexpected collection item type ({0}) found", itemType.Name)); } } } else if (isInverse && value is IEnumerable <IPersistEntity> ) //just an enumeration of IPersistEntity { foreach (var ent in (IEnumerable <IPersistEntity>)value) { InsertCopy(model, ent, mappings, propTransform, includeInverses, keepLabels, getLabeledEntity); } } else if (isInverse && value is IPersistEntity) //it is an inverse and has a single value { InsertCopy(model, (IPersistEntity)value, mappings, propTransform, includeInverses, keepLabels, getLabeledEntity); } else { throw new Exception(string.Format("Unexpected item type ({0}) found", theType.Name)); } } return((T)copy); } catch (Exception e) { throw new XbimException(string.Format("General failure in InsertCopy ({0})", e.Message), e); } }