/// <summary> /// Attaches multiple entire objectgraphs to the context. /// </summary> public static T[] AttachObjectGraphs <T>(this ObjectContext context, IEnumerable <T> entities, params Expression <Func <T, object> >[] paths) { T[] unattachedEntities = entities.ToArray(); T[] attachedEntities = new T[unattachedEntities.Length]; Type entityType = typeof(T); if (unattachedEntities.Length > 0) { // Workaround to ensure the assembly containing the entity type is loaded: // (see: https://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3405138&SiteID=1) try { context.MetadataWorkspace.LoadFromAssembly(entityType.Assembly); } catch { } #region Automatic preload root entities // Create a WHERE clause for preload the root entities: StringBuilder where = new StringBuilder("(1=0)"); List <ObjectParameter> pars = new List <ObjectParameter>(); int pid = 0; foreach (T entity in unattachedEntities) { // If the entity has an entitykey: EntityKey entityKey = ((IEntityWithKey)entity).EntityKey; if (entityKey != null) { where.Append(" OR ((1=1)"); foreach (EntityKeyMember keymember in entityKey.EntityKeyValues) { string pname = String.Format("p{0}", pid++); where.Append(" AND (it.["); where.Append(keymember.Key); where.Append("] = @"); where.Append(pname); where.Append(")"); pars.Add(new ObjectParameter(pname, keymember.Value)); } where.Append(")"); } } // If WHERE clause not empty, construct and execute query: if (pars.Count > 0) { // Construct query: ObjectQuery <T> query = (ObjectQuery <T>)context.PublicGetProperty(context.GetEntitySetName(typeof(T))); foreach (var path in paths) { query = (ObjectQuery <T>)query.Include(path); } query = query.Where(where.ToString(), pars.ToArray()); // Execute query and load entities: //Console.WriteLine(query.ToTraceString()); query.Execute(MergeOption.AppendOnly).ToArray(); } #endregion Automatic preload root entities // Attach the root entities: for (int i = 0; i < unattachedEntities.Length; i++) { attachedEntities[i] = (T)context.AddOrAttachInstance(unattachedEntities[i], true); } // Collect property paths into a tree: TreeNode <ExtendedPropertyInfo> root = new TreeNode <ExtendedPropertyInfo>(null); foreach (var path in paths) { List <ExtendedPropertyInfo> members = new List <ExtendedPropertyInfo>(); EntityFrameworkHelper.CollectRelationalMembers(path, members); root.AddPath(members); } // Navigate over all properties: for (int i = 0; i < unattachedEntities.Length; i++) { NavigatePropertySet(context, root, unattachedEntities[i], attachedEntities[i]); } } // Return the attached root entities: return(attachedEntities); }
/// <summary> /// Navigates a property path on detached instance to translate into attached instance. /// </summary> private static void NavigatePropertySet(ObjectContext context, TreeNode <ExtendedPropertyInfo> propertynode, object owner, object attachedowner) { // Try to navigate each of the properties: foreach (TreeNode <ExtendedPropertyInfo> childnode in propertynode.Children) { ExtendedPropertyInfo property = childnode.Item; // Retrieve property value: object related = property.PropertyInfo.GetValue(owner, null); if ((property.ReferenceOnly) && (typeof(IEnumerable).IsAssignableFrom(property.PropertyInfo.PropertyType))) { // ReferenceOnly marker not valid on collections: throw new InvalidOperationException("The ReferenceOnly marker method is not supported on the many side of relations."); } else if (property.ReferenceOnly) { // Apply reference update on ReferenceOnly: EntityReference reference = (EntityReference)attachedowner.PublicGetProperty(property.PropertyInfo.Name + "Reference"); reference.EntityKey = ((EntityReference)owner.PublicGetProperty(property.PropertyInfo.Name + "Reference")).EntityKey; } else if (related is IEnumerable) { // Load current list in context: object attachedlist = property.PropertyInfo.GetValue(attachedowner, null); RelatedEnd relatedEnd = (RelatedEnd)attachedlist; if (((EntityObject)attachedowner).EntityState != EntityState.Added && !relatedEnd.IsLoaded) { relatedEnd.Load(); } // Recursively navigate through new members: List <object> newlist = new List <object>(); foreach (var relatedinstance in (IEnumerable)related) { object attachedinstance = context.AddOrAttachInstance(relatedinstance, !property.NoUpdate); newlist.Add(attachedinstance); NavigatePropertySet(context, childnode, relatedinstance, attachedinstance); } // Synchronise lists: List <object> removedItems; SyncList(attachedlist, newlist, out removedItems); // Delete removed items if association is owned: if (AssociationEndBehaviorAttribute.GetAttribute(property.PropertyInfo).Owned) { foreach (var removedItem in removedItems) { context.DeleteObject(removedItem); } } } else if (!typeof(IEnumerable).IsAssignableFrom(property.PropertyInfo.PropertyType)) { // Load reference of currently attached in context: RelatedEnd relatedEnd = (RelatedEnd)attachedowner.PublicGetProperty(property.PropertyInfo.Name + "Reference"); if (((EntityObject)attachedowner).EntityState != EntityState.Added && !relatedEnd.IsLoaded) { relatedEnd.Load(); } // Recursively navigate through new value (unless it's null): object attachedinstance; if (related == null) { attachedinstance = null; } else { attachedinstance = context.AddOrAttachInstance(related, !property.NoUpdate); NavigatePropertySet(context, childnode, related, attachedinstance); } // Synchronise value: property.PropertyInfo.SetValue(attachedowner, attachedinstance, null); } } }