/// <summary> /// Обновляет кросс-таблицы для массивных свойств (collection, collection-membership, array) /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="datagram">датаграмма</param> protected void updateCrossTables(XStorageConnection xs, XDatagram datagram) { XDbStatementDispatcher disp = xs.CreateStatementDispatcher(); foreach (XStorageObjectToSave xobj in datagram.ObjectsToInsert) { updateCrossTablesForObject(xs, disp, xobj); } foreach (XStorageObjectToSave xobj in datagram.ObjectsToUpdate) { updateCrossTablesForObject(xs, disp, xobj); } disp.ExecutePendingStatementsAndReturnTotalRowsAffected(); }
/// <summary> /// Вставляет новые объекты /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="disp">диспетчер запросов</param> /// <param name="datagram">Множество обрабатываемых объектов</param> /// <param name="bSuppressMagicBit">признак того, что надо исключить обработку поля MagicBit</param> protected virtual void insertObjects(XStorageConnection xs, XDbStatementDispatcher disp, XDatagram datagram, bool bSuppressMagicBit) { int nIndex; // порядковый индекс объекта XDbCommand cmd; // команда как фабрика для создания параметров // получим упорядоченный список новых объектов упорядоченный по индексу зависимости IList aInsObjects = datagram.ObjectsToInsert; if (aInsObjects.Count == 0) { return; } nIndex = -1; cmd = xs.CreateCommand(); // для каждого объекта создадим заготовку ADO-команды с оператором insert и коллекцией параметров foreach (XStorageObjectToSave xobj in aInsObjects) { insertObject(xs, disp, xobj, cmd, ++nIndex, bSuppressMagicBit); } }
/// <summary> /// Процедура, формирующая операцию UPDATE с использованием данных из временной таблицы /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="disp">XDbStatementDispatcher</param> /// <param name="sTempTableName">Имя временной таблице (уже заэнкоженное)</param> /// <param name="bUseMagicBit">Признак использование "магического бита"</param> /// <param name="sTypeName">Имя типа обновляемой группы объектов</param> /// <param name="sSchemaName">Наименование схемы</param> /// <param name="xmlTypeMD">Метаданные типа</param> protected override void updateWithTempTable( XStorageConnection xs, XDbStatementDispatcher disp, string sTempTableName, bool bUseMagicBit, string sSchemaName, string sTypeName, XmlElement xmlTypeMD) { StringBuilder cmdBuilder; cmdBuilder = new StringBuilder(); cmdBuilder.AppendFormat( "UPDATE {0} SET {1}{2} = CASE WHEN s.{3} ='1' THEN CASE WHEN d.{2}<{3} THEN d.{2}+1 ELSE 1 END ELSE d.{2} END{1}", xs.GetTableQName(sSchemaName, sTypeName), // 0 xs.Behavior.SqlNewLine, // 1 xs.ArrangeSqlName("ts"), // 2 xs.ArrangeSqlName("x_ts"), // 3 Int64.MaxValue // 4 ); // Для каждого скалярного свойства update на значение из временной таблице, если в колонке x_{имя_свойства} лежит 1, иначе само на себя foreach (XmlElement xmlPropMD in xmlTypeMD.SelectNodes("ds:prop[@cp='scalar' and @vt!='bin' and @vt!='text']", xs.MetadataManager.NamespaceManager)) { cmdBuilder.AppendFormat(",[{0}] = CASE WHEN s.[x{0}]='1' THEN s.[c{0}] ELSE d.[{0}] END{1}", xmlPropMD.GetAttribute("n"), // 0 xs.Behavior.SqlNewLine // 1 ); } if (bUseMagicBit) { cmdBuilder.Append(",[MagicBit]=1" + xs.Behavior.SqlNewLine); } cmdBuilder.AppendFormat("FROM {0} d JOIN {1} s ON d.ObjectID = s.ObjectID AND (d.ts = s.ts OR s.ts IS NULL)", xs.GetTableQName(sSchemaName, sTypeName), // 0 sTempTableName // 1 ); disp.DispatchStatement(cmdBuilder.ToString(), true); }
/// <summary> /// Выполняет удаление в БД объектов в списке в порядке их следования в нем /// </summary> /// <param name="xs">Экземпляр XStorageConection</param> /// <param name="disp">Диспетчер запросов</param> /// <param name="aDelObjects">список объектов (типа ObjectToDelete), для которых надо выполнить delete в БД</param> protected virtual int doDelete(XStorageConnection xs, XDbStatementDispatcher disp, ICollection aDelObjects) { int nRowsAffected = 0; // количество удаленных записей bool bForcedMode = false; // признак форсированного удаления (есть хотя бы один объект с незаданным ts) string sTypeNamePrev = String.Empty; // наименование типа предыдущего объекта (в цикле) StringBuilder queryTextBuilder = new StringBuilder(); // построитель оператора delete if (aDelObjects.Count == 0) { return(0); } foreach (XStorageObjectToDelete obj in aDelObjects) { if (obj.TS == -1) { bForcedMode = true; } if (sTypeNamePrev != obj.ObjectType) { // объект нового типа (в т.ч. первый) if (queryTextBuilder.Length > 0) { disp.DispatchStatement(queryTextBuilder.ToString(), true); queryTextBuilder.Length = 0; } queryTextBuilder.AppendFormat("DELETE FROM {0} WHERE {1}={2}", xs.GetTableQName(obj.TypeInfo), // 0 xs.ArrangeSqlName("ObjectID"), // 1 xs.ArrangeSqlGuid(obj.ObjectID) // 2 ); // если для объекта задан TS, добавим условие на него if (obj.AnalyzeTS) { queryTextBuilder.AppendFormat(" AND {0}={1}", xs.ArrangeSqlName("ts"), obj.TS ); } } else { // еще один объект того же типа queryTextBuilder.AppendFormat(" OR {0}={1}", xs.ArrangeSqlName("ObjectID"), // 0 xs.ArrangeSqlGuid(obj.ObjectID) // 1 ); // если для объекта задан TS, добавим условие на него if (obj.AnalyzeTS) { queryTextBuilder.AppendFormat(" AND {0}={1}", xs.ArrangeSqlName("ts"), obj.TS ); } } sTypeNamePrev = obj.ObjectType; } if (queryTextBuilder.Length > 0) { disp.DispatchStatement(queryTextBuilder.ToString(), true); } nRowsAffected = disp.ExecutePendingStatementsAndReturnTotalRowsAffected(); if (!bForcedMode) { if (nRowsAffected != aDelObjects.Count) { // количество удаленных объектов не совпадает с ожидаемым кол-вом. // если в БД остался хотя бы один объект из тех, которые мы удалили, то // это означает, что у него "устарел" ts, следовательно будем ругаться. // Иначе (в БД нет ниодного объект из тех, которые мы удаляли) все хорошо, просто // нас кто-то опередил, но главное результат - все требуемые объекты удалены. sTypeNamePrev = String.Empty; queryTextBuilder = new StringBuilder(); XDbCommand cmd = xs.CreateCommand(); cmd.CommandType = CommandType.Text; foreach (XStorageObjectToDelete obj in aDelObjects) { if (sTypeNamePrev != obj.ObjectType) { // объект нового типа (в т.ч. первый) if (queryTextBuilder.Length > 0) { cmd.CommandText = queryTextBuilder.ToString(); if (Convert.ToInt32(cmd.ExecuteScalar()) > 0) { throw new XOutdatedTimestampException(); } queryTextBuilder.Length = 0; } queryTextBuilder.AppendFormat("SELECT COUNT(1) FROM {0} WHERE {1}={2}", xs.GetTableQName(obj.TypeInfo), // 0 xs.ArrangeSqlName("ObjectID"), // 1 xs.ArrangeSqlGuid(obj.ObjectID) // 2 ); } else { // еще один объект того же типа queryTextBuilder.AppendFormat(" OR {0}={1}", xs.ArrangeSqlName("ObjectID"), // 0 xs.ArrangeSqlGuid(obj.ObjectID) // 1 ); } sTypeNamePrev = obj.ObjectType; } cmd.CommandText = queryTextBuilder.ToString(); if (Convert.ToInt32(cmd.ExecuteScalar()) > 0) { throw new XOutdatedTimestampException(); } } } return(nRowsAffected); }
/// <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 } // конец цикла по объектам из списка обновляемых }
/// <summary> /// Формирует заготовку команды update для переданного объекта. /// Формируется текст ADO-команда, параметры и подсчитывается размер команды. /// Имя параметра устанавливается как имя колонки + "t" + nBatch + "o" + nIndex /// Для всех колонок используются параметры (в отличии от createUpdateCommandForSameTypeObjects). /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="disp">диспетчер запросов</param> /// <param name="xobj">объект, для которого требуется сформировать insert-команду</param> /// <param name="nBatchIndex">индекс группы объектов</param> /// <param name="nIndex">индекс объекта в списке</param> /// <param name="cmd">команда, как фабрика параметров</param> /// <param name="bSuppressMagicBit">признак того, что надо исключить обработку поля MagicBit. /// Если передан false, то в команде insert добавляется поле MagicBit устанавливаемое в 1. /// Если передан true, то в команде insert поле MagicBit не участвует. /// </param> /// <returns>заготовка команды с оператором UPDATE всех объектов из списка, либо null</returns> protected bool updateObject(XStorageConnection xs, XDbStatementDispatcher disp, XStorageObjectToSave xobj, int nBatchIndex, int nIndex, XDbCommand cmd, bool bSuppressMagicBit) { StringBuilder cmdBuilder; // построитель текста одной команды update string sPropName; // наименование свойства, колонки и параметра string sParamName; // наименование параметра команды object vValue; // значение свойства List <XDbParameter> aParameters = new List <XDbParameter>(); // коллекция параметров формируемой команды bool bCmdConstructed = false; // признак того, что команда update сформирована cmdBuilder = new StringBuilder(); cmdBuilder.AppendFormat("UPDATE {0} SET ", xs.GetTableQName(xobj.SchemaName, xobj.ObjectType)); if (xobj.UpdateTS) { cmdBuilder.AppendFormat("{0} = CASE WHEN {0}<{1} THEN {0}+1 ELSE 1 END{2}", xs.ArrangeSqlName("ts"), // 0 Int64.MaxValue, // 1 xs.Behavior.SqlNewLine); // 2 bCmdConstructed = true; } foreach (DictionaryEntry propDesc in xobj.Props) { sPropName = (String)propDesc.Key; vValue = propDesc.Value; XPropInfoBase propInfo = xobj.TypeInfo.GetProp(sPropName); if (!(propInfo is IXPropInfoScalar)) { continue; } if ((propInfo.VarType == XPropType.vt_bin || propInfo.VarType == XPropType.vt_text) /*&& vValue != DBNull.Value */) { continue; } if (bCmdConstructed) { cmdBuilder.Append(","); // текущая колонка уже не первая - добавим запятую } bCmdConstructed = true; xobj.TypeInfo.CheckPropValue(sPropName, propInfo.VarType, vValue); sParamName = xs.GetParameterName(String.Format("{0}t{1}o{2}", sPropName, nBatchIndex, nIndex)); cmdBuilder.Append(xs.ArrangeSqlName(sPropName) + "=" + sParamName + xs.Behavior.SqlNewLine); aParameters.Add(cmd.CreateParameter(sParamName, propInfo.VarType, ParameterDirection.Input, true, vValue)); } if (!bCmdConstructed) { return(false); } // если объект участвует в уникальных индексах и есть объекты в списках на вставку и/или удаление, то // установим MagicBit в 1 для предотвражения нарушения уникальных индексов if (!bSuppressMagicBit && xobj.ParticipateInUniqueIndex) { xobj.MagicBitAffected = true; cmdBuilder.AppendFormat(", {0}=1", xs.ArrangeSqlName("MagicBit")); } // сформируем условие WHERE: (ObjectID={@oid} AND ts={@ts}), // однако условие AND ts={@ts} добавим только если у объекта установлен признак AnalizeTS cmdBuilder.Append(" WHERE "); sParamName = xs.GetParameterName(String.Format("{0}t{1}o{2}", "ObjectID", nBatchIndex, nIndex)); cmdBuilder.AppendFormat("({0}={1}", xs.ArrangeSqlName("ObjectID"), sParamName); aParameters.Add(cmd.CreateParameter(sParamName, DbType.Guid, ParameterDirection.Input, true, xobj.ObjectID)); if (xobj.AnalyzeTS) { sParamName = xs.GetParameterName(String.Format("{0}t{1}o{2}", "ts", nBatchIndex, nIndex)); cmdBuilder.AppendFormat(" AND {0}={1}", xs.ArrangeSqlName("ts"), // 0 sParamName); // 1 aParameters.Add(cmd.CreateParameter(sParamName, DbType.Int64, ParameterDirection.Input, true, xobj.TS)); } cmdBuilder.Append(")"); disp.DispatchStatement(cmdBuilder.ToString(), aParameters, true); return(true); }
/// <summary> /// Формирует заготовку команды insert для переданного объекта. /// Формирутеся текст ADO-команда, параметры и подсчитывается размер команды. /// Имя параметра устанавливается как имя колонки + индекс объекта в общем списке /// </summary> /// <param name="xs">Реализация XStorageConnection</param> /// <param name="disp">диспетчер запросов</param> /// <param name="xobj">объект, для которого требуется сформировать insert-команду</param> /// <param name="cmd">команда</param> /// <param name="nIndex">индекс объекта в списке</param> /// <param name="bSuppressMagicBit">признак того, что надо исключить обработку поля MagicBit. /// Если передан false, то в команде insert добавляется поле MagicBit устанавливаемое в 1. /// Если передан true, то в команде insert поле MagicBit не участвует. /// </param> protected void insertObject(XStorageConnection xs, XDbStatementDispatcher disp, XStorageObjectToSave xobj, XDbCommand cmd, int nIndex, bool bSuppressMagicBit) { StringBuilder queryBuilder = new StringBuilder(); // построитель отдельного insert'a StringBuilder valuesBuilder = new StringBuilder(); // построитель списка значений string sPropName; // наименование свойства, колонки и параметра string sParamName; // наименование параметра команды object vValue; // значение свойства List <XDbParameter> Params = new List <XDbParameter>(); queryBuilder.AppendFormat("INSERT INTO {0} ({1}, {2}", xs.GetTableQName(xobj.SchemaName, xobj.ObjectType), // 0 xs.ArrangeSqlName("ObjectID"), // 1 xs.ArrangeSqlName("ts") // 2 ); // установим значения ObjectID, ts (в качастве ts установим 1) valuesBuilder.Append(xs.ArrangeSqlGuid(xobj.ObjectID) + ",1"); // если не запрещено, то значение MagicBit = 1 if (!bSuppressMagicBit && xobj.ParticipateInUniqueIndex) { queryBuilder.AppendFormat(",{0}", xs.ArrangeSqlName("MagicBit")); xobj.MagicBitAffected = true; valuesBuilder.Append(",1"); } foreach (DictionaryEntry propDesc in xobj.Props) { sPropName = (String)propDesc.Key; vValue = propDesc.Value; if (vValue == null) { continue; } XPropInfoBase propInfo = xobj.TypeInfo.GetProp(sPropName); if (!(propInfo is IXPropInfoScalar)) { continue; } if ((propInfo.VarType == XPropType.vt_bin || propInfo.VarType == XPropType.vt_text) /* && vValue != DBNull.Value*/) { continue; } xobj.TypeInfo.CheckPropValue(sPropName, propInfo.VarType, vValue); if (vValue != DBNull.Value) { queryBuilder.Append(','); queryBuilder.Append(xs.ArrangeSqlName(sPropName)); valuesBuilder.Append(','); if (xs.DangerXmlTypes.ContainsKey(propInfo.VarType)) { // сформируем наименование параметра (без префикса) как имя колонки + "o" + переданный индекс sParamName = xs.GetParameterName(sPropName + "o" + nIndex); Params.Add(cmd.CreateParameter(sParamName, propInfo.VarType, ParameterDirection.Input, false, vValue)); valuesBuilder.Append(sParamName); } else { valuesBuilder.Append(xs.ArrangeSqlValue(vValue, propInfo.VarType)); } } } // сформируем команду и добавим ее в общий батч queryBuilder.AppendFormat(") values ({0})", valuesBuilder.ToString()); disp.DispatchStatement(queryBuilder.ToString(), Params, false); }