/// ------------------------------------------------------------------------------------ /// <summary> /// Merges object into this object. /// if fLoseNoStringData is false: /// For atomic properties, if this object has something in the property, the source /// property is ignored. For sequence properties, the objects in the source will be /// moved and appended to the properties in this object. Any references to the /// source object will be transferred to this object. The source object is deleted /// at the end of this method (objSrc.DeleteUnderlyingObject() call). /// String properties are copied from the source if the destination (this) has no value /// and the source has a value. /// /// if fLoseNoStringData is true, the above is modified as follows: /// 1. If a string property has a value in both source and destination, and the values /// are different, append the source onto the destination. /// 2. If an atomic object property has a value in both source and destination, /// recursively merge the value in the source with the value in the destination. /// </summary> /// <param name="objSrc">Object whose properties will be merged into this object's properties</param> /// <remarks> /// NB: The given object will be deleted in this method, so don't expect it to be valid, afterwards. /// </remarks> /// <param name="fLoseNoStringData"></param> /// ------------------------------------------------------------------------------------ public virtual void MergeObject(ICmObject objSrc, bool fLoseNoStringData) { Debug.Assert(m_cache != null); // We don't allow merging items of different classes. Debug.Assert(ClassID == objSrc.ClassID); if (ClassID != objSrc.ClassID) return; IFwMetaDataCache mdc = m_cache.MetaDataCacheAccessor; PropertyInfo[] myProperties = GetType().GetProperties(); PropertyInfo[] srcProperties = objSrc.GetType().GetProperties(); string fieldname; // Process all the fields in the source. foreach(uint flid in DbOps.GetFieldsInClassOfType(mdc, ClassID, FieldType.kgrfcptAll)) { /* These values will also be returned because they are for most of CmObject's flids. * I think it will do this for each superclass, so there could be some repeats on them. * * pvuFields->Push(101); // kflidCmObject_Guid * pvuFields->Push(102); // kflidCmObject_Class * pvuFields->Push(103); // kflidCmObject_Owner * pvuFields->Push(104); // kflidCmObject_OwnFlid * pvuFields->Push(105); // kflidCmObject_OwnOrd * //pvuFields->Push(106); // kflidCmObject_UpdStmp * //pvuFields->Push(107); // kflidCmObject_UpdDttm * */ if (flid < 1000) continue; // Do nothing for the CmObject flids. if (flid >= (int)SpecialTagValues.ktagMinVp) continue; // Do nothing for virtual properties. int nType = mdc.GetFieldType(flid); fieldname = mdc.GetFieldName(flid); //|| fieldname == "DateModified" //|| nType == (int)FieldType.kcptTime // This is handled by a separate connection, so it can time out, if another transaction is open. if (fieldname == "DateCreated" || nType == (int)FieldType.kcptImage // FDO does not support this one. || nType == (int)FieldType.kcptGenDate) // FDO does not support setter for gendate. continue; // Don't mess with this one. // Set suffixes on some of the types. switch (nType) { case (int)FieldType.kcptOwningAtom: // 23 { fieldname += "OA"; break; } case (int)FieldType.kcptReferenceAtom: // 24 { fieldname += "RA"; break; } case (int)FieldType.kcptOwningCollection: // 25 { fieldname += "OC"; break; } case (int)FieldType.kcptReferenceCollection: // 26 { fieldname += "RC"; break; } case (int)FieldType.kcptOwningSequence: // 27 { fieldname += "OS"; break; } case (int)FieldType.kcptReferenceSequence: // 28 { fieldname += "RS"; break; } } Object myCurrentValue = null; MethodInfo mySetMethod = null; Object srcCurrentValue = null; PropertyInfo pi = this.GetType().GetProperty(fieldname); if (pi != null) { myCurrentValue = pi.GetGetMethod().Invoke(this, null); mySetMethod = pi.GetSetMethod(); srcCurrentValue = objSrc.GetType().GetProperty(fieldname).GetGetMethod().Invoke(objSrc, null); } else { // We must have a custom field, and it needs special treatment. Debug.Assert(m_cache.GetIsCustomField(flid)); mySetMethod = null; string classname = mdc.GetOwnClsName(flid); string sView = classname + "_" + fieldname; switch (nType) { case (int)FieldType.kcptString: case (int)FieldType.kcptBigString: myCurrentValue = new TsStringAccessor(m_cache, m_hvo, (int)flid); srcCurrentValue = new TsStringAccessor(objSrc.Cache, objSrc.Hvo, (int)flid); break; case (int)FieldType.kcptMultiString: case (int)FieldType.kcptMultiBigString: myCurrentValue = new MultiStringAccessor(m_cache, m_hvo, (int)flid, sView); srcCurrentValue = new MultiStringAccessor(objSrc.Cache, objSrc.Hvo, (int)flid, sView); break; case (int)FieldType.kcptMultiUnicode: case (int)FieldType.kcptMultiBigUnicode: myCurrentValue = new MultiUnicodeAccessor(m_cache, m_hvo, (int)flid, sView); srcCurrentValue = new MultiUnicodeAccessor(objSrc.Cache, objSrc.Hvo, (int)flid, sView); break; } } if (srcCurrentValue == null) continue; // Nothing to merge. Debug.Assert(srcCurrentValue != null); /* * NOTE: Each of the cases (except the exception, which can't be tested) * is tested in the MergeObjectsTests class in the unit tests. * If any additions are made, or if some currently unused cases are enabled, * be sure to add them (or enable them) to that class, as well. */ switch (nType) { default: throw new ApplicationException("Unrecognized data type for merging: " + nType.ToString()); /* 0 -> 9 */ case (int)FieldType.kcptBoolean: // 1 { // Can't be null, so we have to live with default of 0 (false). // 0 gets replaced with source data, if 1 (true). bool myBool = (bool)myCurrentValue; bool srcBool = (bool)srcCurrentValue; if (!myBool && srcBool) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcCurrentValue}); } break; } // case (int)FieldType.kcptInteger: // 2 Fall through // Setter not implemented in FDO. case (int)FieldType.kcptGenDate: // 8 { // Can't be null, so we have to live with default of 0. // Zero gets replaced with source data, if greater than 0. int myInt = (int)myCurrentValue; int srcInt = (int)srcCurrentValue; if (myInt == 0 && srcInt > 0) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcCurrentValue}); } break; } case (int)FieldType.kcptTime: // 5 { // If it is DateCreated, we won't even be here, // since we will have already skipped it. bool resetTime = false; DateTime srcTime = DateTime.Now; // If it is DateModified, always set it to 'now'. if (fieldname == "DateModified") { // Already using 'Now'. resetTime = true; } else { // Otherwise, a later source will replace an older target. DateTime myTime = (DateTime)myCurrentValue; srcTime = (DateTime)srcCurrentValue; resetTime = (myTime < srcTime); if (myTime < srcTime) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcTime}); } } if (resetTime) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcTime}); } break; } case (int)FieldType.kcptGuid: // 6 { // May be null. Guid myGuidValue = (Guid)myCurrentValue; Guid srcGuidValue = (Guid)srcCurrentValue; if (myGuidValue == Guid.Empty && srcGuidValue != Guid.Empty) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcGuidValue}); mySetMethod.Invoke(objSrc, new object[] {Guid.Empty}); } break; } //case (int)FieldType.kcptImage: // 7 Fall through. case (int)FieldType.kcptBinary: // 8 { if (myCurrentValue == null) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcCurrentValue}); } break; } /* 13 -> 20 */ case (int)FieldType.kcptString: // 13 Fall through case (int)FieldType.kcptBigString: // 17 { if (MergeStringProp((int)flid, nType, objSrc, fLoseNoStringData, myCurrentValue, srcCurrentValue)) break; TsStringAccessor myTsa = myCurrentValue as TsStringAccessor; myTsa.MergeString(srcCurrentValue as TsStringAccessor, fLoseNoStringData); break; } case (int)FieldType.kcptMultiString: // 14 Fall through. case (int)FieldType.kcptMultiBigString: // 18 { if (MergeStringProp((int)flid, nType, objSrc, fLoseNoStringData, myCurrentValue, srcCurrentValue)) break; MultiStringAccessor myMsa = myCurrentValue as MultiStringAccessor; myMsa.MergeAlternatives(srcCurrentValue as MultiStringAccessor, fLoseNoStringData); break; } case (int)FieldType.kcptUnicode: // 15 Fall through. case (int)FieldType.kcptBigUnicode: // 19 { if (MergeStringProp((int)flid, nType, objSrc, fLoseNoStringData, myCurrentValue, srcCurrentValue)) break; string myUCurrent = myCurrentValue as string; string srcUValue = srcCurrentValue as string; if ((myUCurrent == null || myUCurrent == String.Empty) && srcUValue != String.Empty) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcUValue}); } else if (fLoseNoStringData && myUCurrent != null && myUCurrent != String.Empty && srcUValue != null && srcUValue != String.Empty && srcUValue != myUCurrent) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {myUCurrent + ' ' + srcUValue}); } break; } case (int)FieldType.kcptMultiUnicode: // 16 Fall through case (int)FieldType.kcptMultiBigUnicode: // 20 This one isn't actually used yet, but I hope it is the same as the small MultiUnicode { if (MergeStringProp((int)flid, nType, objSrc, fLoseNoStringData, myCurrentValue, srcCurrentValue)) break; MultiUnicodeAccessor myMua = myCurrentValue as MultiUnicodeAccessor; myMua.MergeAlternatives(srcCurrentValue as MultiUnicodeAccessor, fLoseNoStringData); break; } /* 23 -> 28 */ case (int)FieldType.kcptOwningAtom: case (int)FieldType.kcptReferenceAtom: // 24 { ICmObject srcObj = srcCurrentValue as ICmObject; ICmObject currentObj = myCurrentValue as ICmObject; if (myCurrentValue == null) { Debug.Assert(mySetMethod != null); mySetMethod.Invoke(this, new object[] {srcObj}); break; } else if (fLoseNoStringData && nType == (int)FieldType.kcptOwningAtom && srcObj != null && currentObj.GetType() == srcObj.GetType()) { // merge the child objects. currentObj.MergeObject(srcObj, true); } break; } case (int)FieldType.kcptOwningCollection: // 25 Fall through, since the collection class knows how to merge itself properly. case (int)FieldType.kcptReferenceCollection: // 26 { PropertyInfo piCol = FdoVector<ICmObject>.HvoArrayPropertyInfo(srcCurrentValue); MethodInfo myAddMethod = FdoCollection<ICmObject>.AddIntMethodInfo(myCurrentValue); foreach (int hvo in (int[])piCol.GetGetMethod().Invoke(srcCurrentValue, null)) { myAddMethod.Invoke(myCurrentValue, new object[] { hvo }); } break; } case (int)FieldType.kcptOwningSequence: // 27 Fall through, since the collection class knows how to merge itself properly. case (int)FieldType.kcptReferenceSequence: // 28 { PropertyInfo piCol = FdoVector<ICmObject>.HvoArrayPropertyInfo(srcCurrentValue); MethodInfo myAppendMethod = FdoSequence<ICmObject>.AppendIntMethodInfo(myCurrentValue); foreach (int hvo in (int[])piCol.GetGetMethod().Invoke(srcCurrentValue, null)) { myAppendMethod.Invoke(myCurrentValue, new object[] { hvo }); } break; } } } // Now move all incoming references. CmObject.ReplaceReferences(m_cache, objSrc, this); objSrc.DeleteUnderlyingObject(); }