/// <summary>
        /// Обновить хранилище по объектам (есть параметр, указывающий, всегда ли необходимо взводить ошибку
        /// и откатывать транзакцию при неудачном запросе в базу данных). Если
        /// он true, всегда взводится ошибка. Иначе, выполнение продолжается.
        /// Однако, при этом есть опасность преждевременного окончания транзакции, с переходом для остальных
        /// запросов режима транзакционности в autocommit. Проявлением проблемы являются ошибки навроде:
        /// The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION
        /// </summary>
        /// <param name="objects">Объекты для обновления.</param>
        /// <param name="DataObjectCache">Кэш объектов данных.</param>
        /// <param name="AlwaysThrowException">Если произошла ошибка в базе данных, не пытаться выполнять других запросов, сразу взводить ошибку и откатывать транзакцию.</param>
        public virtual void UpdateObjects(ref DataObject[] objects, DataObjectCache DataObjectCache, bool AlwaysThrowException)
        {
            object id = BusinessTaskMonitor.BeginTask("Update objects");

            if (!DoNotChangeCustomizationString && ChangeCustomizationString != null)
            {
                var tps = new List <Type>();
                foreach (DataObject d in objects)
                {
                    Type t = d.GetType();
                    if (!tps.Contains(t))
                    {
                        tps.Add(t);
                    }
                }

                string cs = ChangeCustomizationString(tps.ToArray());
                customizationString = string.IsNullOrEmpty(cs) ? customizationString : cs;
            }

            // Перенесли этот метод повыше, потому что строка соединения может быть сменена в бизнес-сервере делегатом смены строки соединения (если что-нибудь почитают).
            IDbConnection conection = GetConnection();

            var DeleteQueries      = new StringCollection();
            var UpdateQueries      = new StringCollection();
            var UpdateFirstQueries = new StringCollection();
            var InsertQueries      = new StringCollection();

            var DeleteTables    = new StringCollection();
            var UpdateTables    = new StringCollection();
            var InsertTables    = new StringCollection();
            var TableOperations = new SortedList();
            var QueryOrder      = new StringCollection();

            var AllQueriedObjects = new ArrayList();

            var auditOperationInfoList = new List <AuditAdditionalInfo>();
            var extraProcessingList    = new List <DataObject>();

            GenerateQueriesForUpdateObjects(DeleteQueries, DeleteTables, UpdateQueries, UpdateFirstQueries, UpdateTables, InsertQueries, InsertTables, TableOperations, QueryOrder, true, AllQueriedObjects, DataObjectCache, extraProcessingList, objects);

            GenerateAuditForAggregators(AllQueriedObjects, DataObjectCache, ref extraProcessingList);

            OnBeforeUpdateObjects(AllQueriedObjects);

            Exception ex = null;

            /*access checks*/

            foreach (DataObject dtob in AllQueriedObjects)
            {
                Type dobjType = dtob.GetType();
                if (!SecurityManager.AccessObjectCheck(dobjType, tTypeAccess.Full, false))
                {
                    switch (dtob.GetStatus(false))
                    {
                    case ObjectStatus.Created:
                        SecurityManager.AccessObjectCheck(dobjType, tTypeAccess.Insert, true);
                        break;

                    case ObjectStatus.Altered:
                        SecurityManager.AccessObjectCheck(dobjType, tTypeAccess.Update, true);
                        break;

                    case ObjectStatus.Deleted:
                        SecurityManager.AccessObjectCheck(dobjType, tTypeAccess.Delete, true);
                        break;
                    }
                }
            }

            /*access checks*/

            if (DeleteQueries.Count > 0 || UpdateQueries.Count > 0 || InsertQueries.Count > 0)
            { // Порядок выполнения запросов: delete,insert,update
                if (AuditService.IsAuditEnabled)
                {
                    /* Аудит проводится именно здесь, поскольку на этот момент все бизнес-сервера на объектах уже выполнились,
                     * объекты находятся именно в том состоянии, в каком должны были пойти в базу + в будущем можно транзакцию передать на исполнение
                     */
                    AuditOperation(extraProcessingList, auditOperationInfoList); // TODO: подумать, как записывать аудит до OnBeforeUpdateObjects, но уже потенциально с транзакцией
                }

                conection.Open();
                IDbTransaction trans = null;

                string query       = string.Empty;
                string prevQueries = string.Empty;
                object subTask     = null;
                try
                {
                    trans = CreateTransaction(conection);
                    IDbCommand command = conection.CreateCommand();
                    command.Transaction = trans;

                    #region прошли вглубь обрабатывая only Update||Insert
                    bool go = true;
                    do
                    {
                        string table = QueryOrder[0];
                        if (!TableOperations.ContainsKey(table))
                        {
                            TableOperations.Add(table, OperationType.None);
                        }
                        var ops = (OperationType)TableOperations[table];

                        if ((ops & OperationType.Delete) != OperationType.Delete)
                        {
                            // Смотрим есть ли Инсерты
                            if ((ops & OperationType.Insert) == OperationType.Insert)
                            {
                                if (
                                    (ex =
                                         RunCommands(InsertQueries, InsertTables, table, command, id, AlwaysThrowException))
                                    == null)
                                {
                                    ops = Minus(ops, OperationType.Insert);
                                    TableOperations[table] = ops;
                                }
                                else
                                {
                                    go = false;
                                }
                            }

                            // Смотрим есть ли Update
                            if (go && ((ops & OperationType.Update) == OperationType.Update))
                            {
                                if ((ex = RunCommands(UpdateQueries, UpdateTables, table, command, id, AlwaysThrowException)) == null)
                                {
                                    ops = Minus(ops, OperationType.Update);
                                    TableOperations[table] = ops;
                                }
                                else
                                {
                                    go = false;
                                }
                            }

                            if (go)
                            {
                                QueryOrder.RemoveAt(0);
                                go = QueryOrder.Count > 0;
                            }
                        }
                        else
                        {
                            go = false;
                        }
                    }while (go);

                    #endregion
                    if (QueryOrder.Count > 0)
                    {
                        #region сзади чистые Update

                        go = true;
                        int queryOrderIndex = QueryOrder.Count - 1;
                        do
                        {
                            string table = QueryOrder[queryOrderIndex];
                            if (TableOperations.ContainsKey(table))
                            {
                                var ops = (OperationType)TableOperations[table];

                                if (ops == OperationType.Update)
                                {
                                    if (
                                        (ex = RunCommands(UpdateQueries, UpdateTables, table, command, id, AlwaysThrowException)) == null)
                                    {
                                        ops = Minus(ops, OperationType.Update);
                                        TableOperations[table] = ops;
                                    }
                                    else
                                    {
                                        go = false;
                                    }

                                    if (go)
                                    {
                                        queryOrderIndex--;
                                        go = queryOrderIndex >= 0;
                                    }
                                }
                                else
                                {
                                    go = false;
                                }
                            }
                            else
                            {
                                queryOrderIndex--;
                            }
                        }while (go);

                        #endregion
                    }

                    foreach (string table in QueryOrder)
                    {
                        if ((ex = RunCommands(UpdateFirstQueries, UpdateTables, table, command, id, AlwaysThrowException)) != null)
                        {
                            throw ex;
                        }
                    }

                    // Удаляем в обратном порядке.
                    for (int i = QueryOrder.Count - 1; i >= 0; i--)
                    {
                        string table = QueryOrder[i];
                        if ((ex = RunCommands(DeleteQueries, DeleteTables, table, command, id, AlwaysThrowException)) != null)
                        {
                            throw ex;
                        }
                    }

                    // А теперь опять с начала
                    foreach (string table in QueryOrder)
                    {
                        if ((ex = RunCommands(InsertQueries, InsertTables, table, command, id, AlwaysThrowException)) != null)
                        {
                            throw ex;
                        }

                        if ((ex = RunCommands(UpdateQueries, UpdateTables, table, command, id, AlwaysThrowException)) != null)
                        {
                            throw ex;
                        }
                    }

                    if (AuditService.IsAuditEnabled && auditOperationInfoList.Count > 0)
                    { // Нужно зафиксировать операции аудита (то есть сообщить, что всё было корректно выполнено и запомнить время)
                        AuditService.RatifyAuditOperationWithAutoFields(
                            tExecutionVariant.Executed,
                            AuditAdditionalInfo.SetNewFieldValuesForList(trans, this, auditOperationInfoList),
                            this,
                            true);
                    }

                    if (trans != null)
                    {
                        trans.Commit();
                    }
                }
                catch (Exception excpt)
                {
                    if (trans != null)
                    {
                        trans.Rollback();
                    }
                    if (AuditService.IsAuditEnabled && auditOperationInfoList.Count > 0)
                    { // Нужно зафиксировать операции аудита (то есть сообщить, что всё было откачено)
                        AuditService.RatifyAuditOperationWithAutoFields(tExecutionVariant.Failed, auditOperationInfoList, this, false);
                    }

                    conection.Close();
                    BusinessTaskMonitor.EndSubTask(subTask);
                    throw new ExecutingQueryException(query, prevQueries, excpt);
                }

                conection.Close();

                var res = new ArrayList();
                foreach (DataObject changedObject in objects)
                {
                    changedObject.ClearPrototyping(true);

                    if (changedObject.GetStatus(false) != STORMDO.ObjectStatus.Deleted)
                    {
                        Utils.UpdateInternalDataInObjects(changedObject, true, DataObjectCache);
                        res.Add(changedObject);
                    }
                }

                foreach (DataObject dobj in AllQueriedObjects)
                {
                    if (dobj.GetStatus(false) != STORMDO.ObjectStatus.Deleted &&
                        dobj.GetStatus(false) != STORMDO.ObjectStatus.UnAltered)
                    {
                        Utils.UpdateInternalDataInObjects(dobj, true, DataObjectCache);
                    }
                }

                objects = new DataObject[res.Count];
                res.CopyTo(objects);
                BusinessTaskMonitor.EndTask(id);
            }

            if (AfterUpdateObjects != null)
            {
                AfterUpdateObjects(this, new DataObjectsEventArgs(objects));
            }
        }