public DomainPropSetBase(DomainObject objParent, XPropInfoObject xpropInfo) : base(objParent, xpropInfo) { if (m_objParent.IsNew) { // свойство нового объекта всегда "прогружено" m_state = DomainPropLoadableState.Loaded; } else { m_state = DomainPropLoadableState.Ghost; } }
/// <summary> /// Рекурсивно обходит дерево объектов и добавляет их в множество objSet /// </summary> /// <param name="xmlObject">текущий xml-объект</param> /// <param name="bIsRoot">Признак корневого объекта в пакете (x-datagram)</param> private void walkThroughXmlObjects(XmlElement xmlObject, bool bIsRoot) { string sParentTypeName; // наименование типа родительского объекта string sParentPropName; // наименование родительского свойства XmlElement xmlProp; XTypeInfo typeInfo = m_xmodel.GetTypeByName(xmlObject.LocalName); if (!typeInfo.IsTemporary) { // если у объекта нет атрибута oid, то установим его, сгенерировав новый гуид if (!xmlObject.HasAttribute("oid")) { xmlObject.SetAttribute("oid", XmlConvert.ToString(Guid.NewGuid())); } if (!bIsRoot) { Debug.Assert(xmlObject.ParentNode != null); Debug.Assert(xmlObject.ParentNode.ParentNode != null); // для вложенного объекта создадим обратное свойство, если его нет, // или проверим, что в нем есть ссылка на родителя, если оно есть. // Массивы и членство в массиве игнорируем. // получим наименование типа родительского объекта sParentTypeName = xmlObject.ParentNode.ParentNode.LocalName; // получим наименование родительского свойства sParentPropName = xmlObject.ParentNode.LocalName; // получим метаданные родительского свойства XPropInfoObject xpropParent = (XPropInfoObject)m_xmodel.GetTypeByName(sParentTypeName).GetProp(sParentPropName); //if (xpropParent != null && xpropParent.Capacity != XPropCapacity.ArrayMembership && xpropParent.Capacity != XPropCapacity.Array) if (xpropParent != null && (xpropParent.Capacity == XPropCapacity.Collection || xpropParent.Capacity == XPropCapacity.CollectionMembership)) { if (xpropParent.ReverseProp != null) { XPropInfoObject xprop = (XPropInfoObject)xpropParent.ReverseProp; // обратное свойство есть - поищем его в текущем объекте xmlProp = (XmlElement)xmlObject.SelectSingleNode(xpropParent.ReverseProp.Name); if (xmlProp == null) { // не нашли - надо создать и поместить туда заглушку родительского объекта xmlProp = xmlObject.OwnerDocument.CreateElement(xprop.Name); xmlProp.AppendChild(XStorageUtils.CreateStubFromObject((XmlElement)xmlObject.ParentNode.ParentNode)); xmlObject.AppendChild(xmlProp); } else if (xmlProp.GetAttribute("loaded").Length > 0 || !xmlProp.HasChildNodes && xmlObject.HasAttribute("new")) { // нашли, но оно помеченно как непрогруженное или пустое нового объекта - удалим его xmlProp.ParentNode.RemoveChild(xmlProp); } else if (!xmlProp.HasChildNodes) { // нашли, но пустое - добавим заглушку родительского объекта xmlProp.AppendChild(XStorageUtils.CreateStubFromObject((XmlElement)xmlObject.ParentNode.ParentNode)); xmlObject.AppendChild(xmlProp); } else { // нашли обратное свойство и оно не пустое- проверим, что в нем есть ссылка на родительский объект // если ссылки нет, то добавим ее string sParentObjectID = xmlObject.ParentNode.ParentNode.Attributes["oid"].Value; if (xmlProp.SelectSingleNode(String.Format("{0}[@oid='{1}']", sParentTypeName, sParentObjectID)) == null) { xmlProp.AppendChild(XStorageUtils.CreateStubFromObject((XmlElement)xmlObject.ParentNode.ParentNode)); } } // пометим свойство специальным атрибутом влияющим на процедуру слияния копий объекта xmlProp.SetAttribute(XObject.MERGE_ACTION_WEAK, "1"); } } } Add(xmlObject, typeInfo); } // по всем объектам (не заглушкам!) в объектных свойствах переданного xml-объекта foreach (XmlElement xmlChildObject in xmlObject.SelectNodes("*/*[*]")) { walkThroughXmlObjects(xmlChildObject, false); } }
private void checkReverseProps() { foreach (XStorageObjectBase xobj in m_objects) { if (xobj is XStorageObjectToSave) { foreach (DictionaryEntry entry in ((XStorageObjectToSave)xobj).GetPropsByCapacity(XPropCapacity.CollectionMembership /*, XPropCapacity.Link, XPropCapacity.LinkScalar*/)) { string sPropName = (string)entry.Key; Guid[] valueOIDs = (Guid[])entry.Value; if (valueOIDs.Length == 0) { continue; } XPropInfoObject propInfo = (XPropInfoObject)xobj.TypeInfo.GetProp(sPropName); bool bError = false; foreach (Guid valueOID in valueOIDs) { XStorageObjectBase xobjValue = (XStorageObjectBase)m_objectsDictionary[valueOID]; if (xobjValue == null) { continue; } // владелец обратного свойства есть в датаграмме if (!xobjValue.Props.Contains(propInfo.ReverseProp.Name)) { continue; } // владелец обратного свойства содержит это обратное свойство object vValue = xobjValue.Props[propInfo.ReverseProp.Name]; Debug.Assert(vValue != null); if (propInfo.ReverseProp is XPropInfoObjectScalar) { if (vValue == DBNull.Value || (Guid)vValue != xobj.ObjectID) { bError = true; } } else { // обратное свойство - коллекция, в свойстве массив гидов Guid[] valueOIDsReverse = (Guid[])vValue; // в этом массиве должна быть ссылка на текущий объект (xobj) bool bFound = false; foreach (Guid valueOIDReverse in valueOIDsReverse) { if (valueOIDReverse == xobj.ObjectID) { bFound = true; break; } } if (!bFound) { bError = true; } } if (bError) { throw new XInvalidXmlForestException( String.Format("Не согласованы свойства объектов: {0}[ID='{1}'], свойство {2} и {3}[ID='{4}'], свойство {5}", xobj.ObjectType, // 0 xobj.ObjectID, // 1 sPropName, // 2 xobjValue.ObjectType, // 3 xobjValue.ObjectID, // 4 propInfo.ReverseProp.Name // 5 ) ); } } } } } }
public DomainPropObjectScalar(DomainObject obj, XPropInfoObject xpropInfo) : base(obj, xpropInfo) { }
private void checkAndSyncReverseProps(DomainObjectDataSet dataSet) { IEnumerator enumerator = dataSet.GetModifiedObjectsEnumerator(true); object vPropValue; while (enumerator.MoveNext()) { DomainObjectData xobj = (DomainObjectData)enumerator.Current; foreach (string sPropName in xobj.UpdatedPropNames) { XPropInfoBase propInfo = xobj.TypeInfo.GetProp(sPropName); Debug.Assert(propInfo != null); if (propInfo.VarType != XPropType.vt_object) { continue; } XPropInfoObject propInfoObj = (XPropInfoObject)propInfo; if (propInfoObj.Capacity == XPropCapacity.CollectionMembership || propInfoObj.Capacity == XPropCapacity.Link) { Guid[] values = (Guid[])xobj.GetUpdatedPropValue(sPropName); if (values.Length > 0) { foreach (Guid valueObjectID in values) { bool bError = false; DomainObjectData xobjValue = dataSet.Find(propInfoObj.ReferedType.Name, valueObjectID); // объект-значение объектного свойства sPropName есть в контексте и он будет сохраняться if (xobjValue != null && xobjValue.HasNewData) { // если текущее свойство "членство в коллекции" и в объекте-значении есть обратное свойство (коллекция), // проверим, что обратное свойство содержит ссылку на текущий объект (xobj) if (propInfoObj.Capacity == XPropCapacity.CollectionMembership && xobjValue.HasUpdatedProp(propInfoObj.ReverseProp.Name)) { Guid[] propRevValues = (Guid[])xobjValue.GetUpdatedPropValue(propInfoObj.ReverseProp.Name); Debug.Assert(propRevValues != null); // если обратное свойство (коллекция) не содержит ссылку на текущий объект - исключение if (Array.IndexOf(propRevValues, xobj.ObjectID) == -1) { bError = true; } } // если текущее свойство линк, то установим обратное свойство - объектный скаляр else if (propInfoObj.Capacity == XPropCapacity.Link) { vPropValue = xobjValue.GetUpdatedPropValue(propInfoObj.ReverseProp.Name); // если обратное свойство (скаляр) установлено, то проверим, что оно ссылается на текущий объект if (vPropValue != null) { Debug.Assert(vPropValue is Guid); if ((vPropValue is DBNull) || ((Guid)vPropValue) != xobj.ObjectID) { bError = true; } } // обратное свойство неустановлено - установим его на текущий объект else { xobjValue.SetUpdatedPropValue(propInfoObj.ReverseProp.Name, xobj.ObjectID); } } if (bError) { throw new XInvalidXmlForestException( String.Format("Не согласованы свойства объектов: {0}[ID='{1}'], свойство {2} и {3}[ID='{4}'], свойство {5}", xobj.ObjectType, // 0 xobj.ObjectID, // 1 sPropName, // 2 xobjValue.ObjectType, // 3 xobjValue.ObjectID, // 4 propInfoObj.ReverseProp.Name // 5 )); } } } } } } } }
/// <summary> /// Добавляет значение в объектное свойство. /// Если объект-значение присутствует в контексте, то запускаем его сериализацию (serializeObject), иначе создаем болванку (тип+идентификатор) /// </summary> /// <param name="dataSet"></param> /// <param name="xmlProp">Текущее объектное свойство (скалярное или массивное)</param> /// <param name="propInfo">Метаданные свойства</param> /// <param name="valueOID">Идентификатор объекта-значения</param> private void addValueIntoObjectProp(DomainObjectDataSet dataSet, XmlElement xmlProp, XPropInfoObject propInfo, Guid valueOID, PreloadsNavigator nav) { XmlElement xmlObjectValue; // xml-представление объект-значение DomainObjectData xobjValue = null; // объект-значение свойства в контексте // теоретически контекста может не быть if (dataSet != null && nav != null) { xobjValue = dataSet.Find(propInfo.ReferedType.Name, valueOID); } if (xobjValue != null && nav != null) { // Объект-значение свойства загружен в контекст и задан навигатор - запустим рекурсивно его сериализацию xmlObjectValue = serializeObject(xobjValue, xmlProp.OwnerDocument, nav); } else { xmlObjectValue = xmlProp.OwnerDocument.CreateElement(propInfo.ReferedType.Name); xmlObjectValue.SetAttribute("oid", valueOID.ToString()); } // добавим объект-значение (буть-то ссылка или полный объект) xmlProp.AppendChild(xmlObjectValue); }
/// <summary> /// Записывает данные объектного массивного свойства /// </summary> /// <param name="xobjOwner">владелец свойства</param> /// <param name="xmlProp">xml-свойство</param> /// <param name="vPropValue">Значение массивного свойства (null или Guid[])</param> /// <param name="nav">навигатор по список прогружаемых свойств</param> private void writeArrayProp(DomainObjectData xobjOwner, XmlElement xmlProp, object vPropValue, XPropInfoObject propInfo, PreloadsNavigator nav) { if (vPropValue == null) { if (!xobjOwner.IsNew) { xmlProp.SetAttribute("loaded", "0"); } } else { // свойство есть и загруженное Guid[] oids = (Guid[])vPropValue; foreach (Guid valueOID in oids) { addValueIntoObjectProp(xobjOwner.Context, xmlProp, propInfo, valueOID, nav); } } }
/// <summary> /// Обновляет кросс-таблицы для массивных свойств (collection, collection-membership, array) заданного объекта /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="disp">диспетчер запросов</param> /// <param name="xobj">ds-Объект</param> protected void updateCrossTablesForObject(XStorageConnection xs, XDbStatementDispatcher disp, XStorageObjectToSave xobj) { string sPropName; // наименование свойства string sDBCrossTableName; // полное наименование кросс-таблицы int nIndex; // значение колонки k string sKeyColumn; // наименование колонки кросс таблицы, по которой будем очищать string sValueColumn; // // по всем свойствам текущего объекта вида: массив, коллекция, членство в коллекции: foreach (DictionaryEntry entry in xobj.GetPropsByCapacity(XPropCapacity.Collection, XPropCapacity.Array, XPropCapacity.CollectionMembership)) { sPropName = (string)entry.Key; XPropInfoObject propInfo = (XPropInfoObject)xobj.TypeInfo.GetProp(sPropName); Debug.Assert(entry.Value is Guid[]); Guid[] values = (Guid[])entry.Value; // сформируем наименование кросс-таблицы по поля, по которому будем очищать кросс-таблицу: sDBCrossTableName = xs.GetTableQName(xobj.SchemaName, xobj.TypeInfo.GetPropCrossTableName(sPropName)); // Сохранение массива: очистим по ObjectID и вставим все значения из свойства if (propInfo.Capacity == XPropCapacity.Array) { StringBuilder cmdBuilder = new StringBuilder(); // если сохраняем массив (array) нового объекта (отложенное обновление), то DELETE выполнять не будем if (!xobj.IsToInsert) { // сформируем необходимый оператор delete на кросс-таблицу: cmdBuilder.AppendFormat( "DELETE FROM {0} WHERE {1}={2}", sDBCrossTableName, // 0 xs.ArrangeSqlName("ObjectID"), // 1 xs.GetParameterName("ObjectID") // 2 ); disp.DispatchStatement( cmdBuilder.ToString(), new XDbParameter[] { xs.CreateCommand().CreateParameter("ObjectID", DbType.Guid, ParameterDirection.Input, false, xobj.ObjectID) }, false ); } nIndex = 0; // для каждого значения массивного свойства добавим INSERT в кросс-таблицу foreach (Guid value in values) { cmdBuilder.Length = 0; cmdBuilder.AppendFormat("INSERT INTO {0} ({1}, {2}, {3}) values ({4}, {5}, {6})", sDBCrossTableName, // 0 xs.ArrangeSqlName("ObjectID"), // 1 xs.ArrangeSqlName("Value"), // 2 xs.ArrangeSqlName("k"), // 3 xs.ArrangeSqlGuid(xobj.ObjectID), // 4 xs.ArrangeSqlGuid(value), // 5 nIndex // 6 ); disp.DispatchStatement(cmdBuilder.ToString(), false); ++nIndex; } } // Сохранение коллекции и членства в коллекции else { if (propInfo.Capacity == XPropCapacity.CollectionMembership) { sKeyColumn = "Value"; sValueColumn = "ObjectID"; } else { sKeyColumn = "ObjectID"; sValueColumn = "Value"; } // сформируем необходимый оператор delete на кросс-таблицу: StringBuilder cmdBuilder = new StringBuilder(); cmdBuilder.AppendFormat( "DELETE FROM {0} WHERE {1}={2}", sDBCrossTableName, // 0 xs.ArrangeSqlName(sKeyColumn), // 1 xs.ArrangeSqlGuid(xobj.ObjectID) // 2 //xs.GetParameterName("ObjectID") // 2 ); // если есть новые значения свойства, то исключим их из удаления if (values.Length > 0) { cmdBuilder.AppendFormat( " AND NOT {0} IN (", xs.ArrangeSqlName(sValueColumn) ); foreach (Guid oid in values) { cmdBuilder.Append(xs.ArrangeSqlGuid(oid)); cmdBuilder.Append(","); } // удалим последнюю запятую cmdBuilder.Length--; cmdBuilder.Append(")"); } disp.DispatchStatement( cmdBuilder.ToString(), //new XDbParameter[] {xs.CreateCommand().CreateParameter("ObjectID", DbType.Guid, ParameterDirection.Input, false, xobj.ObjectID)}, false ); // для каждого значения массивного свойства добавим INSERT в кросс-таблицу foreach (Guid value in values) { cmdBuilder.Length = 0; cmdBuilder.AppendFormat("INSERT INTO {0} ({1}, {2}) SELECT {3}, {4} WHERE NOT EXISTS (SELECT 1 FROM {0} WHERE {1}={3} AND {2}={4})", sDBCrossTableName, // 0 xs.ArrangeSqlName(sKeyColumn), // 1 xs.ArrangeSqlName(sValueColumn), // 2 xs.ArrangeSqlGuid(xobj.ObjectID), // 3 xs.ArrangeSqlGuid(value) // 4 ); disp.DispatchStatement(cmdBuilder.ToString(), false); } } } }
/// <summary> /// Подчищает ссылки объектов, удаленных из линков /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="disp">диспетчер запросов</param> /// <param name="datagram">Множество обрабатываемых объектов</param> protected void purgeLinks(XStorageConnection xs, XDbStatementDispatcher disp, XDatagram datagram) { StringBuilder queryBuilder; // построитель запроса string sTypeName; // наименование типа объекта владельца обратного свойства if (datagram.ObjectsToUpdate.Count == 0) { return; // обновлять нечего } queryBuilder = new StringBuilder(); // по всем объектам из списка обновляемых foreach (XStorageObjectToSave xobj in datagram.ObjectsToUpdate) { foreach (DictionaryEntry entry in xobj.GetPropsByCapacity(XPropCapacity.Link, XPropCapacity.LinkScalar)) { XPropInfoObject propInfo = (XPropInfoObject)xobj.TypeInfo.GetProp((string)entry.Key); Guid[] values = (Guid[])entry.Value; queryBuilder.Length = 0; sTypeName = propInfo.ReferedType.Name; // всем объектам ссылающимся на текущуй объект по обратному относительно текущего свойству // обNULLим ссылку.. queryBuilder.AppendFormat( "UPDATE {0} SET {1} = NULL, {2} = CASE WHEN {2}<{3} THEN {2}+1 ELSE 1 END {5}WHERE {1}={4} ", xs.GetTableQName(xobj.TypeInfo.Schema, sTypeName), // 0 xs.ArrangeSqlName(propInfo.ReverseProp.Name), // 1 xs.ArrangeSqlName("ts"), // 2 Int64.MaxValue, // 3 xs.ArrangeSqlGuid(xobj.ObjectID), // 4 xs.Behavior.SqlNewLine // 5 ); // ...при условии, что идентификаторы этих объектов не упомянуты в свойстве if (values.Length > 0) { // в текущем св-ве есть объекты (их идентификаторы в values) queryBuilder.AppendFormat("AND NOT {0} IN (", xs.ArrangeSqlName("ObjectID")); foreach (Guid value in values) { queryBuilder.AppendFormat("{0},", xs.ArrangeSqlGuid(value)); } // отрежем последнюю запятую queryBuilder.Length--; queryBuilder.Append(") "); } // так же исключим объекты, присутствующие в списке удаляемых, // т.к. при удалении проверяется ts, а формируемый update его увеличивает, да и вообще бессмыселен if (datagram.ObjectsToDelete.Count > 0) { StringBuilder addWhereBuilder = new StringBuilder(); foreach (XStorageObjectToDelete xobjDel in datagram.ObjectsToDelete) { if (xobjDel.ObjectType == sTypeName) { addWhereBuilder.AppendFormat("{0},", xs.ArrangeSqlGuid(xobjDel.ObjectID)); } } if (addWhereBuilder.Length > 0) { // отрежем последнюю запятую addWhereBuilder.Length--; queryBuilder.AppendFormat("AND NOT {0} IN ({1}) ", xs.ArrangeSqlName("ObjectID"), // 0 addWhereBuilder.ToString() // 1 ); } } disp.DispatchStatement(queryBuilder.ToString(), false); } // конец цикла по свойствам объекта xobj } // конец цикла по объектам из списка обновляемых }