예제 #1
0
		/// ------------------------------------------------------------------------------------
		/// <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();
		}