diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 074b815d..9a7c0ab8 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -635,7 +635,7 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) { // Создадим объект данных по пришедшей сущности. // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. - DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs); + DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true); for (int i = 0; i < objs.Count; i++) { @@ -698,12 +698,13 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) } /// - /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению по умолчанию, иначе - создаётся новый. + /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый. /// /// Тип объекта, не может быть null. - /// Значение ключа.> + /// Значение ключа. + /// Представление для загрузки объекта. /// Объект данных. - private DataObject ReturnDataObject(Type objType, object keyValue) + private DataObject ReturnDataObject(Type objType, object keyValue, View view) { if (objType == null) { @@ -713,7 +714,6 @@ private DataObject ReturnDataObject(Type objType, object keyValue) if (keyValue != null) { DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); - View view = _model.GetDataObjectDefaultView(objType); if (dataObjectFromCache != null) { @@ -727,16 +727,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue) 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.LoadObject(miniView, dataObjectFromCache, false, true, DataObjectCache); - - if (miniViewDetails.Length > 0) - { - _dataService.SafeLoadDetails(view, new DataObject[] { dataObjectFromCache }, DataObjectCache); - } + _dataService.SafeLoadObject(dataObjectFromCache, view, DataObjectCache); } } @@ -782,6 +773,28 @@ private DataObject ReturnDataObject(Type objType, object keyValue) return obj; } + /// + /// Добавляет объект данных в список на обновление если его там ещё нет. + /// + /// Список на обновление. + /// Объект данных который добавляем. + /// Добавлять в конец списка. + private static void AddDataObject(List objsToUpdate, DataObject dataObject, bool insertToEnd) + { + bool objAlreadyExists = objsToUpdate.Any(o => PKHelper.EQDataObject(o, dataObject, false)); + if (!objAlreadyExists) + { + if (insertToEnd) + { + objsToUpdate.Add(dataObject); // Добавляем в конец списка. + } else + { + objsToUpdate.Insert(0, dataObject); // Добавляем объект в начало списка. + } + + } + } + /// /// Построение объекта данных по сущности OData. /// @@ -789,21 +802,20 @@ private DataObject ReturnDataObject(Type objType, object keyValue) /// Значение ключевого поля сущности. /// Список объектов для обновления. /// Признак, что объект добавляется в конец списка обновления. + /// Использовать представление для обновления (вместо представления по умолчанию). /// Объект данных. - private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false) + private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false) { if (edmEntity == null) { return null; } - IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; - Type objType = _model.GetDataObjectType(_model.GetEdmEntitySet(entityType).Name); - // Значение свойства. object value; // Получим значение ключа. + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; IEnumerable entityProps = entityType.Properties().ToList(); var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName); if (key != null) @@ -815,26 +827,21 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke edmEntity.TryGetPropertyValue(keyProperty.Name, out value); } - // Загрузим объект из хранилища, если он там есть (используем представление по умолчанию), или создадим, если нет, но только для POST. + // Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST. // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. - DataObject obj = ReturnDataObject(objType, value); + Type objType = _model.GetDataObjectType(edmEntity); - // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. - var objInList = dObjs.FirstOrDefault(o => PKHelper.EQDataObject(o, obj, false)); - if (objInList == null) + View view = _model.GetDataObjectDefaultView(objType); + if (useUpdateView) { - if (!endObject) - { - // Добавляем объект в начало списка. - dObjs.Insert(0, obj); - } - else - { - // Добавляем в конец списка. - dObjs.Add(obj); - } + view = _model.GetDataObjectUpdateView(objType) ?? view; } + DataObject obj = ReturnDataObject(objType, value, view); + + // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. + AddDataObject(dObjs, obj, endObject); + // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). string agregatorPropertyName = Information.GetAgregatePropertyName(objType); IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); @@ -876,7 +883,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke { // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); - DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd); + DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); Information.SetPropValueByName(obj, dataObjectPropName, master); @@ -913,7 +920,8 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke (EdmEntityObject)edmEnt, null, dObjs, - true); + true, + useUpdateView); if (det.__PrimaryKey == null) { @@ -1008,20 +1016,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke if (agregator != null) { - DataObject existObject = dObjs.FirstOrDefault(o => PKHelper.EQDataObject(o, agregator, false)); - if (existObject == null) - { - if (!endObject) - { - // Добавляем объект в начало списка. - dObjs.Insert(0, agregator); - } - else - { - // Добавляем в конец списка. - dObjs.Add(agregator); - } - } + AddDataObject(dObjs, agregator, endObject); } } diff --git a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs index 9d0ddd9b..e7630b3b 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs @@ -179,6 +179,37 @@ public static void SafeLoadDetails(this IDataService dataService, View view, ILi } } + /// + /// Догрузка объекта по указанному представлению, с загрузкой детейлов с сохранением состояния изменения. + /// + /// Экземпляр сервиса данных. + /// Объект данных, который нужно догрузить. + /// Представление, которое используется для догрузки. + /// Кеш объектов данных. + 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. /// diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs index c386f791..dcfae14f 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs @@ -511,6 +511,21 @@ public View GetDataObjectDefaultView(Type dataObjectType) } return _metadata[dataObjectType].DefaultView.Clone(); + } + + /// + /// Осуществляет получение представления для обновления объекта, соответствующего заданному типу объекта данных. + /// + /// Тип объекта данных, для которого требуется получить представление для обновления. + /// Представление для обновления объекта, соответствующее заданному типу объекта данных. + public View GetDataObjectUpdateView(Type dataObjectType) + { + if (dataObjectType == null) + { + throw new ArgumentNullException(nameof(dataObjectType), "Contract assertion not met: dataObjectType != null"); + } + + return _metadata[dataObjectType].UpdateView?.Clone(); } /// @@ -534,7 +549,7 @@ public List GetTypes(List strTypes) /// /// Осуществляет получение типа объекта данных, соответствующего заданному имени набора сущностей в EDM-модели. /// - /// Имя набора сущностей в EDM-модели, для которого требуется получить представление по умолчанию. + /// Имя набора сущностей в EDM-модели, для которого требуется получить тип. /// Типа объекта данных, соответствующий заданному имени набора сущностей в EDM-модели. public Type GetDataObjectType(string edmEntitySetName) { @@ -554,6 +569,22 @@ public Type GetDataObjectType(string edmEntitySetName) return dataObjectType; } + /// + /// Осуществляет получение типа объекта данных, соответствующего заданной сущности в EDM-модели. + /// + /// Сущность в EDM-модели, для которой требуется получить тип. + /// Типа объекта данных, соответствующий заданной сущности в EDM-модели. + public Type GetDataObjectType(EdmEntityObject edmEntity) + { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(edmEntity)); + } + + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; + return GetDataObjectType(GetEdmEntitySet(entityType).Name); + } + /// /// Получает список зарегистрированных в модели типов, которые являются дочерними к данному родительскому типу. /// В список добавляется также сам родительский тип. diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs index bcfcc3ef..4e4219a1 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs @@ -31,6 +31,11 @@ public sealed class DataObjectEdmTypeSettings /// public View DefaultView { get; set; } + /// + /// View to be used instead of DefaultView on updates (Patch/Batch). + /// + public View UpdateView { get; set; } + /// /// The list of exposed details. /// diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs index 63f84ab6..b6e3bd18 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -69,6 +69,11 @@ public class DefaultDataObjectEdmModelBuilder : IDataObjectEdmModelBuilder /// public Func EntityPropertyNameBuilder { get; set; } + /// + /// Dictionary of views to be used to load objects on updates. Used to restrict properties for performance (all props are loaded if view is not specified). + /// + private Dictionary UpdateViews { get; set; } + private readonly PropertyInfo _keyProperty = Information.ExtractPropertyInfo(n => n.__PrimaryKey); /// @@ -82,7 +87,8 @@ public DefaultDataObjectEdmModelBuilder( IEnumerable searchAssemblies, bool useNamespaceInEntitySetName = true, PseudoDetailDefinitions pseudoDetailDefinitions = null, - Dictionary additionalMapping = null) + Dictionary additionalMapping = null, + IEnumerable> updateViews = null) { _searchAssemblies = searchAssemblies ?? throw new ArgumentNullException(nameof(searchAssemblies), "Contract assertion not met: searchAssemblies != null"); _useNamespaceInEntitySetName = useNamespaceInEntitySetName; @@ -94,6 +100,11 @@ public DefaultDataObjectEdmModelBuilder( EntityPropertyNameBuilder = BuildEntityPropertyName; EntityTypeNameBuilder = BuildEntityTypeName; EntityTypeNamespaceBuilder = BuildEntityTypeNamespace; + + if (updateViews != null) + { + SetUpdateView(updateViews); + } } /// @@ -173,6 +184,54 @@ public IPseudoDetailDefinition GetPseudoDetailDefinition(object pseudoDetail) .FirstOrDefault(); } + /// + /// Change default views that would be used to load objects on updates. + /// + /// Should be called before MapDataObjectRoute. + /// Key - DataObject type, value - view to be used for objects of that type on updates. + private void SetUpdateView(IEnumerable> updateViews) + { + if (this.UpdateViews is null) + { + this.UpdateViews = new Dictionary(); + } + + if (updateViews != null) + { + foreach (KeyValuePair kvp in updateViews) + { + SetUpdateView(kvp.Key, kvp.Value); + } + } + } + + /// + /// Change for a specific . Update view would be used to load these objects on updates. + /// + /// Should be called before MapDataObjectRoute. + /// DataObject type for which update view would be set. + /// Update view to be used for objects of type . Setting removes update view for the type. + private void SetUpdateView(Type dataObjectType, View updateView) + { + if (!typeof(DataObject).IsAssignableFrom(dataObjectType)) + { + throw new ArgumentException("Update view can be set only for a DataObject.", nameof(dataObjectType)); + } + + if (updateView is null) + { + UpdateViews.Remove(dataObjectType); + return; + } + + if (dataObjectType != updateView.DefineClassType) + { + throw new ArgumentException($"View from DataObject {updateView.DefineClassType} can not be set for a DataObject of type {dataObjectType}.", nameof(updateView)); + } + + UpdateViews[dataObjectType] = updateView; + } + /// /// Adds the property for exposing. /// @@ -238,12 +297,21 @@ private void AddDataObjectWithHierarchy(DataObjectEdmMetadata meta, Type dataObj AddDataObjectWithHierarchy(meta, baseType); + // Extract user-defined update view: + View updateView = null; + if (UpdateViews != null) + { + UpdateViews.TryGetValue(dataObjectType, out updateView); + } + var typeSettings = meta[dataObjectType] = new DataObjectEdmTypeSettings { EnableCollection = true, CollectionName = EntitySetNameBuilder(dataObjectType), - DefaultView = DynamicView.Create(dataObjectType, null).View + DefaultView = DynamicView.Create(dataObjectType, null).View, + UpdateView = updateView, }; + AddProperties(dataObjectType, typeSettings); if (typeSettings.KeyType != null) meta[baseType].KeyType = null; diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs index d0592622..ce6ed159 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs @@ -1605,5 +1605,165 @@ public void UpdateDetailAndDetailTest() } }); } + + + /// + /// Тест на изменение ссылки. + /// + [Fact] + public void MasterChangeTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем обновлять, и добавляем в базу обычным сервисом данных. + Страна страна1 = new Страна { Название = "Страна1" }; + Страна страна2 = new Страна { Название = "Россия" }; + args.DataService.UpdateObject(страна1); + args.DataService.UpdateObject(страна2); + + Лес лес = new Лес { Название = "Тайга", Площадь = 2000, Страна = страна1 }; + 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 информацию, что поменяли ссылку на страну. + var requestJsonData = ODataHelper.AddEntryRelationship(requestJsonDataЛес, лесDynamicView, args.Token.Model, страна2, nameof(Лес.Страна)); + + // Получаем адрес для PATCH запроса. + var requestUrl = ODataHelper.GetRequestUrl(args.Token.Model, лес); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + Лес updatedЛес = new Лес { __PrimaryKey = лес.__PrimaryKey }; + args.DataService.LoadObject(updatedЛес); + + Assert.Equal(лес.Страна.__PrimaryKey.ToString(), updatedЛес.Страна.__PrimaryKey.ToString()); + } + }); + } + + /// + /// Тест на изменение агрегатора у детейла. + /// + [Fact] + public void AggregatorChangeTest() + { + ActODataService(args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Медведь медведь1 = new Медведь { ПорядковыйНомер = 1 }; + Медведь медведь2 = new Медведь { ПорядковыйНомер = 2 }; + args.DataService.UpdateObject(медведь1); + args.DataService.UpdateObject(медведь2); + + Берлога берлога1 = new Берлога { Наименование = "Берлога1", Медведь = медведь1 }; + Берлога берлога2 = new Берлога { Наименование = "Берлога2", Медведь = медведь1 }; + Берлога берлога3 = new Берлога { Наименование = "Берлога3", Медведь = медведь1 }; + args.DataService.UpdateObject(берлога1); + args.DataService.UpdateObject(берлога2); + args.DataService.UpdateObject(берлога3); + + // Обновляем ссылку. + берлога1.Медведь = медведь2; + + // Представление, по которому будем обновлять. + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на медведя. + requestJsonData = ODataHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataHelper.GetRequestUrl(args.Token.Model, берлога1); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + Берлога updatedБерлога = new Берлога { __PrimaryKey = берлога1.__PrimaryKey }; + args.DataService.LoadObject(updatedБерлога); + + Assert.Equal(берлога1.Медведь.__PrimaryKey.ToString(), updatedБерлога.Медведь.__PrimaryKey.ToString()); + } + }); + } + + /// + /// Проверка изменения сложного агрегатора (у которого есть ещё ссылки на другие мастера). + /// + [Fact] + public void ComplexAggregatorChangeTest() + { + ActODataService(args => + { + LegoDevice device1 = new LegoDevice { BlockId = 1, Name = "First" }; + LegoDevice device2 = new LegoDevice { BlockId = 2, Name = "Second" }; + LegoDevice device3 = new LegoDevice { BlockId = 3, Name = "Third" }; + args.DataService.UpdateObject(device1); + args.DataService.UpdateObject(device2); + args.DataService.UpdateObject(device3); + + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + LegoPatent patent = new LegoPatent { Description = "Patent A", BaseLegoBlock = device1 }; + args.DataService.UpdateObject(patent); + + // Обновляем ссылку на агрегатора. + patent.BaseLegoBlock = device2; + + // Представление, по которому будем обновлять. + string[] patentPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + }; + var patentDynamicView = new View(new ViewAttribute("patentDynamicView", patentPropertiesNames), typeof(LegoPatent)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = patent.ToJson(patentDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на агрегатора. + requestJsonData = ODataHelper.AddEntryRelationship(requestJsonData, patentDynamicView, args.Token.Model, device2, nameof(LegoPatent.BaseLegoBlock)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataHelper.GetRequestUrl(args.Token.Model, patent); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + LegoPatent updatedLegoPatent = new LegoPatent { __PrimaryKey = patent.__PrimaryKey }; + args.DataService.LoadObject(updatedLegoPatent); + + Assert.Equal(patent.BaseLegoBlock.__PrimaryKey.ToString(), updatedLegoPatent.BaseLegoBlock.__PrimaryKey.ToString()); + } + }); } } +} diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs new file mode 100644 index 00000000..2979cc0d --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.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; + + /// + /// Тесты для проверки работы UpdateViews. + /// + public class UpdateViewsTest : BaseODataServiceIntegratedTest + { + /// + /// Конструктор по-умолчанию. + /// + /// Фабрика для приложения. + /// Вывод диагностической информации по тестам. + public UpdateViewsTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + : base(factory, output) + { + + } + + /// + /// Проверка работы UpdateView - случай когда в UpdateView не включен мастер. + /// + [Fact] + public void UpdateViewNoMastersTest() + { + ActODataService(args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Медведь медведь1 = new Медведь { ПорядковыйНомер = 1 }; + Медведь медведь2 = new Медведь { ПорядковыйНомер = 2 }; + args.DataService.UpdateObject(медведь1); + args.DataService.UpdateObject(медведь2); + + Берлога берлога1 = new Берлога { Наименование = "Берлога1", Медведь = медведь1 }; + Берлога берлога2 = new Берлога { Наименование = "Берлога2", Медведь = медведь1 }; + Берлога берлога3 = new Берлога { Наименование = "Берлога3", Медведь = медведь1 }; + args.DataService.UpdateObject(берлога1); + args.DataService.UpdateObject(берлога2); + args.DataService.UpdateObject(берлога3); + + // Обновляем ссылку. + берлога1.Медведь = медведь2; + + // Представление, по которому будем обновлять. + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на медведя. + requestJsonData = ODataHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataHelper.GetRequestUrl(args.Token.Model, берлога1); + + // Сейчас обновление мастеров не поддерживается. + Assert.Throws(() => args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result); // Если падает Exception, значит представление поменялось и работает. + }); + } + } +} +#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 ebb6a638..05565b5b 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 @@ -1,1147 +1,1164 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs new file mode 100644 index 00000000..ea2100be --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs @@ -0,0 +1,47 @@ +using ICSSoft.STORMNET; +using ICSSoft.STORMNET.KeyGen; +using NewPlatform.Flexberry.ORM.ODataService.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers +{ + public class ODataHelper + { + /// + /// Добавить запись об изменении ссылки в OData Payload. + /// + /// Исходный payload. + /// Представление исходного объекта. + /// EDM модель. + /// Новый объект данных (по ссылке). + /// Ссылка на новый объект данных. + /// Новый OData Payload. + public static string AddEntryRelationship(string requestJsonData, View view, DataObjectEdmModel model, DataObject dataObject, string relationName) + { + DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonData, view, model); + + objJsonМедв.Add( + $"{relationName}@odata.bind", + string.Format( + "{0}({1})", + model.GetEdmEntitySet(dataObject.GetType()).Name, + ((KeyGuid)dataObject.__PrimaryKey).Guid.ToString("D"))); + + var result = objJsonМедв.Serialize(); + return result; + } + + /// + /// Получить URL для запроса к OData. + /// + /// EDM модель. + /// Объект (по которому выполняется запрос). + /// URL запроса к OData. + public static string GetRequestUrl(DataObjectEdmModel model, DataObject dataObject) + => string.Format("http://localhost/odata/{0}({1})", model.GetEdmEntitySet(dataObject.GetType()).Name, ((KeyGuid)dataObject.__PrimaryKey).Guid.ToString()); + } +} diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs new file mode 100644 index 00000000..d65fbf94 --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs @@ -0,0 +1,76 @@ +#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 UpdateView configuration. + /// + public class UpdateViewsTestStartup : Startup + { + /// + /// Initialize new instance of TestStartup. + /// + /// Configuration for new instance. + public UpdateViewsTestStartup(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 updateViews = new Dictionary() + { + { typeof(Медведь), Медведь.Views.МедведьUpdateView }, + { typeof(Берлога), Берлога.Views.БерлогаUpdateView }, + }; + var modelBuilder = new DefaultDataObjectEdmModelBuilder(assemblies, false, pseudoDetailDefinitions, updateViews: updateViews); + + var token = builder.MapDataObjectRoute(modelBuilder); + + container.RegisterInstance(typeof(ManagementToken), token); + }); + } + } +} +#endif diff --git "a/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" "b/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" index 1dd229ac..7dcc108c 100644 --- "a/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" +++ "b/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" @@ -50,6 +50,11 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests "Сертификат", "СертификатСтрока"})] [MasterViewDefineAttribute("БерлогаE", "ЛесРасположения", ICSSoft.STORMNET.LookupTypeEnum.Standard, "", "Название")] + [View("БерлогаUpdateView", new string[] { + "Наименование as \'Наименование\'", + "Комфортность as \'Комфортность\'", + "Заброшена as \'Заброшена\'", + "ПолеБС"})] public class Берлога : ICSSoft.STORMNET.DataObject { @@ -393,6 +398,17 @@ public static ICSSoft.STORMNET.View БерлогаE return ICSSoft.STORMNET.Information.GetView("БерлогаE", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Берлога)); } } + + /// + /// Представление для тестов UpdateView (без мастеров и детейлов). + /// + public static ICSSoft.STORMNET.View БерлогаUpdateView + { + get + { + return ICSSoft.STORMNET.Information.GetView("БерлогаUpdateView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Берлога)); + } + } } } @@ -419,7 +435,7 @@ public class DetailArrayOfБерлога : ICSSoft.STORMNET.DetailArray /// /// Adds object with type Берлога. /// - public DetailArrayOfБерлога(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМедведь) : + public DetailArrayOfБерлога(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМедведь) : base(typeof(Берлога), ((ICSSoft.STORMNET.DataObject)(fМедведь))) { } diff --git "a/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" "b/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" index 60331c62..854f42cf 100644 --- "a/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" +++ "b/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" @@ -72,6 +72,13 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests "ЛесОбитания.Название as \'Название\'"})] [View("МедведьShort", new string[] { "ПорядковыйНомер as \'Порядковый номер\'"})] + [View("МедведьUpdateView", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "ПолеБС"})] [View("МедведьСДелейломИВычислимымСвойством", new string[] { "ПорядковыйНомер as \'Порядковый номер\'", "Вес as \'Вес\'", @@ -724,6 +731,17 @@ public static ICSSoft.STORMNET.View МедведьShort } } + /// + /// Представление для тестов UpdateView (без мастеров и детейлов). + /// + public static ICSSoft.STORMNET.View МедведьUpdateView + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьUpdateView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + /// /// "МедведьСДелейломИВычислимымСвойством" view. ///