From 3197b6ac3907d54d0510508dc9c1f50f7d538f7f Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Wed, 20 Mar 2024 16:01:17 +0500 Subject: [PATCH 01/17] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20UpdateView=20+=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../DataObjectController.ModifyData.cs | 113 +++++++++++++----- .../Model/DataObjectEdmModel.cs | 7 ++ .../Model/DataObjectEdmTypeSettings.cs | 7 +- .../Model/DefaultDataObjectEdmModelBuilder.cs | 56 ++++++++- .../CRUD/Update/MasterLightLoadTest.cs | 77 ++++++++++++ ...tform.Flexberry.ORM.ODataService.Tests.crp | 2 +- .../Startups/MasterLightLoadTestStartup.cs | 74 ++++++++++++ 8 files changed, 302 insertions(+), 35 deletions(-) create mode 100644 Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs create mode 100644 Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index a62c646e..714342e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### 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). +2. `masterLightLoadTypes` and `masterLightLoadAllTypes` parameters of `DefaultDataObjectEdmModelBuilder` class. They allow to change loading mode of masters during OData update requests. ### Changed 1. Updated Flexberry ORM up to 7.2.0-beta01. diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index e5b682bd..802206c5 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -1,4 +1,4 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Controllers +namespace NewPlatform.Flexberry.ORM.ODataService.Controllers { using System; using System.Collections.Generic; @@ -23,9 +23,11 @@ #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; #endif @@ -38,10 +40,10 @@ using NewPlatform.Flexberry.ORM.ODataService.Middleware; #endif - /// - /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. - /// - public partial class DataObjectController + /// + /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. + /// + public partial class DataObjectController { /// /// Метаданные файлов, временно загруженных в каталог файлового хранилища и привязанных к свойствам обрабатываемых объектов данных. @@ -774,7 +776,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) IEnumerable ownProps = view.Properties.Where(p => !p.Name.Contains('.')); if (!ownProps.All(p => loadedProps.Contains(p.Name))) { - // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказать отдельные операции с детейлами и перевычитка затрёт эти изменения. + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. View miniView = view.Clone(); DetailInView[] miniViewDetails = miniView.Details; miniView.Details = new DetailInView[0]; @@ -790,7 +792,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) return dataObjectFromCache; } - // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказать отдельные операции с детейлами и перевычитка затрёт эти изменения. + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. View lightView = view.Clone(); DetailInView[] lightViewDetails = lightView.Details; lightView.Details = new DetailInView[0]; @@ -848,8 +850,51 @@ private static void AddObjectToUpdate(List objsToUpdate, DataObject objsToUpdate.Insert(0, dataObject); // Добавляем объект в начало списка. } - } } + } + + /// + /// Получить значение ключа у указанной сущности. + /// + /// Сущность. + /// Значение ключа. + private object GetKey(EdmEntityObject edmEntity) + { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(edmEntity), $"{nameof(edmEntity)} can not be null."); + } + + object key; + + // Получим значение ключа. + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; + IEnumerable entityProps = entityType.Properties(); + var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName); + edmEntity.TryGetPropertyValue(keyProperty.Name, out key); + + return key; + } + + /// + /// Построение объекта данных по сущности OData без загрузки свойств и мастеров/детейлов. Загружен только первичный ключ. + /// + /// Сущность OData. + /// Объект данных. + private DataObject GetDataObjectByEdmEntityLight(EdmEntityObject edmEntity) + { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(edmEntity), $"{nameof(edmEntity)} can not be null."); + } + + var masterType = _model.GetDataObjectType(edmEntity); + var masterKey = GetKey(edmEntity); + var dataObject = (DataObject)Activator.CreateInstance(masterType); + dataObject.SetExistObjectPrimaryKey(masterKey); + + return dataObject; + } /// /// Построение объекта данных по сущности OData. @@ -867,21 +912,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke return null; } - // Значение свойства. - object value; - - // Получим значение ключа. - IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; - IEnumerable entityProps = entityType.Properties().ToList(); - var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName); - if (key != null) - { - value = key; - } - else - { - edmEntity.TryGetPropertyValue(keyProperty.Name, out value); - } + key = key ?? GetKey(edmEntity); // Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST. // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. @@ -896,7 +927,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke view = _model.GetDataObjectDefaultView(objType); } - DataObject obj = ReturnDataObject(objType, value, view); + DataObject obj = ReturnDataObject(objType, key, view); // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. AddObjectToUpdate(dObjs, obj, endObject); @@ -906,6 +937,8 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); // Обрабатываем агрегатор первым. + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; + IEnumerable entityProps = entityType.Properties().ToList(); List changedProps = entityProps .Where(ep => changedPropNames.Contains(ep.Name)) .OrderBy(ep => ep.Name != agregatorPropertyName) @@ -931,22 +964,37 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke // Обработка мастеров и детейлов. if (prop is EdmNavigationProperty navProp) { - edmEntity.TryGetPropertyValue(prop.Name, out value); - EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity(); // Обработка мастеров. if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne) { + object value; + edmEntity.TryGetPropertyValue(prop.Name, out value); + if (value is EdmEntityObject edmMaster) { // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); - DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); + bool masterOwnPropsUpdated = edmMaster.GetChangedPropertyNames().Any(propName => propName != _model.KeyPropertyName); + bool isAggregator = dataObjectPropName == agregatorPropertyName; + DataObject master = null; + + Type masterType = _model.GetDataObjectType(edmEntity); + bool masterLightLoad = _model.IsMasterLightLoad(masterType); + + if (masterLightLoad && !masterOwnPropsUpdated && !isAggregator) + { + master = GetDataObjectByEdmEntityLight(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered + } + else + { + master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); + } Information.SetPropValueByName(obj, dataObjectPropName, master); - if (dataObjectPropName == agregatorPropertyName) + if (isAggregator) { master.AddDetail(obj); @@ -969,6 +1017,9 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke { DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName); + object value; + edmEntity.TryGetPropertyValue(prop.Name, out value); + if (value is EdmEntityObjectCollection coll) { if (coll != null && coll.Count > 0) @@ -1002,11 +1053,13 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke else { // Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj). - if (prop.Name != keyProperty.Name) + if (prop.Name != _model.KeyPropertyName) { - Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName); + object value; edmEntity.TryGetPropertyValue(prop.Name, out value); + Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName); + // Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств, // значит свойство файловое, и его нужно обработать особым образом. if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType)) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs index dcfae14f..88f9efea 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs @@ -528,6 +528,13 @@ public View GetDataObjectUpdateView(Type dataObjectType) return _metadata[dataObjectType].UpdateView?.Clone(); } + /// + /// Возвращает информацию, должны ли мастера объекта загружаться в экономном режиме (только __PrimaryKey). + /// + /// Тип объекта данных. + /// Мастера должны загружаться экономно. + public bool IsMasterLightLoad(Type dataObjectType) => _metadata[dataObjectType].MasterLightLoad; + /// /// Получает список зарегистрированных в модели типов по списку имён типов. /// diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs index 4e4219a1..e5c03947 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs @@ -36,6 +36,11 @@ public sealed class DataObjectEdmTypeSettings /// 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. /// @@ -56,4 +61,4 @@ public sealed class DataObjectEdmTypeSettings /// public IDictionary PseudoDetailProperties { get; } = new Dictionary(); } -} \ No newline at end of file +} diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs index 3e6ed225..2cda385e 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -74,6 +74,16 @@ public class DefaultDataObjectEdmModelBuilder : IDataObjectEdmModelBuilder /// private Dictionary UpdateViews { get; set; } + /// + /// Types for which masters should be light-loaded on updates (load only __PrimaryKey). + /// + private IEnumerable MasterLightLoadTypes { get; set; } + + /// + /// Whether to load all masters in light-loaded mode on updates (load only __PrimaryKey). + /// + private bool MasterLightLoadAllTypes { get; set; } + private readonly PropertyInfo _keyProperty = Information.ExtractPropertyInfo(n => n.__PrimaryKey); /// @@ -88,7 +98,9 @@ public DefaultDataObjectEdmModelBuilder( bool useNamespaceInEntitySetName = true, PseudoDetailDefinitions pseudoDetailDefinitions = null, Dictionary additionalMapping = null, - IEnumerable> updateViews = null) + IEnumerable> updateViews = null, + IEnumerable masterLightLoadTypes = null, + bool masterLightLoadAllTypes = false) { _searchAssemblies = searchAssemblies ?? throw new ArgumentNullException(nameof(searchAssemblies), "Contract assertion not met: searchAssemblies != null"); _useNamespaceInEntitySetName = useNamespaceInEntitySetName; @@ -105,6 +117,18 @@ public DefaultDataObjectEdmModelBuilder( { SetUpdateView(updateViews); } + + if (masterLightLoadTypes != null) + { + SetMasterLightLoadTypes(masterLightLoadTypes); + + if (masterLightLoadAllTypes) + { + System.Diagnostics.Debug.WriteLine("Detected usage of masterLightLoadAllTypes parameter together with masterLightLoadTypes in DefaultDataObjectEdmModelBuilder. masterLightLoadTypes will be ignored, all data objects will be loaded in MasterLightLoad mode."); + } + } + + this.MasterLightLoadAllTypes = masterLightLoadAllTypes; } /// @@ -215,7 +239,12 @@ private void SetUpdateView(Type dataObjectType, View updateView) { if (!dataObjectType.IsSubclassOf(typeof(DataObject))) { - throw new ArgumentException("Update view can be set only for a DataObject.", nameof(dataObjectType)); + throw new ArgumentException($"Update view can be set only for a DataObject. Current type is {dataObjectType}", nameof(dataObjectType)); + } + + if (dataObjectType is null) + { + throw new ArgumentException("dataObjectType can not be null.", nameof(dataObjectType)); } if (updateView is null) @@ -232,6 +261,26 @@ private void SetUpdateView(Type dataObjectType, View updateView) UpdateViews[dataObjectType] = updateView; } + /// + /// Sets DataObject types for which masters will be light-loaded on updates (load only __PrimaryKey). + /// + /// Types for which masters should be light-loaded. + private void SetMasterLightLoadTypes(IEnumerable masterLightLoadTypes) + { + if (masterLightLoadTypes != null) + { + foreach (Type type in masterLightLoadTypes) + { + if (!type.IsSubclassOf(typeof(DataObject))) + { + throw new ArgumentException("MasterLightLoad option can be set only for a DataObject.", nameof(masterLightLoadTypes)); + } + } + + MasterLightLoadTypes = masterLightLoadTypes; + } + } + /// /// Adds the property for exposing. /// @@ -310,6 +359,7 @@ private void AddDataObjectWithHierarchy(DataObjectEdmMetadata meta, Type dataObj CollectionName = EntitySetNameBuilder(dataObjectType), DefaultView = DynamicView.Create(dataObjectType, null).View, UpdateView = updateView, + MasterLightLoad = MasterLightLoadAllTypes || (MasterLightLoadTypes?.Contains(dataObjectType) ?? false), }; AddProperties(dataObjectType, typeSettings); @@ -458,4 +508,4 @@ private string BuildEntityPropertyName(PropertyInfo propertyDataObject) return propertyDataObject.Name; } } -} \ No newline at end of file +} diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs new file mode 100644 index 00000000..7b152ede --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs @@ -0,0 +1,77 @@ +#if NETCOREAPP +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +{ + using System; + using System.Net; + using System.Net.Http; + using ICSSoft.STORMNET; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; + + using Xunit; + using Xunit.Abstractions; + + /// + /// Тесты для проверки работы MasterLightLoad. Для запуска OData backend используется модифицированная версия Startup - , + /// которая задаёт флаг экономной загрузки мастеров для . + /// + public class MasterLightLoadTest : BaseODataServiceIntegratedTest + { + /// + /// Конструктор по-умолчанию. + /// + /// Фабрика для приложения. + /// Вывод диагностической информации по тестам. + public MasterLightLoadTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + : base(factory, output) + { + } + + /// + /// Проверка экономной загрузки мастера при активной настройке MasterLightLoad при смене мастера. + /// + [Fact] + public void MasterLightLoadSettingTest() + { + ActODataService(args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Порода порода = new Порода { Название = "Сиамская" }; + Кошка кошка1 = new Кошка { Кличка = "Болтушка", Агрессивная = true, Порода = порода }; + Кошка кошка2 = new Кошка { Кличка = "Петрушка", Агрессивная = false, Порода = порода }; + args.DataService.UpdateObject(порода); + args.DataService.UpdateObject(кошка1); + args.DataService.UpdateObject(кошка2); + + Котенок котенок = new Котенок { Кошка = кошка1, КличкаКотенка = "Котенок Гав", Глупость = 10 }; + args.DataService.UpdateObject(котенок); + + // Обновляем ссылку на мастера + котенок.Кошка = кошка2; + + // Представление, по которому будем обновлять. + string[] котенокPropertiesNames = + { + Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey), + }; + var котенокDynamicView = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNames), typeof(Котенок)); + + // Преобразуем объект в JSON-строку. + string requestJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model); + + // Добавляем в payload информацию о том, что поменяли ссылку на мастера + requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonData, котенокDynamicView, args.Token.Model, кошка2, nameof(Котенок.Кошка)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, котенок); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Если приходит код 200, значит, настройка не ломает загрузку. Фактическую проверку того, что кошка загрузилась в LightLoaded надо делать через отладчик. + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + }); + } + } +} +#endif diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp index b149d758..b65594b7 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp @@ -1332,4 +1332,4 @@ - \ No newline at end of file + diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs new file mode 100644 index 00000000..907a7343 --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs @@ -0,0 +1,74 @@ +#if NETCOREAPP +namespace NewPlatform.Flexberry.ORM.ODataService.Tests +{ + using System; + using System.Collections.Generic; + using ICSSoft.Services; + using ICSSoft.STORMNET; + using IIS.Caseberry.Logging.Objects; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Configuration; + using NewPlatform.Flexberry.ORM.ODataService; + using NewPlatform.Flexberry.ORM.ODataService.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Model; + using NewPlatform.Flexberry.ORM.ODataService.WebApi.Extensions; + using NewPlatform.Flexberry.Services; + using ODataServiceSample.AspNetCore; + using Unity; + + /// + /// Startup for testing MasterLightLoad configuration. + /// Differs from TestStartup that it marks type as data object for which masters should be light-loaded. + /// + public class MasterLightLoadTestStartup : Startup + { + /// + /// Initialize new instance of TestStartup. + /// + /// Configuration for new instance. + public MasterLightLoadTestStartup(IConfiguration configuration) + : base(configuration) + { + } + + /// + public override void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + IUnityContainer unityContainer = UnityFactory.GetContainer(); + unityContainer.RegisterInstance(env); + + app.UseMiddleware(); + + app.UseMvc(builder => + { + builder.MapRoute("Lock", "api/lock/{action}/{dataObjectId}", new { controller = "Lock" }); + builder.MapFileRoute(); + }); + + app.UseODataService(builder => + { + IUnityContainer container = UnityFactory.GetContainer(); + + var assemblies = new[] + { + typeof(Котенок).Assembly, + typeof(ApplicationLog).Assembly, + typeof(UserSetting).Assembly, + typeof(Lock).Assembly, + }; + + PseudoDetailDefinitions pseudoDetailDefinitions = (PseudoDetailDefinitions)container.Resolve(typeof(PseudoDetailDefinitions)); + + var masterLightLoadTypes = new List { typeof(Котенок) }; // <- set MasterLightLoad property for this DataObject + var modelBuilder = new DefaultDataObjectEdmModelBuilder(assemblies, false, pseudoDetailDefinitions, masterLightLoadTypes: masterLightLoadTypes); + + var token = builder.MapDataObjectRoute(modelBuilder); + + container.RegisterInstance(typeof(ManagementToken), token); + }); + } + } +} +#endif From d7ed4e838b374e913c5814564cc95dba142dd711 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Thu, 21 Mar 2024 17:34:35 +0500 Subject: [PATCH 02/17] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B8=D0=B7=20=D0=BA=D0=B5=D1=88=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20UpdateView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataObjectController.ModifyData.cs | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 802206c5..a9cf2193 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -32,6 +32,7 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers using NewPlatform.Flexberry.ORM.ODataService.Handlers; #endif #if NETSTANDARD + using System.Data; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -40,10 +41,10 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers using NewPlatform.Flexberry.ORM.ODataService.Middleware; #endif - /// - /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. - /// - public partial class DataObjectController + /// + /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. + /// + public partial class DataObjectController { /// /// Метаданные файлов, временно загруженных в каталог файлового хранилища и привязанных к свойствам обрабатываемых объектов данных. @@ -741,13 +742,31 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) } /// - /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый. + /// Получить объект данных на основании EdmEntity. + /// + /// EdmEntity, который будет использован для получения объекта данных. + /// Объект данных. + private DataObject ReturnDataObject(EdmEntityObject edmEntity) + { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(edmEntity)); + } + + Type masterType = _model.GetDataObjectType(edmEntity); + object masterKey = GetKey(edmEntity); + + return ReturnDataObject(masterType, masterKey); + } + + /// + /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению по умолчанию, иначе - создаётся новый. /// /// Тип объекта, не может быть null. /// Значение ключа. - /// Представление для загрузки объекта. + /// Использовать UpdateView для загрузки объекта (при наличии). /// Объект данных. - private DataObject ReturnDataObject(Type objType, object keyValue, View view) + private DataObject ReturnDataObject(Type objType, object keyValue, bool useUpdateView = false) { if (objType == null) { @@ -756,6 +775,16 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) if (keyValue != null) { + View view = null; + if (useUpdateView) + { + view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); + } + else + { + view = _model.GetDataObjectDefaultView(objType); + } + DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); if (dataObjectFromCache != null) @@ -876,26 +905,6 @@ private object GetKey(EdmEntityObject edmEntity) return key; } - /// - /// Построение объекта данных по сущности OData без загрузки свойств и мастеров/детейлов. Загружен только первичный ключ. - /// - /// Сущность OData. - /// Объект данных. - private DataObject GetDataObjectByEdmEntityLight(EdmEntityObject edmEntity) - { - if (edmEntity == null) - { - throw new ArgumentNullException(nameof(edmEntity), $"{nameof(edmEntity)} can not be null."); - } - - var masterType = _model.GetDataObjectType(edmEntity); - var masterKey = GetKey(edmEntity); - var dataObject = (DataObject)Activator.CreateInstance(masterType); - dataObject.SetExistObjectPrimaryKey(masterKey); - - return dataObject; - } - /// /// Построение объекта данных по сущности OData. /// @@ -918,16 +927,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. Type objType = _model.GetDataObjectType(edmEntity); - View view = null; - if (useUpdateView) - { - view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); - } else - { - view = _model.GetDataObjectDefaultView(objType); - } - - DataObject obj = ReturnDataObject(objType, key, view); + DataObject obj = ReturnDataObject(objType, key, useUpdateView); // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. AddObjectToUpdate(dObjs, obj, endObject); @@ -980,12 +980,12 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke bool isAggregator = dataObjectPropName == agregatorPropertyName; DataObject master = null; - Type masterType = _model.GetDataObjectType(edmEntity); - bool masterLightLoad = _model.IsMasterLightLoad(masterType); + Type objectType = _model.GetDataObjectType(edmEntity); + bool masterLightLoad = _model.IsMasterLightLoad(objectType); if (masterLightLoad && !masterOwnPropsUpdated && !isAggregator) { - master = GetDataObjectByEdmEntityLight(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered + master = ReturnDataObject(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered } else { From 981f7c3ae45e5cca0c8dfac67269be9fca99a4bd Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 22 Mar 2024 19:22:23 +0500 Subject: [PATCH 03/17] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D1=91=D0=BD=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=20useUpdateVie?= =?UTF-8?q?w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataObjectController.ModifyData.cs | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index a9cf2193..a38fcd6d 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -679,7 +679,7 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) { // Создадим объект данных по пришедшей сущности. // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. - DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true); + DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs); for (int i = 0; i < objs.Count; i++) { @@ -764,9 +764,8 @@ private DataObject ReturnDataObject(EdmEntityObject edmEntity) /// /// Тип объекта, не может быть null. /// Значение ключа. - /// Использовать UpdateView для загрузки объекта (при наличии). /// Объект данных. - private DataObject ReturnDataObject(Type objType, object keyValue, bool useUpdateView = false) + private DataObject ReturnDataObject(Type objType, object keyValue) { if (objType == null) { @@ -775,15 +774,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, bool useUpdat if (keyValue != null) { - View view = null; - if (useUpdateView) - { - view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); - } - else - { - view = _model.GetDataObjectDefaultView(objType); - } + View view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); @@ -912,9 +903,8 @@ private object GetKey(EdmEntityObject edmEntity) /// Значение ключевого поля сущности. /// Список объектов для обновления. /// Признак, что объект добавляется в конец списка обновления. - /// Использовать представление для обновления (вместо представления по умолчанию). /// Объект данных. - private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false) + private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false) { if (edmEntity == null) { @@ -927,7 +917,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. Type objType = _model.GetDataObjectType(edmEntity); - DataObject obj = ReturnDataObject(objType, key, useUpdateView); + DataObject obj = ReturnDataObject(objType, key); // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. AddObjectToUpdate(dObjs, obj, endObject); @@ -989,7 +979,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke } else { - master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); + master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd); } Information.SetPropValueByName(obj, dataObjectPropName, master); @@ -1030,8 +1020,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke (EdmEntityObject)edmEnt, null, dObjs, - true, - useUpdateView); + true); if (det.__PrimaryKey == null) { From c3ff6f2b155dc4c1800da3904af766c7ee16fd3a Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 22 Mar 2024 19:23:00 +0500 Subject: [PATCH 04/17] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20null=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=BA=D0=B5=20=D0=BD=D0=B0=20LightLoad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/DataObjectEdmModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs index 88f9efea..95586d2a 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs @@ -533,7 +533,7 @@ public View GetDataObjectUpdateView(Type dataObjectType) /// /// Тип объекта данных. /// Мастера должны загружаться экономно. - public bool IsMasterLightLoad(Type dataObjectType) => _metadata[dataObjectType].MasterLightLoad; + public bool IsMasterLightLoad(Type dataObjectType) => _metadata == null ? false : _metadata[dataObjectType]?.MasterLightLoad ?? false; /// /// Получает список зарегистрированных в модели типов по списку имён типов. From a5befc6cba58cb53fbb3d54f7a84378ea0c65c00 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 22 Mar 2024 19:23:57 +0500 Subject: [PATCH 05/17] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20batch=20+=20Ma?= =?UTF-8?q?sterLightLoad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CRUD/Update/MasterLightLoadTest.cs | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs index 7b152ede..efabf49d 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs @@ -2,9 +2,14 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update { using System; + using System.Data; + using System.Linq; using System.Net; using System.Net.Http; using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business.LINQProvider; + using ICSSoft.STORMNET.KeyGen; + using NewPlatform.Flexberry.ORM.ODataService.Batch; using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; @@ -31,7 +36,7 @@ public MasterLightLoadTest(CustomWebApplicationFactory [Fact] - public void MasterLightLoadSettingTest() + public void MasterChangedTest() { ActODataService(args => { @@ -72,6 +77,84 @@ public void MasterLightLoadSettingTest() } }); } + + /// + /// Проверка экономной загрузки мастера при активной настройке MasterLightLoad при смене значения поля у мастера. + /// + [Fact] + public void MasterPropsChangedBatchTest() + { + ActODataService(async args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Порода порода = new Порода { Название = "Сиамская" }; + Кошка кошка = new Кошка { Кличка = "Болтушка", Агрессивная = true, Порода = порода }; + args.DataService.UpdateObject(порода); + args.DataService.UpdateObject(кошка); + + Котенок котенок = new Котенок { Кошка = кошка, КличкаКотенка = "Котенок Гав", Глупость = 10 }; + args.DataService.UpdateObject(котенок); + + // Обновляем атрибут объекта + котенок.Глупость = 1; + + // Обновляем атрибут мастера + котенок.Кошка.Кличка = "Петрушка"; + + // Представление, по которому будем обновлять объект + string[] котенокPropertiesNames = + { + Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Котенок>(x => x.Глупость), + }; + var котенокDynamicView = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNames), typeof(Котенок)); + + // Представление, по которому будем обновлять мастер + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + }; + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + // Преобразуем объект в JSON-строку. + string котенокJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model); + + // Добавляем в payload информацию о ссылке на мастера + котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, кошка, nameof(Котенок.Кошка)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, котенок); + + const string baseUrl = "http://localhost/odata"; + string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + кошка.ToJson(кошкаDynamicView, args.Token.Model), + кошка), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Котенок)).Name}", + котенокJsonData, + котенок), + }; + + // Act. + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + // Assert. + // TODO: проверка на экономную загрузку атрибутов. + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + Котенок котенокLoaded = args.DataService.Query<Котенок>(котенокDynamicView).FirstOrDefault(x => x.__PrimaryKey == котенок.__PrimaryKey); + Кошка кошкаLoaded = args.DataService.Query<Кошка>(кошкаDynamicView).FirstOrDefault(x => x.__PrimaryKey == кошка.__PrimaryKey); + Assert.NotNull(котенокLoaded); + Assert.NotNull(кошкаLoaded); + Assert.Equal(1, котенокLoaded.Глупость); + Assert.Equal("Петрушка", кошкаLoaded.Кличка); + } + }); + } } } #endif From 1f6e344b1501b7ddcb2452770877a17601ae9d41 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 22 Mar 2024 19:24:11 +0500 Subject: [PATCH 06/17] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Helpers/ODataTestHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs index 4f8bf49c..d4eba42e 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs @@ -20,16 +20,16 @@ public static class ODataTestHelper /// Новое тело запроса к OData. public static string AddEntryRelationship(string requestJsonData, View view, DataObjectEdmModel model, DataObject dataObject, string relationName) { - DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonData, view, model); + DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonData, view, model); - objJsonМедв.Add( + objJson.Add( $"{relationName}@odata.bind", string.Format( "{0}({1})", model.GetEdmEntitySet(dataObject.GetType()).Name, ((KeyGuid)dataObject.__PrimaryKey).Guid.ToString("D"))); - var result = objJsonМедв.Serialize(); + var result = objJson.Serialize(); return result; } From 650aaae821987b8c6a97b579e28a760c5932f259 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 22 Mar 2024 19:25:43 +0500 Subject: [PATCH 07/17] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BB=D0=B8=D1=88=D0=BD=D1=8F=D1=8F=20=D0=BE=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CRUD/Update/MasterLightLoadTest.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs index efabf49d..4b8bebca 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs @@ -123,9 +123,6 @@ public void MasterPropsChangedBatchTest() // Добавляем в payload информацию о ссылке на мастера котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, кошка, nameof(Котенок.Кошка)); - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, котенок); - const string baseUrl = "http://localhost/odata"; string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. { From c99efbfef4440a4f71e7ee7c0d0113c8845f68ef Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 22 Mar 2024 19:30:25 +0500 Subject: [PATCH 08/17] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App.config | 2 +- .../CRUD/Update/MasterLightLoadTest.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config index 58ddf418..596265bb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config @@ -6,7 +6,7 @@ - + diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs index 4b8bebca..2afbc75d 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs @@ -72,8 +72,11 @@ public void MasterChangedTest() using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) { - // Если приходит код 200, значит, настройка не ломает загрузку. Фактическую проверку того, что кошка загрузилась в LightLoaded надо делать через отладчик. + // Если приходит код 200, значит, настройка не ломает загрузку. + // Фактическую проверку того, что кошка загрузилась в LightLoaded надо делать через отладчик. Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // TODO: проверка на экономную загрузку мастера. } }); } @@ -141,7 +144,6 @@ public void MasterPropsChangedBatchTest() using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) { // Assert. - // TODO: проверка на экономную загрузку атрибутов. CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); Котенок котенокLoaded = args.DataService.Query<Котенок>(котенокDynamicView).FirstOrDefault(x => x.__PrimaryKey == котенок.__PrimaryKey); Кошка кошкаLoaded = args.DataService.Query<Кошка>(кошкаDynamicView).FirstOrDefault(x => x.__PrimaryKey == кошка.__PrimaryKey); @@ -149,6 +151,8 @@ public void MasterPropsChangedBatchTest() Assert.NotNull(кошкаLoaded); Assert.Equal(1, котенокLoaded.Глупость); Assert.Equal("Петрушка", кошкаLoaded.Кличка); + + // TODO: проверка на экономную загрузку мастера. } }); } From 16999ff6aac2ea62c5bba680363b49657a53154d Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 26 Mar 2024 10:08:09 +0500 Subject: [PATCH 09/17] =?UTF-8?q?=D0=92=D1=8B=D0=B1=D1=80=D0=BE=D1=81=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=BD=D0=BE=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D0=BC=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=BF=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2=20masterLightLoadAllTypes?= =?UTF-8?q?=20=D0=B8=20masterLightLoadTypes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/DefaultDataObjectEdmModelBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs index 2cda385e..fbe317d4 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -124,7 +124,7 @@ public DefaultDataObjectEdmModelBuilder( if (masterLightLoadAllTypes) { - System.Diagnostics.Debug.WriteLine("Detected usage of masterLightLoadAllTypes parameter together with masterLightLoadTypes in DefaultDataObjectEdmModelBuilder. masterLightLoadTypes will be ignored, all data objects will be loaded in MasterLightLoad mode."); + throw new ArgumentException("Parameters masterLightLoadAllTypes and masterLightLoadTypes can not be used together in DefaultDataObjectEdmModelBuilder."); } } From 626e9200faae30742f2f69a6489982393e6244dd Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 26 Mar 2024 10:08:32 +0500 Subject: [PATCH 10/17] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B9=20connection=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config index 596265bb..58ddf418 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/App.config @@ -6,7 +6,7 @@ - + From f0248988d6ff6e40fe50bca3237a546155d394f5 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 26 Mar 2024 12:09:00 +0500 Subject: [PATCH 11/17] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BC=D0=B0=D1=81=D1=82=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=BE=D0=B1=D0=BB=D0=B5=D0=B3=D1=87=D1=91=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=BC=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataObjectController.ModifyData.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index a38fcd6d..dd3838c6 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -30,6 +30,7 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers using Newtonsoft.Json.Linq; using NewPlatform.Flexberry.ORM.ODataService.Events; using NewPlatform.Flexberry.ORM.ODataService.Handlers; + using NewPlatform.Flexberry.ORM.ODataService.Model; #endif #if NETSTANDARD using System.Data; @@ -742,11 +743,12 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) } /// - /// Получить объект данных на основании EdmEntity. + /// Загрузить существующий объект данных, используя EdmEntity. /// /// EdmEntity, который будет использован для получения объекта данных. + /// Представление, по которому будет загружен объект (если не указано, будет загружен только __PrimaryKey). /// Объект данных. - private DataObject ReturnDataObject(EdmEntityObject edmEntity) + private DataObject SafeLoadObject(EdmEntityObject edmEntity, View view = null) { if (edmEntity == null) { @@ -756,7 +758,34 @@ private DataObject ReturnDataObject(EdmEntityObject edmEntity) Type masterType = _model.GetDataObjectType(edmEntity); object masterKey = GetKey(edmEntity); - return ReturnDataObject(masterType, masterKey); + if (view == null) + { + view = new View(new ViewAttribute("dynView", new string[] { Information.ExtractPropertyPath(x => x.__PrimaryKey) }), masterType); + } + + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. + View lightView = view.Clone(); + DetailInView[] lightViewDetails = lightView.Details; + lightView.Details = new DetailInView[0]; + + // Проверим существование объекта в базе. + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(masterType, lightView); + lcs.LimitFunction = FunctionBuilder.BuildEquals(masterKey); + lcs.ReturnTop = 2; + DataObject[] dobjs = _dataService.LoadObjects(lcs, DataObjectCache); + if (dobjs.Length == 1) + { + DataObject dataObject = dobjs[0]; + if (lightViewDetails.Any()) + { + // Дочитаем детейлы, чтобы в бизнес-серверах эти данные уже были. Детейлы с изменёнными состояниями будут пропущены из зачитки. + _dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, DataObjectCache); + } + + return dataObject; + } + + return null; } /// @@ -975,7 +1004,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke if (masterLightLoad && !masterOwnPropsUpdated && !isAggregator) { - master = ReturnDataObject(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered + master = SafeLoadObject(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered } else { From dbe2e01d055a9e082f6306b4d7dbd6beea65780e Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 26 Mar 2024 12:09:11 +0500 Subject: [PATCH 12/17] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BF=D0=BE=D1=80=D1=8F=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=20using?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DataObjectController.ModifyData.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index dd3838c6..1f256b0c 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -27,10 +27,9 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers 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 NewPlatform.Flexberry.ORM.ODataService.Model; + using Newtonsoft.Json.Linq; #endif #if NETSTANDARD using System.Data; From 845394a3e411c7d20dfb586e8d08413d7613e0cd Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 26 Mar 2024 21:29:57 +0500 Subject: [PATCH 13/17] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=D0=B0=20=D1=81=20=D0=BE=D0=B1=D0=BB=D0=B5=D0=B3=D1=87?= =?UTF-8?q?=D1=91=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B7=D0=BA=D0=BE=D0=B9=20=D0=BC=D0=B0=D1=81=D1=82=D0=B5=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20batch=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataObjectController.ModifyData.cs | 119 +++++++++--------- .../CRUD/Update/MasterLightLoadTest.cs | 2 +- 2 files changed, 57 insertions(+), 64 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 1f256b0c..5c0fb5f8 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -30,6 +30,8 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers using NewPlatform.Flexberry.ORM.ODataService.Events; using NewPlatform.Flexberry.ORM.ODataService.Handlers; using Newtonsoft.Json.Linq; + using System.Web.UI.WebControls; + using View = ICSSoft.STORMNET.View; #endif #if NETSTANDARD using System.Data; @@ -742,12 +744,11 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) } /// - /// Загрузить существующий объект данных, используя EdmEntity. + /// Загрузить существующий объект данных в облегчённом варианте (только __PrimaryKey), используя информацию из EdmEntity. /// /// EdmEntity, который будет использован для получения объекта данных. - /// Представление, по которому будет загружен объект (если не указано, будет загружен только __PrimaryKey). /// Объект данных. - private DataObject SafeLoadObject(EdmEntityObject edmEntity, View view = null) + private DataObject LightLoadDataObject(EdmEntityObject edmEntity) { if (edmEntity == null) { @@ -756,10 +757,54 @@ private DataObject SafeLoadObject(EdmEntityObject edmEntity, View view = null) Type masterType = _model.GetDataObjectType(edmEntity); object masterKey = GetKey(edmEntity); + View view = new View(new ViewAttribute("dynView", new string[] { Information.ExtractPropertyPath(x => x.__PrimaryKey) }), masterType); - if (view == null) + return LoadDataObject(masterType, masterKey, view); + } + + /// + /// Загрузить существующий объект данных. + /// + /// Тип загружаемого объекта. + /// Первичный ключ загружаемого объекта. + /// Представление, по которому будет загружен объект. + /// Объект данных. + private DataObject LoadDataObject(Type objType, object keyValue, View view) + { + DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); + + if (dataObjectFromCache != null) { - view = new View(new ViewAttribute("dynView", new string[] { Information.ExtractPropertyPath(x => x.__PrimaryKey) }), masterType); + // Если объект не новый и не загружен целиком (начиная с ORM@5.1.0-beta15). + if (dataObjectFromCache.GetStatus(false) == ObjectStatus.UnAltered + && dataObjectFromCache.GetLoadingState() != LoadingState.Loaded) + { + // Для обратной совместимости сравним перечень загруженных свойств и свойств в представлении. + /* Данный код срабатывает, например, если в кэше был объект, который загрузился только на уровне первичного ключа. + * + * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В. + * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В. + * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded. + * Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены). + */ + string[] loadedProps = dataObjectFromCache.GetLoadedProperties(); + IEnumerable ownProps = view.Properties.Where(p => !p.Name.Contains('.')); + if (!ownProps.All(p => loadedProps.Contains(p.Name))) + { + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. + View miniView = view.Clone(); + DetailInView[] miniViewDetails = miniView.Details; + miniView.Details = new DetailInView[0]; + _dataService.SafeLoadWithMasters(miniView, dataObjectFromCache, DataObjectCache); + + if (miniViewDetails.Length > 0) + { + _dataService.SafeLoadDetails(view, new DataObject[] { dataObjectFromCache }, DataObjectCache); + } + } + } + + return dataObjectFromCache; } // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. @@ -768,8 +813,8 @@ private DataObject SafeLoadObject(EdmEntityObject edmEntity, View view = null) lightView.Details = new DetailInView[0]; // Проверим существование объекта в базе. - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(masterType, lightView); - lcs.LimitFunction = FunctionBuilder.BuildEquals(masterKey); + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(objType, lightView); + lcs.LimitFunction = FunctionBuilder.BuildEquals(keyValue); lcs.ReturnTop = 2; DataObject[] dobjs = _dataService.LoadObjects(lcs, DataObjectCache); if (dobjs.Length == 1) @@ -803,62 +848,9 @@ private DataObject ReturnDataObject(Type objType, object keyValue) if (keyValue != null) { View view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); - - DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); - - if (dataObjectFromCache != null) + DataObject dataObject = LoadDataObject(objType, keyValue, view); + if (dataObject != null) { - // Если объект не новый и не загружен целиком (начиная с ORM@5.1.0-beta15). - if (dataObjectFromCache.GetStatus(false) == ObjectStatus.UnAltered - && dataObjectFromCache.GetLoadingState() != LoadingState.Loaded) - { - // Для обратной совместимости сравним перечень загруженных свойств и свойств в представлении. - /* Данный код срабатывает, например, если в кэше был объект, который загрузился только на уровне первичного ключа. - * - * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В. - * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В. - * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded. - * Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены). - */ - string[] loadedProps = dataObjectFromCache.GetLoadedProperties(); - IEnumerable ownProps = view.Properties.Where(p => !p.Name.Contains('.')); - if (!ownProps.All(p => loadedProps.Contains(p.Name))) - { - // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. - View miniView = view.Clone(); - DetailInView[] miniViewDetails = miniView.Details; - miniView.Details = new DetailInView[0]; - _dataService.SafeLoadWithMasters(miniView, dataObjectFromCache, DataObjectCache); - - if (miniViewDetails.Length > 0) - { - _dataService.SafeLoadDetails(view, new DataObject[] { dataObjectFromCache }, DataObjectCache); - } - } - } - - return dataObjectFromCache; - } - - // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. - View lightView = view.Clone(); - DetailInView[] lightViewDetails = lightView.Details; - lightView.Details = new DetailInView[0]; - - // Проверим существование объекта в базе. - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(objType, lightView); - lcs.LimitFunction = FunctionBuilder.BuildEquals(keyValue); - lcs.ReturnTop = 2; - DataObject[] dobjs = _dataService.LoadObjects(lcs, DataObjectCache); - if (dobjs.Length == 1) - { - DataObject dataObject = dobjs[0]; - if (lightViewDetails.Any()) - { - // Дочитаем детейлы, чтобы в бизнес-серверах эти данные уже были. Детейлы с изменёнными состояниями будут пропущены из зачитки. - _dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, DataObjectCache); - } - return dataObject; } } @@ -1003,7 +995,8 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke if (masterLightLoad && !masterOwnPropsUpdated && !isAggregator) { - master = SafeLoadObject(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered + master = LightLoadDataObject(edmMaster); + //AddObjectToUpdate(dObjs, master, insertIntoEnd); } else { diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs index 2afbc75d..85b664e1 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs @@ -124,7 +124,7 @@ public void MasterPropsChangedBatchTest() string котенокJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model); // Добавляем в payload информацию о ссылке на мастера - котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, кошка, nameof(Котенок.Кошка)); + котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, котенок.Кошка, nameof(Котенок.Кошка)); const string baseUrl = "http://localhost/odata"; string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. From 361badc86b9e60d838b752aa4c09add3bc2f13db Mon Sep 17 00:00:00 2001 From: inaidanov Date: Wed, 27 Mar 2024 09:19:56 +0500 Subject: [PATCH 14/17] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D1=91=D0=BD=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B9=20using?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DataObjectController.ModifyData.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 5c0fb5f8..a99fb282 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -30,8 +30,7 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers using NewPlatform.Flexberry.ORM.ODataService.Events; using NewPlatform.Flexberry.ORM.ODataService.Handlers; using Newtonsoft.Json.Linq; - using System.Web.UI.WebControls; - using View = ICSSoft.STORMNET.View; + using ICSSoft.STORMNET.View; #endif #if NETSTANDARD using System.Data; From 9abb80871d0346a6708b40a73f8afcf7336b5e48 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Wed, 27 Mar 2024 09:26:06 +0500 Subject: [PATCH 15/17] =?UTF-8?q?=D0=9B=D0=B8=D1=88=D0=BD=D0=B8=D0=B9=20us?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DataObjectController.ModifyData.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index a99fb282..1fd72656 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -30,7 +30,6 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Controllers using NewPlatform.Flexberry.ORM.ODataService.Events; using NewPlatform.Flexberry.ORM.ODataService.Handlers; using Newtonsoft.Json.Linq; - using ICSSoft.STORMNET.View; #endif #if NETSTANDARD using System.Data; From 31d0ded3a8d8a90c5494c2559a61855332e2c634 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Wed, 27 Mar 2024 09:49:04 +0500 Subject: [PATCH 16/17] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D1=8D=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D0=BE=D0=BC=D0=BD=D0=BE=D0=B9=20=D0=B7=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20=D0=BC=D0=B0=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B2=20Batch=20(=D0=BC=D0=B0=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D1=80=20=D0=BE=D1=82=D1=81=D1=82=D1=83=D1=82=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D1=83=D0=B5=D1=82=20=D0=B2=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CRUD/Update/MasterLightLoadTest.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs index 85b664e1..01f70568 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs @@ -156,6 +156,78 @@ public void MasterPropsChangedBatchTest() } }); } + + /// + /// Проверка экономной загрузки мастера при активной настройке MasterLightLoad при смене значения поля у мастера. + /// + [Fact] + public void MasterChangedBatchTest() + { + ActODataService(async args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Порода порода = new Порода { Название = "Сиамская" }; + Кошка кошка1 = new Кошка { Кличка = "Болтушка", Агрессивная = true, Порода = порода }; + Кошка кошка2 = new Кошка { Кличка = "Петрушка", Агрессивная = false, Порода = порода }; + args.DataService.UpdateObject(порода); + args.DataService.UpdateObject(кошка1); + args.DataService.UpdateObject(кошка2); + + Котенок котенок = new Котенок { Кошка = кошка1, КличкаКотенка = "Котенок Гав", Глупость = 10 }; + args.DataService.UpdateObject(котенок); + + // Обновляем атрибут объекта + котенок.Глупость = 1; + + // Обновляем мастера + котенок.Кошка = кошка2; + + // Представление, по которому будем обновлять объект + string[] котенокPropertiesNames = + { + Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Котенок>(x => x.Глупость), + }; + var котенокDynamicView = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNames), typeof(Котенок)); + + // Преобразуем объект в JSON-строку. + string котенокJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model); + + // Добавляем в payload информацию о ссылке на мастера + котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, котенок.Кошка, nameof(Котенок.Кошка)); + + const string baseUrl = "http://localhost/odata"; + string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Котенок)).Name}", + котенокJsonData, + котенок), + }; + + // Act. + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + // Assert. + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK }); + + string[] котенокPropertiesNamesMaster = + { + Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Котенок>(x => x.Глупость), + Information.ExtractPropertyPath<Котенок>(x => x.Кошка), + }; + var котенокDynamicViewMaster = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNamesMaster), typeof(Котенок)); + Котенок котенокLoaded = args.DataService.Query<Котенок>(котенокDynamicViewMaster).FirstOrDefault(x => x.Кошка.__PrimaryKey == кошка2.__PrimaryKey); + Assert.NotNull(котенокLoaded); + Assert.Equal(кошка2.__PrimaryKey, котенокLoaded.Кошка.__PrimaryKey); + Assert.Equal(1, котенокLoaded.Глупость); + + // TODO: проверка на экономную загрузку мастера. + } + }); + } } } #endif From 21ac6cc87fd97ec60ba13eeede23cac18b25d32d Mon Sep 17 00:00:00 2001 From: Bratchikov Igor <837035+bratchikov@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:03:56 +0500 Subject: [PATCH 17/17] Update Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs --- .../Startups/MasterLightLoadTestStartup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs index 907a7343..f79b7d5f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs @@ -61,7 +61,8 @@ public override void Configure(IApplicationBuilder app, IHostingEnvironment env) PseudoDetailDefinitions pseudoDetailDefinitions = (PseudoDetailDefinitions)container.Resolve(typeof(PseudoDetailDefinitions)); - var masterLightLoadTypes = new List { typeof(Котенок) }; // <- set MasterLightLoad property for this DataObject + // Set MasterLightLoad property for this DataObject + var masterLightLoadTypes = new List { typeof(Котенок) }; var modelBuilder = new DefaultDataObjectEdmModelBuilder(assemblies, false, pseudoDetailDefinitions, masterLightLoadTypes: masterLightLoadTypes); var token = builder.MapDataObjectRoute(modelBuilder);