diff --git a/NewPlatform.Flexberry.ORM.ODataService.nuspec b/NewPlatform.Flexberry.ORM.ODataService.nuspec index 40f52d72..3fcf28b3 100644 --- a/NewPlatform.Flexberry.ORM.ODataService.nuspec +++ b/NewPlatform.Flexberry.ORM.ODataService.nuspec @@ -1,115 +1,115 @@ - - - - NewPlatform.Flexberry.ORM.ODataService - 7.2.0-beta02 - Flexberry ORM ODataService - New Platform Ltd. - New Platform Ltd. - http://flexberry.ru/License-FlexberryOrm-Runtime - https://flexberry.net/ru/developers-flexberry-orm.html - https://flexberry.net/img/logo-color.png - true - Flexberry ORM OData Service Package. - - Added - 1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). - - Changed - 1. Updated Flexberry ORM up to 7.2.0-beta01. - - Fixed - 1. Fixed loading of object with crushing of already loaded masters. - 2. Fixed loading of details. - - Copyright New Platform Ltd 2023 - Flexberry ORM OData ODataService - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + NewPlatform.Flexberry.ORM.ODataService + 7.2.0-beta02 + Flexberry ORM ODataService + New Platform Ltd. + New Platform Ltd. + http://flexberry.ru/License-FlexberryOrm-Runtime + https://flexberry.net/ru/developers-flexberry-orm.html + https://flexberry.net/img/logo-color.png + true + Flexberry ORM OData Service Package. + + Added + 1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). + + Changed + 1. Updated Flexberry ORM up to 7.2.0-beta01. + + Fixed + 1. Fixed loading of object with crushing of already loaded masters. + 2. Fixed loading of details. + + Copyright New Platform Ltd 2023 + Flexberry ORM OData ODataService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index b9bfc550..bc17d0e4 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -23,13 +23,13 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers #if NETFRAMEWORK using System.Net.Http.Formatting; + using System.Web; using System.Web.Http; using System.Web.Http.Results; using System.Web.Http.Validation; + using Newtonsoft.Json.Linq; using NewPlatform.Flexberry.ORM.ODataService.Events; using NewPlatform.Flexberry.ORM.ODataService.Handlers; - using Newtonsoft.Json.Linq; - using System.Web; #endif #if NETSTANDARD using Microsoft.AspNet.OData.Formatter; @@ -860,6 +860,11 @@ private static void AddObjectToUpdate(List objsToUpdate, DataObject /// Значение ключа. private object GetKey(EdmEntityObject edmEntity) { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(value), $"{nameof(edmEntity)} can not be null."); + } + object key; // Получим значение ключа. @@ -878,6 +883,11 @@ private object GetKey(EdmEntityObject edmEntity) /// Объект данных. private DataObject GetDataObjectByEdmEntityLight(EdmEntityObject edmEntity) { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(value), $"{nameof(edmEntity)} can not be null."); + } + var masterType = _model.GetDataObjectType(edmEntity); var masterKey = GetKey(edmEntity); var dataObject = (DataObject)Activator.CreateInstance(masterType); diff --git a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs index 427dbf86..a299aa6c 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs @@ -1,419 +1,419 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Extensions -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET.Business.LINQProvider; - using ICSSoft.STORMNET.FunctionalLanguage; - using ICSSoft.STORMNET.FunctionalLanguage.SQLWhere; - - /// - /// Класс, содержащий расширения для сервиса данных. - /// - public static class DataServiceExtensions - { - /// - /// Осуществляет вычитку объектов данных заданного типа по заданному представлению и LINQ-выражению. - /// - /// Экземпляр сервиса данных. - /// Тип вычитываемых объектов данных, наследуемый от . - /// Представления для вычитки объектов данных. - /// LINQ-выражение, ограничивающее набор вычитываемых объектов. - /// Коллекция вычитанных объектов. - public static object Execute(this SQLDataService dataService, Type dataObjectType, View dataObjectView, Expression expression) - { - IQueryProvider queryProvider = typeof(LcsQueryProvider<,>) - .MakeGenericType(dataObjectType, typeof(LcsGeneratorQueryModelVisitor)) - .GetConstructor(new Type[3] { typeof(SQLDataService), typeof(View), typeof(IEnumerable) }) - .Invoke(new object[3] { dataService, dataObjectView, null }) as IQueryProvider; - - return queryProvider.Execute(expression); - } - - /// - /// Загрузка объекта с его мастерами (объект должен быть не изменнённый и не до конца загруженный). - /// С мастерами необходимо обращаться аккуратно: если в кэше уже есть мастер, то нужно эту ситуацию разрешить, - /// поскольку иначе стандартная загрузка перетрёт данные мастера в кэше (и если он там изменён, то все изменения будут потеряны). - /// - /// Экземпляр сервиса данных. - /// Представление объекта с мастерами. - /// Объект данных, в который будет производиться загрузка. - /// Текущий кэш объектов данных (в данном кэше ранее существующие там объекты не должны быть перетёрты). - public static void SafeLoadWithMasters( - this IDataService dataService, View view, ICSSoft.STORMNET.DataObject dobjectFromCache, DataObjectCache dataObjectCache) - { - if (dataService == null) - { - throw new ArgumentNullException(nameof(dataService)); - } - - if (view == null) - { - throw new ArgumentNullException(nameof(view)); - } - - if (dobjectFromCache == null) - { - throw new ArgumentNullException(nameof(dobjectFromCache)); - } - - if (dataObjectCache == null) - { - throw new ArgumentNullException(nameof(dataObjectCache)); - } - - // Прогружается пустой объект, чтобы избежать риска перетирания основного. - DataObject createdObject = (DataObject)Activator.CreateInstance(dobjectFromCache.GetType()); - createdObject.SetExistObjectPrimaryKey(dobjectFromCache.__PrimaryKey); - - // Используется отдельный кэш, чтобы не перетереть данные в основном кэше. - DataObjectCache specialCache = new DataObjectCache(); - specialCache.StartCaching(false); - specialCache.AddDataObject(createdObject); - dataService.LoadObject(view, createdObject, false, true, specialCache); - specialCache.StopCaching(); - - // Перенос данных из одного объекта в другой. - ProperUpdateOfObject(dobjectFromCache, createdObject, dataObjectCache, specialCache); - } - - /// - /// Загрузка детейлов с сохранением состояния изменения. - /// - /// Экземпляр сервиса данных. - /// Представление агрегатора с детейлами. Чтение будет идти по массиву Details представлений детейлов, включая детейлы второго и последующих уровней. - /// Агрегаторы, для которых дочитываются детейлы. - /// Кеш объектов данных, в нём хранятся состояния детейлов, которые не надо затирать. В него же будут добавлены новые зачитанные детейлы. - public static void SafeLoadDetails(this IDataService dataService, View view, IList agregators, DataObjectCache dataObjectCache) - { - if (dataService == null) - { - throw new ArgumentNullException(nameof(dataService)); - } - - if (view == null) - { - throw new ArgumentNullException(nameof(view)); - } - - if (agregators == null) - { - throw new ArgumentNullException(nameof(agregators)); - } - - if (dataObjectCache == null) - { - throw new ArgumentNullException(nameof(dataObjectCache)); - } - - if (agregators.Count == 0) - { - return; - } - - // Обрабатываем все детейлы, которые указаны в представлении. - DetailInView[] detailsInView = view.Details; - foreach (DetailInView detailInView in detailsInView) - { - // Оригинальное представление будет использоваться в рекурсии. - View detailView = detailInView.View.Clone(); - DetailInView[] secondDetailsInView = detailView.Details; - - // Удалим из представления детейлы второго уровня, поскольку их обработка будет рекурсивной с аналогичной логикой. - foreach (DetailInView secondDetailInView in secondDetailsInView) - { - detailView.RemoveDetail(secondDetailInView.Name); - } - - Type detailType = detailView.DefineClassType; - string agregatorPropertyName = Information.GetAgregatePropertyName(detailType); - - // Нужно гарантировать, что у загруженных детейлов будет проставлена ссылка на агрегатора, поэтому добавим свойство в представление (если уже есть, то второй раз не добавится). - detailView.AddProperty(agregatorPropertyName); - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(detailView.DefineClassType, detailView); - Type[] usageTypes = TypeUsageProvider.TypeUsage.GetUsageTypes(view.DefineClassType, detailInView.Name); - List storedTypes = new List(); - foreach (var usageType in usageTypes) - { - if (Information.IsStoredType(usageType)) - { - storedTypes.Add(usageType); - } - } - - if (!storedTypes.Any()) - { - foreach (DataObject agregator in agregators) - { - Type agregatorType = agregator.GetType(); - Type[] usageTypesFromAgregator = TypeUsageProvider.TypeUsage.GetUsageTypes(agregatorType, detailInView.Name); - foreach (Type usageTypeFromAgregator in usageTypesFromAgregator) - { - if (Information.IsStoredType(usageTypeFromAgregator) && !storedTypes.Contains(usageTypeFromAgregator)) - { - storedTypes.Add(usageTypeFromAgregator); - } - } - } - } - - lcs.LoadingTypes = storedTypes.ToArray(); - Type agregatorKeyType = agregators[0].__PrimaryKey.GetType(); - - // Производим обработку только тех агрегаторов, для которых ранее не был загружен детейл. - object[] keys = agregators.Where(a => !a.CheckLoadedProperty(detailInView.Name)).Select(d => d.__PrimaryKey).ToArray(); - lcs.LimitFunction = FunctionBuilder.BuildIn(agregatorPropertyName, SQLWhereLanguageDef.LanguageDef.GetObjectTypeForNetType(agregatorKeyType), keys); - - // Нужно соблюсти единственность инстанций агрегаторов при вычитке, поэтому реализуем отдельный кеш. Смешивать с кешем dataObjectCache нельзя, поскольку в предстоящей выборке будут те же самые детейлы (значения в кеше затрутся). - // Агрегаторы в кэш не помещаем. От помещения агрегаторов в кэш возникают неконтролируемые сбои основного кэша. - DataObjectCache agregatorCache = new DataObjectCache(); - agregatorCache.StartCaching(false); - - // Вычитываются детейлы одного типа, но для нескольких инстанций агрегаторов (оптимизируем количество SQL-запросов). - DataObject[] loadedDetails = dataService.LoadObjects(lcs, agregatorCache); - agregatorCache.StopCaching(); - - Dictionary extraCacheForAgregators = new Dictionary(); - foreach (DataObject agregator in agregators) - { - agregator.AddLoadedProperties(detailInView.Name); - extraCacheForAgregators.Add(agregator.__PrimaryKey, agregator); - } - - // Ввиду того, что агрегаторы нам пришли готовые с пустыми коллекциями детейлов, заполняем детейлы по агрегаторам значениями из кеша или из базы. - // На загрузку детейлов второго уровня передаем только детейлы отсутствующие в кэше. - List toLoadSecondDetails = new List(); - foreach (DataObject loadedDetail in loadedDetails) - { - DataObject agregatorTemp = (DataObject)Information.GetPropValueByName(loadedDetail, agregatorPropertyName); - DataObject agregator = extraCacheForAgregators[agregatorTemp.__PrimaryKey]; - object detailPrimaryKey = loadedDetail.__PrimaryKey; - - DataObject detailFromCache = dataObjectCache.GetLivingDataObject(loadedDetail.GetType(), detailPrimaryKey); - - // Необходимо игнорировать объекты-пустышки, которые проинициализированы только первичным ключом. - bool detailFromCacheIsEmpty = detailFromCache == null || !detailFromCache.GetLoadedProperties().Any(); - DataObject detailForAdd = detailFromCacheIsEmpty - ? loadedDetail - : detailFromCache; - - agregator.AddDetail(detailForAdd); - - DataObject agregatorDataCopy = agregator.GetDataCopy(); - DataObject detailDataCopy = detailForAdd.GetDataCopy(); - - if (agregatorDataCopy != null && detailDataCopy != null) - { - agregatorDataCopy.AddDetail(detailDataCopy); - } - - if (detailFromCacheIsEmpty) - { - dataObjectCache.AddDataObject(loadedDetail); - toLoadSecondDetails.Add(loadedDetail); - } - } - - // Детейлы второго и последующих уровней нужно обработать аналогичным образом. - if (toLoadSecondDetails.Any() && secondDetailsInView.Any()) - { - dataService.SafeLoadDetails(detailInView.View, toLoadSecondDetails, dataObjectCache); - } - } - } - - /// - /// Догрузка объекта по указанному представлению, с загрузкой детейлов с сохранением состояния изменения. - /// - /// Экземпляр сервиса данных. - /// Объект данных, который нужно догрузить. - /// Представление, которое используется для догрузки. - /// Кеш объектов данных. - public static void SafeLoadObject(this IDataService dataService, DataObject dataObject, View view, DataObjectCache dataObjectCache) - { - if (dataService == null) - { - throw new ArgumentNullException(nameof(dataService)); - } - - if (view == null) - { - throw new ArgumentNullException(nameof(view)); - } - - // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказать отдельные операции с детейлами и перевычитка затрёт эти изменения. - View miniView = view.Clone(); - DetailInView[] miniViewDetails = miniView.Details; - miniView.Details = new DetailInView[0]; - dataService.LoadObject(miniView, dataObject, false, true, dataObjectCache); - - if (miniViewDetails.Length > 0) - { - dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, dataObjectCache); - } - } - - /// - /// Add detail object to agregator according detail type. - /// - /// Agregator object. - /// Detail object. - public static void AddDetail(this DataObject agregator, DataObject detail) - { - if (agregator == null) - { - throw new ArgumentNullException(nameof(agregator)); - } - - if (detail == null) - { - throw new ArgumentNullException(nameof(detail)); - } - - Type agregatorType = agregator.GetType(); - Type detailType = detail.GetType(); - string detailPropertyName = Information.GetDetailArrayPropertyName(agregatorType, detailType); - - Type parentType = detailType.BaseType; - while (detailPropertyName == null && parentType != typeof(DataObject) && parentType != typeof(object) && parentType != null) - { - detailPropertyName = Information.GetDetailArrayPropertyName(agregatorType, parentType); - parentType = parentType.BaseType; - } - - if (detailPropertyName != null) - { - DetailArray details = (DetailArray)Information.GetPropValueByName(agregator, detailPropertyName); - - if (details != null) - { - DataObject existDetail = details.GetByKey(detail.__PrimaryKey); - - if (existDetail == null) - { - details.AddObject(detail); - } - } - } - else - { - LogService.LogWarn($"Detail type {detailType.AssemblyQualifiedName} not found in agregator of type {agregatorType.AssemblyQualifiedName}."); - } - } - - - /// - /// Перенос означенных свойств из свежезагруженного объекта в основной, расположенный в основном кэше. - /// - /// Основной объект, куда необходимо копировать значения свойств. - /// Свежезагруженный объект. - /// Основной кэш. - /// Локальный кэш, куда была выполнена свежая прогрузка. - private static void ProperUpdateOfObject(DataObject currentObject, DataObject loadedObjectLocal, DataObjectCache dataObjectCache, DataObjectCache dataObjectCacheLocal) - { - if (currentObject == null) - { - throw new ArgumentNullException(nameof(currentObject)); - } - - if (loadedObjectLocal == null) - { - throw new ArgumentNullException(nameof(loadedObjectLocal)); - } - - if (dataObjectCache == null) - { - throw new ArgumentNullException(nameof(dataObjectCache)); - } - - if (dataObjectCacheLocal == null) - { - throw new ArgumentNullException(nameof(dataObjectCacheLocal)); - } - - // Перенос значений свойств объекта (в том числе могут быть мастера). Если мастера означены, то перенос свойств мастера производится далее. - List localObjectLoadedProps = loadedObjectLocal.GetLoadedPropertiesList(); - List currentObjectLoadedProps = currentObject.GetLoadedPropertiesList(); - List notLoadedForActual = localObjectLoadedProps.Except(currentObjectLoadedProps).ToList(); - DataObject currentDataCopy = currentObject.GetDataCopy(); - foreach (string notLoadedPropName in notLoadedForActual) - { - object propValue = Information.GetPropValueByName(loadedObjectLocal, notLoadedPropName); - Information.SetPropValueByName(currentObject, notLoadedPropName, propValue); - currentObject.AddLoadedProperties(notLoadedPropName); - Information.SetPropValueByName(currentDataCopy, notLoadedPropName, propValue); - } - - // Ещё могут быть частично загруженные мастера. - ProperCacheUpdateForOneObject(dataObjectCache, dataObjectCacheLocal, loadedObjectLocal, true); - } - - /// - /// Обновление кэша по свежезагруженному объекту. - /// - /// Текущий основной кэш объектов. - /// Вспомогательный кэш, куда загружался объект. - /// Свежезагруженный объект, по которому обновляется основной кэш. - /// Флаг, определяющий, что в кэш уже добавлен свежезагруженный объект. - private static void ProperCacheUpdateForOneObject(DataObjectCache dataObjectCacheActual, DataObjectCache dataObjectCacheWithMasters, DataObject loadedDataObject, bool loadedObjectsAdded) - { - if (dataObjectCacheActual == null) - { - throw new ArgumentNullException(nameof(dataObjectCacheActual)); - } - - if (dataObjectCacheWithMasters == null) - { - throw new ArgumentNullException(nameof(dataObjectCacheWithMasters)); - } - - if (loadedDataObject == null) - { - return; - } - - if (!loadedObjectsAdded) - { - dataObjectCacheActual.AddDataObject(loadedDataObject); - } - - Type dobjType = typeof(DataObject); - Type currentType = loadedDataObject.GetType(); - List loadedProperties = loadedDataObject.GetLoadedPropertiesList(); - foreach (string currentPropertyName in loadedProperties) - { - Type currentPropertyType = Information.GetPropertyType(currentType, currentPropertyName); - if (currentPropertyType.IsSubclassOf(dobjType)) // Выбираем у текущего объекта ссылки на мастеров. - { - DataObject currentMaster = (DataObject)Information.GetPropValueByName(loadedDataObject, currentPropertyName); - if (currentMaster != null) - { - // Типы currentPropertyType и currentMaster.GetType() могут различаться из-за наследования. - DataObject masterFromActualCache = dataObjectCacheActual.GetLivingDataObject(currentMaster.GetType(), currentMaster.__PrimaryKey); - - if (masterFromActualCache == null) - { - // Если мастера ранее не было в кэше, то просто его туда переносим. - dataObjectCacheActual.AddDataObject(currentMaster); - - // Но в добавленном мастере могут быть мастера 2 и далее уровней. - ProperCacheUpdateForOneObject(dataObjectCacheActual, dataObjectCacheWithMasters, currentMaster, true); - } - else - { // Если мастер был в кэше, то аккуратно нужно перенести только незагруженные ранее свойства. - if (masterFromActualCache.GetStatus(false) == ObjectStatus.UnAltered && masterFromActualCache.GetLoadingState() != LoadingState.Loaded) - { - ProperUpdateOfObject(masterFromActualCache, currentMaster, dataObjectCacheActual, dataObjectCacheWithMasters); - } - } - } - } - } - } - - } -} +namespace NewPlatform.Flexberry.ORM.ODataService.Extensions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET.Business.LINQProvider; + using ICSSoft.STORMNET.FunctionalLanguage; + using ICSSoft.STORMNET.FunctionalLanguage.SQLWhere; + + /// + /// Класс, содержащий расширения для сервиса данных. + /// + public static class DataServiceExtensions + { + /// + /// Осуществляет вычитку объектов данных заданного типа по заданному представлению и LINQ-выражению. + /// + /// Экземпляр сервиса данных. + /// Тип вычитываемых объектов данных, наследуемый от . + /// Представления для вычитки объектов данных. + /// LINQ-выражение, ограничивающее набор вычитываемых объектов. + /// Коллекция вычитанных объектов. + public static object Execute(this SQLDataService dataService, Type dataObjectType, View dataObjectView, Expression expression) + { + IQueryProvider queryProvider = typeof(LcsQueryProvider<,>) + .MakeGenericType(dataObjectType, typeof(LcsGeneratorQueryModelVisitor)) + .GetConstructor(new Type[3] { typeof(SQLDataService), typeof(View), typeof(IEnumerable) }) + .Invoke(new object[3] { dataService, dataObjectView, null }) as IQueryProvider; + + return queryProvider.Execute(expression); + } + + /// + /// Загрузка объекта с его мастерами (объект должен быть не изменнённый и не до конца загруженный). + /// С мастерами необходимо обращаться аккуратно: если в кэше уже есть мастер, то нужно эту ситуацию разрешить, + /// поскольку иначе стандартная загрузка перетрёт данные мастера в кэше (и если он там изменён, то все изменения будут потеряны). + /// + /// Экземпляр сервиса данных. + /// Представление объекта с мастерами. + /// Объект данных, в который будет производиться загрузка. + /// Текущий кэш объектов данных (в данном кэше ранее существующие там объекты не должны быть перетёрты). + public static void SafeLoadWithMasters( + this IDataService dataService, View view, ICSSoft.STORMNET.DataObject dobjectFromCache, DataObjectCache dataObjectCache) + { + if (dataService == null) + { + throw new ArgumentNullException(nameof(dataService)); + } + + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } + + if (dobjectFromCache == null) + { + throw new ArgumentNullException(nameof(dobjectFromCache)); + } + + if (dataObjectCache == null) + { + throw new ArgumentNullException(nameof(dataObjectCache)); + } + + // Прогружается пустой объект, чтобы избежать риска перетирания основного. + DataObject createdObject = (DataObject)Activator.CreateInstance(dobjectFromCache.GetType()); + createdObject.SetExistObjectPrimaryKey(dobjectFromCache.__PrimaryKey); + + // Используется отдельный кэш, чтобы не перетереть данные в основном кэше. + DataObjectCache specialCache = new DataObjectCache(); + specialCache.StartCaching(false); + specialCache.AddDataObject(createdObject); + dataService.LoadObject(view, createdObject, false, true, specialCache); + specialCache.StopCaching(); + + // Перенос данных из одного объекта в другой. + ProperUpdateOfObject(dobjectFromCache, createdObject, dataObjectCache, specialCache); + } + + /// + /// Загрузка детейлов с сохранением состояния изменения. + /// + /// Экземпляр сервиса данных. + /// Представление агрегатора с детейлами. Чтение будет идти по массиву Details представлений детейлов, включая детейлы второго и последующих уровней. + /// Агрегаторы, для которых дочитываются детейлы. + /// Кеш объектов данных, в нём хранятся состояния детейлов, которые не надо затирать. В него же будут добавлены новые зачитанные детейлы. + public static void SafeLoadDetails(this IDataService dataService, View view, IList agregators, DataObjectCache dataObjectCache) + { + if (dataService == null) + { + throw new ArgumentNullException(nameof(dataService)); + } + + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } + + if (agregators == null) + { + throw new ArgumentNullException(nameof(agregators)); + } + + if (dataObjectCache == null) + { + throw new ArgumentNullException(nameof(dataObjectCache)); + } + + if (agregators.Count == 0) + { + return; + } + + // Обрабатываем все детейлы, которые указаны в представлении. + DetailInView[] detailsInView = view.Details; + foreach (DetailInView detailInView in detailsInView) + { + // Оригинальное представление будет использоваться в рекурсии. + View detailView = detailInView.View.Clone(); + DetailInView[] secondDetailsInView = detailView.Details; + + // Удалим из представления детейлы второго уровня, поскольку их обработка будет рекурсивной с аналогичной логикой. + foreach (DetailInView secondDetailInView in secondDetailsInView) + { + detailView.RemoveDetail(secondDetailInView.Name); + } + + Type detailType = detailView.DefineClassType; + string agregatorPropertyName = Information.GetAgregatePropertyName(detailType); + + // Нужно гарантировать, что у загруженных детейлов будет проставлена ссылка на агрегатора, поэтому добавим свойство в представление (если уже есть, то второй раз не добавится). + detailView.AddProperty(agregatorPropertyName); + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(detailView.DefineClassType, detailView); + Type[] usageTypes = TypeUsageProvider.TypeUsage.GetUsageTypes(view.DefineClassType, detailInView.Name); + List storedTypes = new List(); + foreach (var usageType in usageTypes) + { + if (Information.IsStoredType(usageType)) + { + storedTypes.Add(usageType); + } + } + + if (!storedTypes.Any()) + { + foreach (DataObject agregator in agregators) + { + Type agregatorType = agregator.GetType(); + Type[] usageTypesFromAgregator = TypeUsageProvider.TypeUsage.GetUsageTypes(agregatorType, detailInView.Name); + foreach (Type usageTypeFromAgregator in usageTypesFromAgregator) + { + if (Information.IsStoredType(usageTypeFromAgregator) && !storedTypes.Contains(usageTypeFromAgregator)) + { + storedTypes.Add(usageTypeFromAgregator); + } + } + } + } + + lcs.LoadingTypes = storedTypes.ToArray(); + Type agregatorKeyType = agregators[0].__PrimaryKey.GetType(); + + // Производим обработку только тех агрегаторов, для которых ранее не был загружен детейл. + object[] keys = agregators.Where(a => !a.CheckLoadedProperty(detailInView.Name)).Select(d => d.__PrimaryKey).ToArray(); + lcs.LimitFunction = FunctionBuilder.BuildIn(agregatorPropertyName, SQLWhereLanguageDef.LanguageDef.GetObjectTypeForNetType(agregatorKeyType), keys); + + // Нужно соблюсти единственность инстанций агрегаторов при вычитке, поэтому реализуем отдельный кеш. Смешивать с кешем dataObjectCache нельзя, поскольку в предстоящей выборке будут те же самые детейлы (значения в кеше затрутся). + // Агрегаторы в кэш не помещаем. От помещения агрегаторов в кэш возникают неконтролируемые сбои основного кэша. + DataObjectCache agregatorCache = new DataObjectCache(); + agregatorCache.StartCaching(false); + + // Вычитываются детейлы одного типа, но для нескольких инстанций агрегаторов (оптимизируем количество SQL-запросов). + DataObject[] loadedDetails = dataService.LoadObjects(lcs, agregatorCache); + agregatorCache.StopCaching(); + + Dictionary extraCacheForAgregators = new Dictionary(); + foreach (DataObject agregator in agregators) + { + agregator.AddLoadedProperties(detailInView.Name); + extraCacheForAgregators.Add(agregator.__PrimaryKey, agregator); + } + + // Ввиду того, что агрегаторы нам пришли готовые с пустыми коллекциями детейлов, заполняем детейлы по агрегаторам значениями из кеша или из базы. + // На загрузку детейлов второго уровня передаем только детейлы отсутствующие в кэше. + List toLoadSecondDetails = new List(); + foreach (DataObject loadedDetail in loadedDetails) + { + DataObject agregatorTemp = (DataObject)Information.GetPropValueByName(loadedDetail, agregatorPropertyName); + DataObject agregator = extraCacheForAgregators[agregatorTemp.__PrimaryKey]; + object detailPrimaryKey = loadedDetail.__PrimaryKey; + + DataObject detailFromCache = dataObjectCache.GetLivingDataObject(loadedDetail.GetType(), detailPrimaryKey); + + // Необходимо игнорировать объекты-пустышки, которые проинициализированы только первичным ключом. + bool detailFromCacheIsEmpty = detailFromCache == null || !detailFromCache.GetLoadedProperties().Any(); + DataObject detailForAdd = detailFromCacheIsEmpty + ? loadedDetail + : detailFromCache; + + agregator.AddDetail(detailForAdd); + + DataObject agregatorDataCopy = agregator.GetDataCopy(); + DataObject detailDataCopy = detailForAdd.GetDataCopy(); + + if (agregatorDataCopy != null && detailDataCopy != null) + { + agregatorDataCopy.AddDetail(detailDataCopy); + } + + if (detailFromCacheIsEmpty) + { + dataObjectCache.AddDataObject(loadedDetail); + toLoadSecondDetails.Add(loadedDetail); + } + } + + // Детейлы второго и последующих уровней нужно обработать аналогичным образом. + if (toLoadSecondDetails.Any() && secondDetailsInView.Any()) + { + dataService.SafeLoadDetails(detailInView.View, toLoadSecondDetails, dataObjectCache); + } + } + } + + /// + /// Догрузка объекта по указанному представлению, с загрузкой детейлов с сохранением состояния изменения. + /// + /// Экземпляр сервиса данных. + /// Объект данных, который нужно догрузить. + /// Представление, которое используется для догрузки. + /// Кеш объектов данных. + public static void SafeLoadObject(this IDataService dataService, DataObject dataObject, View view, DataObjectCache dataObjectCache) + { + if (dataService == null) + { + throw new ArgumentNullException(nameof(dataService)); + } + + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } + + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказать отдельные операции с детейлами и перевычитка затрёт эти изменения. + View miniView = view.Clone(); + DetailInView[] miniViewDetails = miniView.Details; + miniView.Details = new DetailInView[0]; + dataService.LoadObject(miniView, dataObject, false, true, dataObjectCache); + + if (miniViewDetails.Length > 0) + { + dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, dataObjectCache); + } + } + + /// + /// Add detail object to agregator according detail type. + /// + /// Agregator object. + /// Detail object. + public static void AddDetail(this DataObject agregator, DataObject detail) + { + if (agregator == null) + { + throw new ArgumentNullException(nameof(agregator)); + } + + if (detail == null) + { + throw new ArgumentNullException(nameof(detail)); + } + + Type agregatorType = agregator.GetType(); + Type detailType = detail.GetType(); + string detailPropertyName = Information.GetDetailArrayPropertyName(agregatorType, detailType); + + Type parentType = detailType.BaseType; + while (detailPropertyName == null && parentType != typeof(DataObject) && parentType != typeof(object) && parentType != null) + { + detailPropertyName = Information.GetDetailArrayPropertyName(agregatorType, parentType); + parentType = parentType.BaseType; + } + + if (detailPropertyName != null) + { + DetailArray details = (DetailArray)Information.GetPropValueByName(agregator, detailPropertyName); + + if (details != null) + { + DataObject existDetail = details.GetByKey(detail.__PrimaryKey); + + if (existDetail == null) + { + details.AddObject(detail); + } + } + } + else + { + LogService.LogWarn($"Detail type {detailType.AssemblyQualifiedName} not found in agregator of type {agregatorType.AssemblyQualifiedName}."); + } + } + + + /// + /// Перенос означенных свойств из свежезагруженного объекта в основной, расположенный в основном кэше. + /// + /// Основной объект, куда необходимо копировать значения свойств. + /// Свежезагруженный объект. + /// Основной кэш. + /// Локальный кэш, куда была выполнена свежая прогрузка. + private static void ProperUpdateOfObject(DataObject currentObject, DataObject loadedObjectLocal, DataObjectCache dataObjectCache, DataObjectCache dataObjectCacheLocal) + { + if (currentObject == null) + { + throw new ArgumentNullException(nameof(currentObject)); + } + + if (loadedObjectLocal == null) + { + throw new ArgumentNullException(nameof(loadedObjectLocal)); + } + + if (dataObjectCache == null) + { + throw new ArgumentNullException(nameof(dataObjectCache)); + } + + if (dataObjectCacheLocal == null) + { + throw new ArgumentNullException(nameof(dataObjectCacheLocal)); + } + + // Перенос значений свойств объекта (в том числе могут быть мастера). Если мастера означены, то перенос свойств мастера производится далее. + List localObjectLoadedProps = loadedObjectLocal.GetLoadedPropertiesList(); + List currentObjectLoadedProps = currentObject.GetLoadedPropertiesList(); + List notLoadedForActual = localObjectLoadedProps.Except(currentObjectLoadedProps).ToList(); + DataObject currentDataCopy = currentObject.GetDataCopy(); + foreach (string notLoadedPropName in notLoadedForActual) + { + object propValue = Information.GetPropValueByName(loadedObjectLocal, notLoadedPropName); + Information.SetPropValueByName(currentObject, notLoadedPropName, propValue); + currentObject.AddLoadedProperties(notLoadedPropName); + Information.SetPropValueByName(currentDataCopy, notLoadedPropName, propValue); + } + + // Ещё могут быть частично загруженные мастера. + ProperCacheUpdateForOneObject(dataObjectCache, dataObjectCacheLocal, loadedObjectLocal, true); + } + + /// + /// Обновление кэша по свежезагруженному объекту. + /// + /// Текущий основной кэш объектов. + /// Вспомогательный кэш, куда загружался объект. + /// Свежезагруженный объект, по которому обновляется основной кэш. + /// Флаг, определяющий, что в кэш уже добавлен свежезагруженный объект. + private static void ProperCacheUpdateForOneObject(DataObjectCache dataObjectCacheActual, DataObjectCache dataObjectCacheWithMasters, DataObject loadedDataObject, bool loadedObjectsAdded) + { + if (dataObjectCacheActual == null) + { + throw new ArgumentNullException(nameof(dataObjectCacheActual)); + } + + if (dataObjectCacheWithMasters == null) + { + throw new ArgumentNullException(nameof(dataObjectCacheWithMasters)); + } + + if (loadedDataObject == null) + { + return; + } + + if (!loadedObjectsAdded) + { + dataObjectCacheActual.AddDataObject(loadedDataObject); + } + + Type dobjType = typeof(DataObject); + Type currentType = loadedDataObject.GetType(); + List loadedProperties = loadedDataObject.GetLoadedPropertiesList(); + foreach (string currentPropertyName in loadedProperties) + { + Type currentPropertyType = Information.GetPropertyType(currentType, currentPropertyName); + if (currentPropertyType.IsSubclassOf(dobjType)) // Выбираем у текущего объекта ссылки на мастеров. + { + DataObject currentMaster = (DataObject)Information.GetPropValueByName(loadedDataObject, currentPropertyName); + if (currentMaster != null) + { + // Типы currentPropertyType и currentMaster.GetType() могут различаться из-за наследования. + DataObject masterFromActualCache = dataObjectCacheActual.GetLivingDataObject(currentMaster.GetType(), currentMaster.__PrimaryKey); + + if (masterFromActualCache == null) + { + // Если мастера ранее не было в кэше, то просто его туда переносим. + dataObjectCacheActual.AddDataObject(currentMaster); + + // Но в добавленном мастере могут быть мастера 2 и далее уровней. + ProperCacheUpdateForOneObject(dataObjectCacheActual, dataObjectCacheWithMasters, currentMaster, true); + } + else + { // Если мастер был в кэше, то аккуратно нужно перенести только незагруженные ранее свойства. + if (masterFromActualCache.GetStatus(false) == ObjectStatus.UnAltered && masterFromActualCache.GetLoadingState() != LoadingState.Loaded) + { + ProperUpdateOfObject(masterFromActualCache, currentMaster, dataObjectCacheActual, dataObjectCacheWithMasters); + } + } + } + } + } + } + + } +} diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs index 82db8ccb..e5c03947 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs @@ -1,64 +1,64 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Model -{ - using System.Collections.Generic; - using System.Reflection; - - using ICSSoft.STORMNET; - using System; - - /// - /// Metadata class for types. - /// - public sealed class DataObjectEdmTypeSettings - { - /// - /// Type of key, if null then used from based type. - /// - public Type KeyType { get; set; } - - /// - /// The name of appropriate EDM entity set. - /// - public string CollectionName { get; set; } - - /// - /// Is EDM entity set is required. - /// - public bool EnableCollection { get; set; } - - /// - /// Default view for queries without $select parameter. - /// - public View DefaultView { get; set; } - - /// - /// View to be used instead of DefaultView on updates (Patch/Batch). - /// - public View UpdateView { get; set; } - - /// - /// Whether to load object masters in LightLoaded state (load only primary key). - /// - public bool MasterLightLoad { get; set; } - - /// - /// The list of exposed details. - /// - public IDictionary DetailProperties { get; } = new Dictionary(); - - /// - /// The list of exposed masters. - /// - public IDictionary MasterProperties { get; } = new Dictionary(); - - /// - /// The list of exposed properties. - /// - public IList OwnProperties { get; } = new List(); - - /// - /// The list of exposed links from master to pseudodetail (pseudoproperties) as properties. - /// - public IDictionary PseudoDetailProperties { get; } = new Dictionary(); - } -} \ No newline at end of file +namespace NewPlatform.Flexberry.ORM.ODataService.Model +{ + using System.Collections.Generic; + using System.Reflection; + + using ICSSoft.STORMNET; + using System; + + /// + /// Metadata class for types. + /// + public sealed class DataObjectEdmTypeSettings + { + /// + /// Type of key, if null then used from based type. + /// + public Type KeyType { get; set; } + + /// + /// The name of appropriate EDM entity set. + /// + public string CollectionName { get; set; } + + /// + /// Is EDM entity set is required. + /// + public bool EnableCollection { get; set; } + + /// + /// Default view for queries without $select parameter. + /// + public View DefaultView { get; set; } + + /// + /// View to be used instead of DefaultView on updates (Patch/Batch). + /// + public View UpdateView { get; set; } + + /// + /// Whether to load object masters in LightLoaded state (load only primary key). + /// + public bool MasterLightLoad { get; set; } + + /// + /// The list of exposed details. + /// + public IDictionary DetailProperties { get; } = new Dictionary(); + + /// + /// The list of exposed masters. + /// + public IDictionary MasterProperties { get; } = new Dictionary(); + + /// + /// The list of exposed properties. + /// + public IList OwnProperties { get; } = new List(); + + /// + /// The list of exposed links from master to pseudodetail (pseudoproperties) as properties. + /// + public IDictionary PseudoDetailProperties { get; } = new Dictionary(); + } +}