From 361c3e1982aa02557c4f6f405443ed9ec1901f0a Mon Sep 17 00:00:00 2001 From: inaidanov Date: Wed, 17 Jan 2024 16:23:24 +0500 Subject: [PATCH 01/18] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B3=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20=D0=9C=D0=B5=D0=B4?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D1=8F=20=D0=B8=20=D0=91=D0=B5=D1=80=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B8=202024=20=D0=B3=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...21\200\320\273\320\276\320\263\320\260.cs" | 7 +- ...20\264\320\262\320\265\320\264\321\214.cs" | 1662 +++++++++-------- 2 files changed, 838 insertions(+), 831 deletions(-) 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 3467287d..1dd229ac 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" @@ -22,7 +22,12 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests /// Берлога. /// // *** Start programmer edit section *** (Берлога CustomAttributes) - + [View("БерлогаDefaultView", new string[] { + "ПолеБС", + "Наименование as \'Наименование\'", + "Комфортность as \'Комфортность\'", + "Заброшена as \'Заброшена\'", + "ПолеБС"})] // *** End programmer edit section *** (Берлога CustomAttributes) [BusinessServer("NewPlatform.Flexberry.ORM.ODataService.Tests.DenBS, NewPlatform.Flexberry.ORM.ODa" + "taService.Tests.BusinessServers", ICSSoft.STORMNET.Business.DataServiceObjectEvents.OnAllEvents)] 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 870b2994..c379db99 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" @@ -1,830 +1,832 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NewPlatform.Flexberry.ORM.ODataService.Tests -{ - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business.Audit; - using ICSSoft.STORMNET.Business.Audit.Objects; - - - // *** Start programmer edit section *** (Using statements) - - // *** End programmer edit section *** (Using statements) - - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь CustomAttributes) - - // *** End programmer edit section *** (Медведь CustomAttributes) - [BusinessServer("NewPlatform.Flexberry.ORM.ODataService.Tests.BearBS, NewPlatform.Flexberry.ORM.OD" + - "ataService.Tests.BusinessServers", ICSSoft.STORMNET.Business.DataServiceObjectEvents.OnAllEvents)] - [AutoAltered()] - [AccessType(ICSSoft.STORMNET.AccessType.none)] - [View("LoadTestView", new string[] { - "ЛесОбитания", - "ЛесОбитания.Заповедник", - "Мама", - "Мама.ЦветГлаз", - "Вес"})] - [AssociatedDetailViewAttribute("LoadTestView", "Берлога", "LoadTestView", true, "", "", true, new string[] { - ""})] - [View("OrderNumberTest", new string[] { - "ПорядковыйНомер", - "ЛесОбитания"})] - [View("МедведьE", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "Мама as \'Мама\'", - "Мама.ЦветГлаз as \'Цвет глаз\'", - "Мама.Вес", - "Папа as \'Папа\'", - "Папа.ЦветГлаз as \'Цвет глаз\'", - "Папа.Вес", - "ЛесОбитания as \'Лес обитания\'", - "ЛесОбитания.Название as \'Название\'", - "ПолеБС", - "СтранаРождения", - "СтранаРождения.Название"})] - [AssociatedDetailViewAttribute("МедведьE", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { - ""})] - [View("МедведьL", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "Мама.ЦветГлаз as \'Цвет глаз\'", - "Папа.ЦветГлаз as \'Цвет глаз\'", - "ЛесОбитания.Название as \'Название\'"})] - [View("МедведьShort", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'"})] - [View("МедведьСДелейломИВычислимымСвойством", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "Мама as \'Мама\'", - "Мама.ЦветГлаз as \'Цвет глаз\'", - "Папа as \'Папа\'", - "Папа.ЦветГлаз as \'Цвет глаз\'", - "ЛесОбитания as \'Лес обитания\'", - "ЛесОбитания.Название as \'Название\'", - "МедведьСтрокой"})] - [AssociatedDetailViewAttribute("МедведьСДелейломИВычислимымСвойством", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { - ""})] - public class Медведь : ICSSoft.STORMNET.DataObject, IDataObjectWithAuditFields - { - - private int fВес; - - private ICSSoft.STORMNET.UserDataTypes.NullableDateTime fДатаРождения; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.tПол fПол; - - private string fПолеБС; - - private int fПорядковыйНомер; - - private string fЦветГлаз; - - private System.Nullable fCreateTime; - - private string fCreator; - - private string fEditor; - - private System.Nullable fEditTime; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Лес fЛесОбитания; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМама; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fПапа; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Страна fСтранаРождения; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога fБерлога; - - // *** Start programmer edit section *** (Медведь CustomMembers) - - // *** End programmer edit section *** (Медведь CustomMembers) - - - /// - /// Вес. - /// - // *** Start programmer edit section *** (Медведь.Вес CustomAttributes) - - // *** End programmer edit section *** (Медведь.Вес CustomAttributes) - public virtual int Вес - { - get - { - // *** Start programmer edit section *** (Медведь.Вес Get start) - - // *** End programmer edit section *** (Медведь.Вес Get start) - int result = this.fВес; - // *** Start programmer edit section *** (Медведь.Вес Get end) - - // *** End programmer edit section *** (Медведь.Вес Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Вес Set start) - - // *** End programmer edit section *** (Медведь.Вес Set start) - this.fВес = value; - // *** Start programmer edit section *** (Медведь.Вес Set end) - - // *** End programmer edit section *** (Медведь.Вес Set end) - } - } - - /// - /// ВычислимоеПоле. - /// - // *** Start programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) - - // *** End programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) - [ICSSoft.STORMNET.NotStored()] - [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "@ПорядковыйНомер@ + @Вес@")] - public virtual int ВычислимоеПоле - { - get - { - // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Get) - return 0; - // *** End programmer edit section *** (Медведь.ВычислимоеПоле Get) - } - set - { - // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Set) - - // *** End programmer edit section *** (Медведь.ВычислимоеПоле Set) - } - } - - /// - /// ДатаРождения. - /// - // *** Start programmer edit section *** (Медведь.ДатаРождения CustomAttributes) - - // *** End programmer edit section *** (Медведь.ДатаРождения CustomAttributes) - public virtual ICSSoft.STORMNET.UserDataTypes.NullableDateTime ДатаРождения - { - get - { - // *** Start programmer edit section *** (Медведь.ДатаРождения Get start) - - // *** End programmer edit section *** (Медведь.ДатаРождения Get start) - ICSSoft.STORMNET.UserDataTypes.NullableDateTime result = this.fДатаРождения; - // *** Start programmer edit section *** (Медведь.ДатаРождения Get end) - - // *** End programmer edit section *** (Медведь.ДатаРождения Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ДатаРождения Set start) - - // *** End programmer edit section *** (Медведь.ДатаРождения Set start) - this.fДатаРождения = value; - // *** Start programmer edit section *** (Медведь.ДатаРождения Set end) - - // *** End programmer edit section *** (Медведь.ДатаРождения Set end) - } - } - - /// - /// МедведьСтрокой. - /// - // *** Start programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) - - // *** End programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) - [ICSSoft.STORMNET.NotStored()] - [StrLen(255)] - [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "\'ПорядковыйНомер:\' + @ПорядковыйНомер@ + \", Цвет глаз мамы:\" + isnull(@Мама.ЦветГ" + - "лаз@,\'\')")] - public virtual string МедведьСтрокой - { - get - { - // *** Start programmer edit section *** (Медведь.МедведьСтрокой Get) - return null; - // *** End programmer edit section *** (Медведь.МедведьСтрокой Get) - } - set - { - // *** Start programmer edit section *** (Медведь.МедведьСтрокой Set) - - // *** End programmer edit section *** (Медведь.МедведьСтрокой Set) - } - } - - /// - /// Пол. - /// - // *** Start programmer edit section *** (Медведь.Пол CustomAttributes) - - // *** End programmer edit section *** (Медведь.Пол CustomAttributes) - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.tПол Пол - { - get - { - // *** Start programmer edit section *** (Медведь.Пол Get start) - - // *** End programmer edit section *** (Медведь.Пол Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.tПол result = this.fПол; - // *** Start programmer edit section *** (Медведь.Пол Get end) - - // *** End programmer edit section *** (Медведь.Пол Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Пол Set start) - - // *** End programmer edit section *** (Медведь.Пол Set start) - this.fПол = value; - // *** Start programmer edit section *** (Медведь.Пол Set end) - - // *** End programmer edit section *** (Медведь.Пол Set end) - } - } - - /// - /// ПолеБС. - /// - // *** Start programmer edit section *** (Медведь.ПолеБС CustomAttributes) - - // *** End programmer edit section *** (Медведь.ПолеБС CustomAttributes) - [StrLen(255)] - public virtual string ПолеБС - { - get - { - // *** Start programmer edit section *** (Медведь.ПолеБС Get start) - - // *** End programmer edit section *** (Медведь.ПолеБС Get start) - string result = this.fПолеБС; - // *** Start programmer edit section *** (Медведь.ПолеБС Get end) - - // *** End programmer edit section *** (Медведь.ПолеБС Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ПолеБС Set start) - - // *** End programmer edit section *** (Медведь.ПолеБС Set start) - this.fПолеБС = value; - // *** Start programmer edit section *** (Медведь.ПолеБС Set end) - - // *** End programmer edit section *** (Медведь.ПолеБС Set end) - } - } - - /// - /// ПорядковыйНомер. - /// - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) - public virtual int ПорядковыйНомер - { - get - { - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get start) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get start) - int result = this.fПорядковыйНомер; - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get end) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set start) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set start) - this.fПорядковыйНомер = value; - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set end) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set end) - } - } - - /// - /// ЦветГлаз. - /// - // *** Start programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) - - // *** End programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) - [StrLen(255)] - public virtual string ЦветГлаз - { - get - { - // *** Start programmer edit section *** (Медведь.ЦветГлаз Get start) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Get start) - string result = this.fЦветГлаз; - // *** Start programmer edit section *** (Медведь.ЦветГлаз Get end) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ЦветГлаз Set start) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Set start) - this.fЦветГлаз = value; - // *** Start programmer edit section *** (Медведь.ЦветГлаз Set end) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Set end) - } - } - - /// - /// Время создания объекта. - /// - // *** Start programmer edit section *** (Медведь.CreateTime CustomAttributes) - - // *** End programmer edit section *** (Медведь.CreateTime CustomAttributes) - public virtual System.Nullable CreateTime - { - get - { - // *** Start programmer edit section *** (Медведь.CreateTime Get start) - - // *** End programmer edit section *** (Медведь.CreateTime Get start) - System.Nullable result = this.fCreateTime; - // *** Start programmer edit section *** (Медведь.CreateTime Get end) - - // *** End programmer edit section *** (Медведь.CreateTime Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.CreateTime Set start) - - // *** End programmer edit section *** (Медведь.CreateTime Set start) - this.fCreateTime = value; - // *** Start programmer edit section *** (Медведь.CreateTime Set end) - - // *** End programmer edit section *** (Медведь.CreateTime Set end) - } - } - - /// - /// Создатель объекта. - /// - // *** Start programmer edit section *** (Медведь.Creator CustomAttributes) - - // *** End programmer edit section *** (Медведь.Creator CustomAttributes) - [StrLen(255)] - public virtual string Creator - { - get - { - // *** Start programmer edit section *** (Медведь.Creator Get start) - - // *** End programmer edit section *** (Медведь.Creator Get start) - string result = this.fCreator; - // *** Start programmer edit section *** (Медведь.Creator Get end) - - // *** End programmer edit section *** (Медведь.Creator Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Creator Set start) - - // *** End programmer edit section *** (Медведь.Creator Set start) - this.fCreator = value; - // *** Start programmer edit section *** (Медведь.Creator Set end) - - // *** End programmer edit section *** (Медведь.Creator Set end) - } - } - - /// - /// Последний редактор объекта. - /// - // *** Start programmer edit section *** (Медведь.Editor CustomAttributes) - - // *** End programmer edit section *** (Медведь.Editor CustomAttributes) - [StrLen(255)] - public virtual string Editor - { - get - { - // *** Start programmer edit section *** (Медведь.Editor Get start) - - // *** End programmer edit section *** (Медведь.Editor Get start) - string result = this.fEditor; - // *** Start programmer edit section *** (Медведь.Editor Get end) - - // *** End programmer edit section *** (Медведь.Editor Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Editor Set start) - - // *** End programmer edit section *** (Медведь.Editor Set start) - this.fEditor = value; - // *** Start programmer edit section *** (Медведь.Editor Set end) - - // *** End programmer edit section *** (Медведь.Editor Set end) - } - } - - /// - /// Время последнего редактирования объекта. - /// - // *** Start programmer edit section *** (Медведь.EditTime CustomAttributes) - - // *** End programmer edit section *** (Медведь.EditTime CustomAttributes) - public virtual System.Nullable EditTime - { - get - { - // *** Start programmer edit section *** (Медведь.EditTime Get start) - - // *** End programmer edit section *** (Медведь.EditTime Get start) - System.Nullable result = this.fEditTime; - // *** Start programmer edit section *** (Медведь.EditTime Get end) - - // *** End programmer edit section *** (Медведь.EditTime Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.EditTime Set start) - - // *** End programmer edit section *** (Медведь.EditTime Set start) - this.fEditTime = value; - // *** Start programmer edit section *** (Медведь.EditTime Set end) - - // *** End programmer edit section *** (Медведь.EditTime Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) - - // *** End programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) - [PropertyStorage(new string[] { - "ЛесОбитания"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Лес ЛесОбитания - { - get - { - // *** Start programmer edit section *** (Медведь.ЛесОбитания Get start) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Лес result = this.fЛесОбитания; - // *** Start programmer edit section *** (Медведь.ЛесОбитания Get end) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ЛесОбитания Set start) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Set start) - this.fЛесОбитания = value; - // *** Start programmer edit section *** (Медведь.ЛесОбитания Set end) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.Мама CustomAttributes) - - // *** End programmer edit section *** (Медведь.Мама CustomAttributes) - [PropertyStorage(new string[] { - "Мама"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Мама - { - get - { - // *** Start programmer edit section *** (Медведь.Мама Get start) - - // *** End programmer edit section *** (Медведь.Мама Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fМама; - // *** Start programmer edit section *** (Медведь.Мама Get end) - - // *** End programmer edit section *** (Медведь.Мама Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Мама Set start) - - // *** End programmer edit section *** (Медведь.Мама Set start) - this.fМама = value; - // *** Start programmer edit section *** (Медведь.Мама Set end) - - // *** End programmer edit section *** (Медведь.Мама Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.Папа CustomAttributes) - - // *** End programmer edit section *** (Медведь.Папа CustomAttributes) - [PropertyStorage(new string[] { - "Папа"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Папа - { - get - { - // *** Start programmer edit section *** (Медведь.Папа Get start) - - // *** End programmer edit section *** (Медведь.Папа Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fПапа; - // *** Start programmer edit section *** (Медведь.Папа Get end) - - // *** End programmer edit section *** (Медведь.Папа Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Папа Set start) - - // *** End programmer edit section *** (Медведь.Папа Set start) - this.fПапа = value; - // *** Start programmer edit section *** (Медведь.Папа Set end) - - // *** End programmer edit section *** (Медведь.Папа Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.СтранаРождения CustomAttributes) - - // *** End programmer edit section *** (Медведь.СтранаРождения CustomAttributes) - [PropertyStorage(new string[] { - "Страна"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Страна СтранаРождения - { - get - { - // *** Start programmer edit section *** (Медведь.СтранаРождения Get start) - - // *** End programmer edit section *** (Медведь.СтранаРождения Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Страна result = this.fСтранаРождения; - // *** Start programmer edit section *** (Медведь.СтранаРождения Get end) - - // *** End programmer edit section *** (Медведь.СтранаРождения Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.СтранаРождения Set start) - - // *** End programmer edit section *** (Медведь.СтранаРождения Set start) - this.fСтранаРождения = value; - // *** Start programmer edit section *** (Медведь.СтранаРождения Set end) - - // *** End programmer edit section *** (Медведь.СтранаРождения Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.Берлога CustomAttributes) - - // *** End programmer edit section *** (Медведь.Берлога CustomAttributes) - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога Берлога - { - get - { - // *** Start programmer edit section *** (Медведь.Берлога Get start) - - // *** End programmer edit section *** (Медведь.Берлога Get start) - if ((this.fБерлога == null)) - { - this.fБерлога = new NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога(this); - } - NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога result = this.fБерлога; - // *** Start programmer edit section *** (Медведь.Берлога Get end) - - // *** End programmer edit section *** (Медведь.Берлога Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Берлога Set start) - - // *** End programmer edit section *** (Медведь.Берлога Set start) - this.fБерлога = value; - // *** Start programmer edit section *** (Медведь.Берлога Set end) - - // *** End programmer edit section *** (Медведь.Берлога Set end) - } - } - - /// - /// Class views container. - /// - public class Views - { - - /// - /// Представление для работы тестов на загрузку объектов. - /// - public static ICSSoft.STORMNET.View LoadTestView - { - get - { - return ICSSoft.STORMNET.Information.GetView("LoadTestView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// Представление для работы теста по использованию порядкового номера. - /// - public static ICSSoft.STORMNET.View OrderNumberTest - { - get - { - return ICSSoft.STORMNET.Information.GetView("OrderNumberTest", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьE" view. - /// - public static ICSSoft.STORMNET.View МедведьE - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьE", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьL" view. - /// - public static ICSSoft.STORMNET.View МедведьL - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьL", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьShort" view. - /// - public static ICSSoft.STORMNET.View МедведьShort - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьShort", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьСДелейломИВычислимымСвойством" view. - /// - public static ICSSoft.STORMNET.View МедведьСДелейломИВычислимымСвойством - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьСДелейломИВычислимымСвойством", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - } - - /// - /// Audit class settings. - /// - public class AuditSettings - { - - /// - /// Включён ли аудит для класса. - /// - public static bool AuditEnabled = true; - - /// - /// Использовать имя представления для аудита по умолчанию. - /// - public static bool UseDefaultView = true; - - /// - /// Включён ли аудит операции чтения. - /// - public static bool SelectAudit = true; - - /// - /// Имя представления для аудирования операции чтения. - /// - public static string SelectAuditViewName = "AuditView"; - - /// - /// Включён ли аудит операции создания. - /// - public static bool InsertAudit = true; - - /// - /// Имя представления для аудирования операции создания. - /// - public static string InsertAuditViewName = "AuditView"; - - /// - /// Включён ли аудит операции изменения. - /// - public static bool UpdateAudit = true; - - /// - /// Имя представления для аудирования операции изменения. - /// - public static string UpdateAuditViewName = "AuditView"; - - /// - /// Включён ли аудит операции удаления. - /// - public static bool DeleteAudit = true; - - /// - /// Имя представления для аудирования операции удаления. - /// - public static string DeleteAuditViewName = "AuditView"; - - /// - /// Путь к форме просмотра результатов аудита. - /// - public static string FormUrl = ""; - - /// - /// Режим записи данных аудита (синхронный или асинхронный). - /// - public static ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode WriteMode = ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode.Synchronous; - - /// - /// Максимальная длина сохраняемого значения поля (если 0, то строка обрезаться не будет). - /// - public static int PrunningLength = 0; - - /// - /// Показывать ли пользователям в изменениях первичные ключи. - /// - public static bool ShowPrimaryKey = false; - - /// - /// Сохранять ли старое значение. - /// - public static bool KeepOldValue = true; - - /// - /// Сжимать ли сохраняемые значения. - /// - public static bool Compress = false; - - /// - /// Сохранять ли все значения атрибутов, а не только изменяемые. - /// - public static bool KeepAllValues = false; - } - } -} - +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NewPlatform.Flexberry.ORM.ODataService.Tests +{ + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business.Audit; + using ICSSoft.STORMNET.Business.Audit.Objects; + + + // *** Start programmer edit section *** (Using statements) + + // *** End programmer edit section *** (Using statements) + + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь CustomAttributes) + + // *** End programmer edit section *** (Медведь CustomAttributes) + [BusinessServer("NewPlatform.Flexberry.ORM.ODataService.Tests.BearBS, NewPlatform.Flexberry.ORM.OD" + + "ataService.Tests.BusinessServers", ICSSoft.STORMNET.Business.DataServiceObjectEvents.OnAllEvents)] + [AutoAltered()] + [AccessType(ICSSoft.STORMNET.AccessType.none)] + [View("LoadTestView", new string[] { + "ЛесОбитания", + "ЛесОбитания.Заповедник", + "Мама", + "Мама.ЦветГлаз", + "Вес"})] + [AssociatedDetailViewAttribute("LoadTestView", "Берлога", "LoadTestView", true, "", "", true, new string[] { + ""})] + [View("OrderNumberTest", new string[] { + "ПорядковыйНомер", + "ЛесОбитания"})] + [View("МедведьE", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "Мама as \'Мама\'", + "Мама.ЦветГлаз as \'Цвет глаз\'", + "Мама.Вес", + "Папа as \'Папа\'", + "Папа.ЦветГлаз as \'Цвет глаз\'", + "Папа.Вес", + "ЛесОбитания as \'Лес обитания\'", + "ЛесОбитания.Название as \'Название\'", + "ПолеБС", + "СтранаРождения", + "СтранаРождения.Название"})] + [AssociatedDetailViewAttribute("МедведьE", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { + ""})] + [View("МедведьL", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "Мама.ЦветГлаз as \'Цвет глаз\'", + "Папа.ЦветГлаз as \'Цвет глаз\'", + "ЛесОбитания.Название as \'Название\'"})] + [View("МедведьShort", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'"})] + [View("МедведьСДелейломИВычислимымСвойством", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "Мама as \'Мама\'", + "Мама.ЦветГлаз as \'Цвет глаз\'", + "Папа as \'Папа\'", + "Папа.ЦветГлаз as \'Цвет глаз\'", + "ЛесОбитания as \'Лес обитания\'", + "ЛесОбитания.Название as \'Название\'", + "МедведьСтрокой"})] + [AssociatedDetailViewAttribute("МедведьСДелейломИВычислимымСвойством", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { + ""})] + public class Медведь : ICSSoft.STORMNET.DataObject, IDataObjectWithAuditFields + { + + private int fВес; + + private ICSSoft.STORMNET.UserDataTypes.NullableDateTime fДатаРождения; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.tПол fПол; + + private string fПолеБС; + + private int fПорядковыйНомер; + + private string fЦветГлаз; + + private System.Nullable fCreateTime; + + private string fCreator; + + private string fEditor; + + private System.Nullable fEditTime; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Лес fЛесОбитания; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМама; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fПапа; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Страна fСтранаРождения; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога fБерлога; + + // *** Start programmer edit section *** (Медведь CustomMembers) + + // *** End programmer edit section *** (Медведь CustomMembers) + + + /// + /// Вес. + /// + // *** Start programmer edit section *** (Медведь.Вес CustomAttributes) + + // *** End programmer edit section *** (Медведь.Вес CustomAttributes) + public virtual int Вес + { + get + { + // *** Start programmer edit section *** (Медведь.Вес Get start) + + // *** End programmer edit section *** (Медведь.Вес Get start) + int result = this.fВес; + // *** Start programmer edit section *** (Медведь.Вес Get end) + + // *** End programmer edit section *** (Медведь.Вес Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Вес Set start) + + // *** End programmer edit section *** (Медведь.Вес Set start) + this.fВес = value; + // *** Start programmer edit section *** (Медведь.Вес Set end) + + // *** End programmer edit section *** (Медведь.Вес Set end) + } + } + + /// + /// ВычислимоеПоле. + /// + // *** Start programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) + + // *** End programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) + [ICSSoft.STORMNET.NotStored()] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "@ПорядковыйНомер@ + @Вес@")] + public virtual int ВычислимоеПоле + { + get + { + // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Get) + return 0; + // *** End programmer edit section *** (Медведь.ВычислимоеПоле Get) + } + set + { + // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Set) + + // *** End programmer edit section *** (Медведь.ВычислимоеПоле Set) + } + } + + /// + /// ДатаРождения. + /// + // *** Start programmer edit section *** (Медведь.ДатаРождения CustomAttributes) + + // *** End programmer edit section *** (Медведь.ДатаРождения CustomAttributes) + public virtual ICSSoft.STORMNET.UserDataTypes.NullableDateTime ДатаРождения + { + get + { + // *** Start programmer edit section *** (Медведь.ДатаРождения Get start) + + // *** End programmer edit section *** (Медведь.ДатаРождения Get start) + ICSSoft.STORMNET.UserDataTypes.NullableDateTime result = this.fДатаРождения; + // *** Start programmer edit section *** (Медведь.ДатаРождения Get end) + + // *** End programmer edit section *** (Медведь.ДатаРождения Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ДатаРождения Set start) + + // *** End programmer edit section *** (Медведь.ДатаРождения Set start) + this.fДатаРождения = value; + // *** Start programmer edit section *** (Медведь.ДатаРождения Set end) + + // *** End programmer edit section *** (Медведь.ДатаРождения Set end) + } + } + + /// + /// МедведьСтрокой. + /// + // *** Start programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) + + // *** End programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) + [ICSSoft.STORMNET.NotStored()] + [StrLen(255)] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "\'ПорядковыйНомер:\' + @ПорядковыйНомер@ + \", Цвет глаз мамы:\" + isnull(@Мама.ЦветГ" + + "лаз@,\'\')")] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.PostgresDataService), "\'ПорядковыйНомер:\' || @ПорядковыйНомер@ || \", Цвет глаз мамы:\" || coalesce(@Мама.ЦветГ" + + "лаз@,\'\')")] + public virtual string МедведьСтрокой + { + get + { + // *** Start programmer edit section *** (Медведь.МедведьСтрокой Get) + return null; + // *** End programmer edit section *** (Медведь.МедведьСтрокой Get) + } + set + { + // *** Start programmer edit section *** (Медведь.МедведьСтрокой Set) + + // *** End programmer edit section *** (Медведь.МедведьСтрокой Set) + } + } + + /// + /// Пол. + /// + // *** Start programmer edit section *** (Медведь.Пол CustomAttributes) + + // *** End programmer edit section *** (Медведь.Пол CustomAttributes) + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.tПол Пол + { + get + { + // *** Start programmer edit section *** (Медведь.Пол Get start) + + // *** End programmer edit section *** (Медведь.Пол Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.tПол result = this.fПол; + // *** Start programmer edit section *** (Медведь.Пол Get end) + + // *** End programmer edit section *** (Медведь.Пол Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Пол Set start) + + // *** End programmer edit section *** (Медведь.Пол Set start) + this.fПол = value; + // *** Start programmer edit section *** (Медведь.Пол Set end) + + // *** End programmer edit section *** (Медведь.Пол Set end) + } + } + + /// + /// ПолеБС. + /// + // *** Start programmer edit section *** (Медведь.ПолеБС CustomAttributes) + + // *** End programmer edit section *** (Медведь.ПолеБС CustomAttributes) + [StrLen(255)] + public virtual string ПолеБС + { + get + { + // *** Start programmer edit section *** (Медведь.ПолеБС Get start) + + // *** End programmer edit section *** (Медведь.ПолеБС Get start) + string result = this.fПолеБС; + // *** Start programmer edit section *** (Медведь.ПолеБС Get end) + + // *** End programmer edit section *** (Медведь.ПолеБС Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ПолеБС Set start) + + // *** End programmer edit section *** (Медведь.ПолеБС Set start) + this.fПолеБС = value; + // *** Start programmer edit section *** (Медведь.ПолеБС Set end) + + // *** End programmer edit section *** (Медведь.ПолеБС Set end) + } + } + + /// + /// ПорядковыйНомер. + /// + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) + public virtual int ПорядковыйНомер + { + get + { + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get start) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get start) + int result = this.fПорядковыйНомер; + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get end) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set start) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set start) + this.fПорядковыйНомер = value; + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set end) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set end) + } + } + + /// + /// ЦветГлаз. + /// + // *** Start programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) + + // *** End programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) + [StrLen(255)] + public virtual string ЦветГлаз + { + get + { + // *** Start programmer edit section *** (Медведь.ЦветГлаз Get start) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Get start) + string result = this.fЦветГлаз; + // *** Start programmer edit section *** (Медведь.ЦветГлаз Get end) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ЦветГлаз Set start) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Set start) + this.fЦветГлаз = value; + // *** Start programmer edit section *** (Медведь.ЦветГлаз Set end) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Set end) + } + } + + /// + /// Время создания объекта. + /// + // *** Start programmer edit section *** (Медведь.CreateTime CustomAttributes) + + // *** End programmer edit section *** (Медведь.CreateTime CustomAttributes) + public virtual System.Nullable CreateTime + { + get + { + // *** Start programmer edit section *** (Медведь.CreateTime Get start) + + // *** End programmer edit section *** (Медведь.CreateTime Get start) + System.Nullable result = this.fCreateTime; + // *** Start programmer edit section *** (Медведь.CreateTime Get end) + + // *** End programmer edit section *** (Медведь.CreateTime Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.CreateTime Set start) + + // *** End programmer edit section *** (Медведь.CreateTime Set start) + this.fCreateTime = value; + // *** Start programmer edit section *** (Медведь.CreateTime Set end) + + // *** End programmer edit section *** (Медведь.CreateTime Set end) + } + } + + /// + /// Создатель объекта. + /// + // *** Start programmer edit section *** (Медведь.Creator CustomAttributes) + + // *** End programmer edit section *** (Медведь.Creator CustomAttributes) + [StrLen(255)] + public virtual string Creator + { + get + { + // *** Start programmer edit section *** (Медведь.Creator Get start) + + // *** End programmer edit section *** (Медведь.Creator Get start) + string result = this.fCreator; + // *** Start programmer edit section *** (Медведь.Creator Get end) + + // *** End programmer edit section *** (Медведь.Creator Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Creator Set start) + + // *** End programmer edit section *** (Медведь.Creator Set start) + this.fCreator = value; + // *** Start programmer edit section *** (Медведь.Creator Set end) + + // *** End programmer edit section *** (Медведь.Creator Set end) + } + } + + /// + /// Последний редактор объекта. + /// + // *** Start programmer edit section *** (Медведь.Editor CustomAttributes) + + // *** End programmer edit section *** (Медведь.Editor CustomAttributes) + [StrLen(255)] + public virtual string Editor + { + get + { + // *** Start programmer edit section *** (Медведь.Editor Get start) + + // *** End programmer edit section *** (Медведь.Editor Get start) + string result = this.fEditor; + // *** Start programmer edit section *** (Медведь.Editor Get end) + + // *** End programmer edit section *** (Медведь.Editor Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Editor Set start) + + // *** End programmer edit section *** (Медведь.Editor Set start) + this.fEditor = value; + // *** Start programmer edit section *** (Медведь.Editor Set end) + + // *** End programmer edit section *** (Медведь.Editor Set end) + } + } + + /// + /// Время последнего редактирования объекта. + /// + // *** Start programmer edit section *** (Медведь.EditTime CustomAttributes) + + // *** End programmer edit section *** (Медведь.EditTime CustomAttributes) + public virtual System.Nullable EditTime + { + get + { + // *** Start programmer edit section *** (Медведь.EditTime Get start) + + // *** End programmer edit section *** (Медведь.EditTime Get start) + System.Nullable result = this.fEditTime; + // *** Start programmer edit section *** (Медведь.EditTime Get end) + + // *** End programmer edit section *** (Медведь.EditTime Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.EditTime Set start) + + // *** End programmer edit section *** (Медведь.EditTime Set start) + this.fEditTime = value; + // *** Start programmer edit section *** (Медведь.EditTime Set end) + + // *** End programmer edit section *** (Медведь.EditTime Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) + + // *** End programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) + [PropertyStorage(new string[] { + "ЛесОбитания"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Лес ЛесОбитания + { + get + { + // *** Start programmer edit section *** (Медведь.ЛесОбитания Get start) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Лес result = this.fЛесОбитания; + // *** Start programmer edit section *** (Медведь.ЛесОбитания Get end) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ЛесОбитания Set start) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Set start) + this.fЛесОбитания = value; + // *** Start programmer edit section *** (Медведь.ЛесОбитания Set end) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.Мама CustomAttributes) + + // *** End programmer edit section *** (Медведь.Мама CustomAttributes) + [PropertyStorage(new string[] { + "Мама"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Мама + { + get + { + // *** Start programmer edit section *** (Медведь.Мама Get start) + + // *** End programmer edit section *** (Медведь.Мама Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fМама; + // *** Start programmer edit section *** (Медведь.Мама Get end) + + // *** End programmer edit section *** (Медведь.Мама Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Мама Set start) + + // *** End programmer edit section *** (Медведь.Мама Set start) + this.fМама = value; + // *** Start programmer edit section *** (Медведь.Мама Set end) + + // *** End programmer edit section *** (Медведь.Мама Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.Папа CustomAttributes) + + // *** End programmer edit section *** (Медведь.Папа CustomAttributes) + [PropertyStorage(new string[] { + "Папа"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Папа + { + get + { + // *** Start programmer edit section *** (Медведь.Папа Get start) + + // *** End programmer edit section *** (Медведь.Папа Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fПапа; + // *** Start programmer edit section *** (Медведь.Папа Get end) + + // *** End programmer edit section *** (Медведь.Папа Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Папа Set start) + + // *** End programmer edit section *** (Медведь.Папа Set start) + this.fПапа = value; + // *** Start programmer edit section *** (Медведь.Папа Set end) + + // *** End programmer edit section *** (Медведь.Папа Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.СтранаРождения CustomAttributes) + + // *** End programmer edit section *** (Медведь.СтранаРождения CustomAttributes) + [PropertyStorage(new string[] { + "Страна"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Страна СтранаРождения + { + get + { + // *** Start programmer edit section *** (Медведь.СтранаРождения Get start) + + // *** End programmer edit section *** (Медведь.СтранаРождения Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Страна result = this.fСтранаРождения; + // *** Start programmer edit section *** (Медведь.СтранаРождения Get end) + + // *** End programmer edit section *** (Медведь.СтранаРождения Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.СтранаРождения Set start) + + // *** End programmer edit section *** (Медведь.СтранаРождения Set start) + this.fСтранаРождения = value; + // *** Start programmer edit section *** (Медведь.СтранаРождения Set end) + + // *** End programmer edit section *** (Медведь.СтранаРождения Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.Берлога CustomAttributes) + + // *** End programmer edit section *** (Медведь.Берлога CustomAttributes) + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога Берлога + { + get + { + // *** Start programmer edit section *** (Медведь.Берлога Get start) + + // *** End programmer edit section *** (Медведь.Берлога Get start) + if ((this.fБерлога == null)) + { + this.fБерлога = new NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога(this); + } + NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога result = this.fБерлога; + // *** Start programmer edit section *** (Медведь.Берлога Get end) + + // *** End programmer edit section *** (Медведь.Берлога Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Берлога Set start) + + // *** End programmer edit section *** (Медведь.Берлога Set start) + this.fБерлога = value; + // *** Start programmer edit section *** (Медведь.Берлога Set end) + + // *** End programmer edit section *** (Медведь.Берлога Set end) + } + } + + /// + /// Class views container. + /// + public class Views + { + + /// + /// Представление для работы тестов на загрузку объектов. + /// + public static ICSSoft.STORMNET.View LoadTestView + { + get + { + return ICSSoft.STORMNET.Information.GetView("LoadTestView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// Представление для работы теста по использованию порядкового номера. + /// + public static ICSSoft.STORMNET.View OrderNumberTest + { + get + { + return ICSSoft.STORMNET.Information.GetView("OrderNumberTest", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьE" view. + /// + public static ICSSoft.STORMNET.View МедведьE + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьE", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьL" view. + /// + public static ICSSoft.STORMNET.View МедведьL + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьL", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьShort" view. + /// + public static ICSSoft.STORMNET.View МедведьShort + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьShort", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьСДелейломИВычислимымСвойством" view. + /// + public static ICSSoft.STORMNET.View МедведьСДелейломИВычислимымСвойством + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьСДелейломИВычислимымСвойством", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + } + + /// + /// Audit class settings. + /// + public class AuditSettings + { + + /// + /// Включён ли аудит для класса. + /// + public static bool AuditEnabled = true; + + /// + /// Использовать имя представления для аудита по умолчанию. + /// + public static bool UseDefaultView = true; + + /// + /// Включён ли аудит операции чтения. + /// + public static bool SelectAudit = true; + + /// + /// Имя представления для аудирования операции чтения. + /// + public static string SelectAuditViewName = "AuditView"; + + /// + /// Включён ли аудит операции создания. + /// + public static bool InsertAudit = true; + + /// + /// Имя представления для аудирования операции создания. + /// + public static string InsertAuditViewName = "AuditView"; + + /// + /// Включён ли аудит операции изменения. + /// + public static bool UpdateAudit = true; + + /// + /// Имя представления для аудирования операции изменения. + /// + public static string UpdateAuditViewName = "AuditView"; + + /// + /// Включён ли аудит операции удаления. + /// + public static bool DeleteAudit = true; + + /// + /// Имя представления для аудирования операции удаления. + /// + public static string DeleteAuditViewName = "AuditView"; + + /// + /// Путь к форме просмотра результатов аудита. + /// + public static string FormUrl = ""; + + /// + /// Режим записи данных аудита (синхронный или асинхронный). + /// + public static ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode WriteMode = ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode.Synchronous; + + /// + /// Максимальная длина сохраняемого значения поля (если 0, то строка обрезаться не будет). + /// + public static int PrunningLength = 0; + + /// + /// Показывать ли пользователям в изменениях первичные ключи. + /// + public static bool ShowPrimaryKey = false; + + /// + /// Сохранять ли старое значение. + /// + public static bool KeepOldValue = true; + + /// + /// Сжимать ли сохраняемые значения. + /// + public static bool Compress = false; + + /// + /// Сохранять ли все значения атрибутов, а не только изменяемые. + /// + public static bool KeepAllValues = false; + } + } +} + From 252324d906f2f74182b37f4ec005a36fc4e0fec9 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 23 Jan 2024 11:08:04 +0500 Subject: [PATCH 02/18] =?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 --- .../DataObjectController.ModifyData.cs | 2160 +++++----- .../Extensions/DataServiceExtensions.cs | 31 + .../Model/DataObjectEdmModel.cs | 33 +- .../Model/DataObjectEdmTypeSettings.cs | 5 + .../Model/DefaultDataObjectEdmModelBuilder.cs | 72 +- .../CRUD/Update/ModifyDataTest.cs | 3703 +++++++++-------- .../CRUD/Update/UpdateViewsTest.cs | 77 + ...tform.Flexberry.ORM.ODataService.Tests.crp | 2666 ++++++------ .../Helpers/ODataHelper.cs | 47 + .../Startups/UpdateViewsTestStartup.cs | 76 + ...21\200\320\273\320\276\320\263\320\260.cs" | 18 +- ...20\264\320\262\320\265\320\264\321\214.cs" | 18 + 12 files changed, 4713 insertions(+), 4193 deletions(-) create mode 100644 Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs create mode 100644 Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs create mode 100644 Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 9b8ce899..fd604103 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -1,1078 +1,1082 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Controllers -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Text; - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET.FunctionalLanguage; - using Microsoft.AspNet.OData; - using Microsoft.OData.Edm; - using NewPlatform.Flexberry.ORM.ODataService.Batch; - using NewPlatform.Flexberry.ORM.ODataService.Extensions; - using NewPlatform.Flexberry.ORM.ODataService.Files; - using NewPlatform.Flexberry.ORM.ODataService.Files.Providers; - using NewPlatform.Flexberry.ORM.ODataService.Formatter; - using Newtonsoft.Json; - using File = ICSSoft.STORMNET.FileType.File; - using KeySegment = Microsoft.OData.UriParser.KeySegment; - -#if NETFRAMEWORK - using System.Net.Http.Formatting; - using System.Web.Http; - using System.Web.Http.Results; - using System.Web.Http.Validation; - using NewPlatform.Flexberry.ORM.ODataService.Events; - using NewPlatform.Flexberry.ORM.ODataService.Handlers; -#endif -#if NETSTANDARD - using Microsoft.AspNet.OData.Formatter; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Mvc.Formatters; - using Microsoft.Extensions.Primitives; - using NewPlatform.Flexberry.ORM.ODataService.Middleware; -#endif - - /// - /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. - /// - public partial class DataObjectController - { - /// - /// Метаданные файлов, временно загруженных в каталог файлового хранилища и привязанных к свойствам обрабатываемых объектов данных. - /// Файлы будут удалены из файловой системы - /// в случае успешного сохранения объектов данных. - /// - private List _removingFileDescriptions = new List(); - - /// - /// Кэш типов, у которых одинакового типа детейлы и мастера. - /// - private List _typesWithSameDetailAndMaster = new List(); - - /// - /// Кэш типов, у которых нет одинакового типа детейлов и мастеров. - /// - private List _typesWithNotSameDetailAndMaster = new List(); - - /// - /// Создание сущности и всех связанных. При существовании в БД произойдёт обновление. - /// - /// Создаваемая сущность. - /// Созданная сущность. -#if NETFRAMEWORK - public HttpResponseMessage Post([FromBody] EdmEntityObject edmEntity) -#elif NETSTANDARD - public IActionResult Post([FromBody] EdmEntityObject edmEntity) -#endif - { - try - { - if (edmEntity == null) - { - edmEntity = ReplaceOdataBindNull(); - } - - DataObject obj = UpdateObject(edmEntity, null); - ExecuteCallbackAfterCreate(obj); - - edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); - var responseForPreferMinimal = TestPreferMinimal(); - if (responseForPreferMinimal != null) - { - return responseForPreferMinimal; - } - -#if NETFRAMEWORK - var result = Request.CreateResponse(HttpStatusCode.Created, edmEntity); - if (Request.Headers.Contains("Prefer")) - { - result.Headers.Add("Preference-Applied", "return=representation"); - } -#elif NETSTANDARD - var result = new ObjectResult(edmEntity) { StatusCode = StatusCodes.Status201Created }; - if (Request.Headers.ContainsKey("Prefer")) - { - Response.Headers.Add("Preference-Applied", "return=representation"); - } -#endif - - return result; - } - catch (Exception ex) - { -#if NETFRAMEWORK - return InternalServerErrorMessage(ex); -#elif NETSTANDARD - throw CustomException(ex); -#endif - } - } - - /// - /// Обновление сущности (свойства могут быть заданы частично, т.е. указывать можно значения только измененных свойств). - /// Если сущности с заданным ключом нет в БД происходит Upsert (в соответствии со стандартом). - /// - /// Ключ обновляемой сущности. - /// Обновляемая сущность. - /// Обновлённая сущность. -#if NETFRAMEWORK - public HttpResponseMessage Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) -#elif NETSTANDARD - public IActionResult Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) -#endif - { - try - { - if (key == null) - { - throw new ArgumentNullException("key"); - } - - if (edmEntity == null) - { - edmEntity = ReplaceOdataBindNull(); - } - - IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; - -#if NETFRAMEWORK - var dictionary = Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? - (Dictionary)Request.Properties[ExtendedODataEntityDeserializer.Dictionary] : - new Dictionary(); -#elif NETSTANDARD - var dictionary = Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? - (Dictionary)Request.HttpContext.Items[ExtendedODataEntityDeserializer.Dictionary] : - new Dictionary(); -#endif - - foreach (var prop in entityType.Properties()) - { - if (!dictionary.ContainsKey(prop.Name) && edmEntity.GetChangedPropertyNames().Contains(prop.Name) && prop is EdmNavigationProperty) - { - const string msg = "Error processing request stream. Deep updates are not supported in PUT or PATCH operations."; -#if NETFRAMEWORK - return Request.CreateResponse(HttpStatusCode.BadRequest, msg); -#elif NETSTANDARD - return BadRequest(msg); -#endif - } - - if (dictionary.ContainsKey(prop.Name) && dictionary[prop.Name] == null && - (!prop.Type.IsNullable || prop.Type.IsCollection())) - { - string msg = $"The property {prop.Name} can not be null."; -#if NETFRAMEWORK - return Request.CreateResponse(HttpStatusCode.BadRequest, msg); -#elif NETSTANDARD - return BadRequest(msg); -#endif - } - } - - DataObject obj = UpdateObject(edmEntity, key); - ExecuteCallbackAfterUpdate(obj); - - var responseForPreferMinimal = TestPreferMinimal(); - if (responseForPreferMinimal != null) - { - return responseForPreferMinimal; - } - -#if NETFRAMEWORK - if (!Request.Headers.Contains("Prefer")) - { - return Request.CreateResponse(HttpStatusCode.NoContent); - } -#elif NETSTANDARD - if (!Request.Headers.ContainsKey("Prefer")) - { - return NoContent(); - } -#endif - - edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); -#if NETFRAMEWORK - var result = Request.CreateResponse(HttpStatusCode.OK, edmEntity); - result.Headers.Add("Preference-Applied", "return=representation"); -#elif NETSTANDARD - var result = Ok(edmEntity); - Response.Headers.Add("Preference-Applied", "return=representation"); -#endif - return result; - } - catch (Exception ex) - { -#if NETFRAMEWORK - return InternalServerErrorMessage(ex); -#elif NETSTANDARD - throw CustomException(ex); -#endif - } - } - - /// - /// Осуществляет удаление сущности. - /// - /// - /// Результат выполнения запроса типа , соответствующий статусу . - /// -#if NETFRAMEWORK - public HttpResponseMessage DeleteString() -#elif NETSTANDARD - public NoContentResult DeleteString() -#endif - { - var keySegment = ODataPath.Segments[1] as KeySegment; - string key = keySegment.Keys.First().Value.ToString().Trim().Replace("'", string.Empty); - return DeleteEntity(key); - } - - /// - /// Осуществляет удаление сущности. - /// - /// - /// Результат выполнения запроса типа , соответствующий статусу . - /// -#if NETFRAMEWORK - public HttpResponseMessage DeleteGuid() -#elif NETSTANDARD - public NoContentResult DeleteGuid() -#endif - { - var keySegment = ODataPath.Segments[1] as KeySegment; - Guid key = new Guid(keySegment.Keys.First().Value.ToString()); - return DeleteEntity(key); - } - -#if NETFRAMEWORK - private HttpResponseMessage DeleteEntity(object key) -#elif NETSTANDARD - private NoContentResult DeleteEntity(object key) -#endif - { - try - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - Init(); - - /* В ситуации, когда мастер и детейл одного типа, ORM без подгрузки копии данных не может корректно разобрать порядок, - * в котором объекты должны быть удалены. - */ - bool needDataCopyLoad = _typesWithSameDetailAndMaster.Contains(type); - if (!_typesWithNotSameDetailAndMaster.Contains(type) && !needDataCopyLoad) - { - string[] props = Information.GetAllPropertyNames(type); - int length = props.Length; - int index = 0; - - while(!needDataCopyLoad && index < length) - { - string prop = props[index]; - Type propType = Information.GetPropertyType(type, prop); - needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; - - index++; - } - } - - DataObject obj = null; - if (!needDataCopyLoad) - { - obj = DataObjectCache.CreateDataObject(type, key); - _typesWithNotSameDetailAndMaster.Add(type); - } - else - { - obj = LoadObject(type, key.ToString()); - _typesWithSameDetailAndMaster.Add(type); - } - - // Удаляем объект с заданным ключем. - // Детейлы удалятся вместе с агрегатором автоматически. - // Если удаляемый объект является мастером для какого-либо объекта, то - // спецификация предполагает, что зависимые объекты будут каскадно удалены либо ссылки в них заменены - // на null/значению по умолчанию, если это задано в модели через ReferentialConstraints. - // Но если это задано в модели, то соответвующие объекты данных реализуют интерфейсы - // IReferencesCascadeDelete/IReferencesNullDelete и требуемые действия будут выполнены автоматически. - // В данный момент ReferentialConstraints не создаются в модели. - obj.SetStatus(ObjectStatus.Deleted); - - // Раз объект данных удаляется, то и все ассоциированные с ним файлы должны быть удалены. - // Запоминаем метаданные всех ассоциированных файлов, кроме файлов соответствующих файловым свойствам типа File - // (файлы соответствующие свойствам типа File хранятся в БД, и из файловой системы просто нечего удалять). - // TODO: подумать как быть с детейлами, детейлами детейлов, и т д. - var descriptions = _dataObjectFileAccessor.GetDataObjectFileDescriptions(_dataService, obj, new List { typeof(File) }); - _removingFileDescriptions.AddRange(descriptions); - - List objs = new List(); - - if (ExecuteCallbackBeforeDelete(obj)) - { - string agregatorPropertyName = Information.GetAgregatePropertyName(type); - if (!string.IsNullOrEmpty(agregatorPropertyName)) - { - DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); - - if (agregator != null) - { - objs.Add(agregator); - } - } - - objs.Add(obj); - - if (IsBatchChangeSetRequest) - { -#if NETFRAMEWORK - List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; - -#elif NETSTANDARD - List dataObjectsToUpdate = (List)HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; -#endif - dataObjectsToUpdate.AddRange(objs); - allProcessedObjects.Add(obj); - } - else - { - DataObject[] dataObjects = objs.ToArray(); - _dataService.UpdateObjects(ref dataObjects); - } - } - - // При успешном удалении вычищаем из файловой системы, файлы подлежащие удалению. - _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); - ExecuteCallbackAfterDelete(obj); - -#if NETFRAMEWORK - return Request.CreateResponse(HttpStatusCode.NoContent); -#elif NETSTANDARD - return NoContent(); -#endif - } - catch (Exception ex) - { - _removingFileDescriptions.Clear(); -#if NETFRAMEWORK - return InternalServerErrorMessage(ex); -#elif NETSTANDARD - throw CustomException(ex); -#endif - } - } - -#if NETFRAMEWORK - /// - /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. - /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. - /// - /// Ошибка сервиса. - /// Http-ответ. - private HttpResponseMessage InternalServerErrorMessage(Exception ex) - { - return InternalServerErrorMessage(ex, _events, Request); - } - - /// - /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. - /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. - /// - /// Ошибка сервиса. - /// The container with registered events. - /// Original HTTP request message for create a response. - /// Http-ответ. - public static HttpResponseMessage InternalServerErrorMessage(Exception ex, IEventHandlerContainer events, HttpRequestMessage request) - { - HttpStatusCode code = HttpStatusCode.InternalServerError; - Exception originalEx = ex; - - if (events?.CallbackAfterInternalServerError != null) - { - ex = events?.CallbackAfterInternalServerError(ex, ref code); - } - - if (ex == null) - { - ex = new Exception("Exception is null."); - } - - StringBuilder details = new StringBuilder(); - StringBuilder trace = new StringBuilder(); - var ex2 = ex; - while (ex2.InnerException != null) - { - string detailsItem = - "{" + - $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + - $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.InnerException.Message)}" + - "}"; - if (details.Length > 0) - details.Append(", "); - details.Append(detailsItem); - ex2 = ex2.InnerException; - } - - ex2 = ex; - do - { - string traceItem = - "{" + - $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.Message)}, " + - $"{JsonConvert.ToString("stack")}: {JsonConvert.ToString(ex2.StackTrace)}" + - "}"; - if (trace.Length > 0) - trace.Append(", "); - trace.Append(traceItem); - ex2 = ex2.InnerException; - } - while (ex2 != null); - - details.Insert(0, "[").Append("]"); - trace.Insert(0, $"{{{JsonConvert.ToString("trace")}: [").Append("]}"); - - HttpResponseMessage msg = request.CreateResponse(code); - msg.Content = new StringContent( - "{" + - $"{JsonConvert.ToString("error")}: " + - "{ " + - $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + - $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex.Message)}, " + - $"{JsonConvert.ToString("details")}: {details.ToString()}, " + - $"{JsonConvert.ToString("innererror")}: {trace.ToString()}" + - "}" + - "}", - Encoding.UTF8, - "application/json"); - LogService.LogError(originalEx.Message, originalEx); - return msg; - } -#endif - -#if NETFRAMEWORK - private HttpResponseMessage TestPreferMinimal() - { - if (Request.Headers.Contains("Prefer")) - { - KeyValuePair> header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); - if (header.Value != null && header.Value.ToString().ToLower().Contains("return=minimal")) - { - HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.NoContent); - result.Headers.Add("Preference-Applied", "return=minimal"); - return result; - } - } - - return null; - } -#elif NETSTANDARD - - private NoContentResult TestPreferMinimal() - { - if (Request.Headers.ContainsKey("Prefer")) - { - KeyValuePair header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); - if (header.Value.ToString() != null && header.Value.ToString().ToLower().Contains("return=minimal")) - { - NoContentResult result = NoContent(); - Request.Headers.Add("Preference-Applied", "return=minimal"); - - return result; - } - } - - return null; - } -#endif - -#if NETFRAMEWORK - /// - /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. - /// - /// Возвращается EdmEntityObject преобразованный из JSON-строки. - private EdmEntityObject ReplaceOdataBindNull() - { - if (!Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) - { - if (Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; - throw new Exception("ReplaceOdataBindNull: edmEntity is null."); - } - - Stream stream; - - string requestContentKey = PostPatchHandler.RequestContent; - if (Request.Properties.ContainsKey(PostPatchHandler.PropertyKeyBatchRequest) && (bool)Request.Properties[PostPatchHandler.PropertyKeyBatchRequest] == true) - { - requestContentKey = PostPatchHandler.RequestContent + $"_{PostPatchHandler.PropertyKeyContentId}_{Request.Properties[PostPatchHandler.PropertyKeyContentId]}"; - } - - string json = (string)Request.Properties[requestContentKey]; - - Dictionary props = - JsonConvert.DeserializeObject>(json, new JsonSerializerSettings() { FloatParseHandling = FloatParseHandling.Decimal }); - var keys = props.Keys.ToArray(); - var odataBindNullList = new List(); - foreach (var key in keys) - { - var p = key.IndexOf("@odata.bind"); - if (p != -1 && props[key] == null) - { - props.Remove(key); - var newKey = key.Substring(0, p); - if (props.ContainsKey(newKey)) - { - props.Remove(newKey); - } - - var type = (EdmEntityTypeReference)Request.Properties[ExtendedODataEntityDeserializer.OdataBindNull]; - - var prop = type.FindNavigationProperty(newKey); - if (prop.Type.IsCollection()) - { - odataBindNullList.Add(newKey); - } - else - { - props.Add(newKey, null); - } - } - } - - json = JsonConvert.SerializeObject(props); - Request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - - IContentNegotiator negotiator = (IContentNegotiator)Configuration.Services.GetService(typeof(IContentNegotiator)); - var resultNegotiate = negotiator.Negotiate(typeof(EdmEntityObject), Request, Configuration.Formatters); - - stream = Request.Content.ReadAsStreamAsync().Result; - var formatter = resultNegotiate.Formatter; - - /* - // Другой вариант получения форматтера. - var formatter = ((ODataMediaTypeFormatter)Configuration.Formatters[0]).GetPerRequestFormatterInstance( - typeof(EdmEntityObject), Request, Request.Content.Headers.ContentType); - - */ - - var edmEntity = (EdmEntityObject)formatter.ReadFromStreamAsync( - typeof(EdmEntityObject), - stream, - Request.Content, - new ModelStateFormatterLogger(ModelState, "edmEntity")).Result; - if (edmEntity == null && Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - { - throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; - } - - foreach (var prop in odataBindNullList) - { - edmEntity.TrySetPropertyValue(prop, null); - } - - return edmEntity; - } -#elif NETSTANDARD - /// - /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. - /// - /// Возвращается EdmEntityObject преобразованный из JSON-строки. - private EdmEntityObject ReplaceOdataBindNull() - { - if (!Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) - { - if (Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; - throw new Exception("ReplaceOdataBindNull: edmEntity is null."); - } - - string json = (string)Request.HttpContext.Items[RequestHeadersHookMiddleware.PropertyKeyRequestContent]; - - Dictionary props = JsonConvert.DeserializeObject>(json); - var keys = props.Keys.ToArray(); - var odataBindNullList = new List(); - foreach (var key in keys) - { - var p = key.IndexOf("@odata.bind"); - if (p != -1 && props[key] == null) - { - props.Remove(key); - var newKey = key.Substring(0, p); - if (props.ContainsKey(newKey)) - { - props.Remove(newKey); - } - - var type = (EdmEntityTypeReference)Request.HttpContext.Items[ExtendedODataEntityDeserializer.OdataBindNull]; - - var prop = type.FindNavigationProperty(newKey); - if (prop.Type.IsCollection()) - { - odataBindNullList.Add(newKey); - } - else - { - props.Add(newKey, null); - } - } - } - - json = JsonConvert.SerializeObject(props); - Request.Body = new StringContent(json, Encoding.UTF8, "application/json").ReadAsStreamAsync().Result; - - var ictx = new InputFormatterContext( - HttpContext, - string.Empty, - ModelState, - MetadataProvider.GetMetadataForType(typeof(EdmEntityObject)), - (x, y) => new StreamReader(x, y)); - - IList formatters = ODataInputFormatterFactory.Create(); - - // The JSON input formatter is the first formatter in the OData input formatters list. - InputFormatterResult formatterResult = formatters.First().ReadRequestBodyAsync(ictx, Encoding.UTF8).Result; - - var edmEntity = (EdmEntityObject)formatterResult.Model; - - if (edmEntity == null && Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - { - throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; - } - - foreach (var prop in odataBindNullList) - { - edmEntity.TrySetPropertyValue(prop, null); - } - - return edmEntity; - } -#endif - - /// - /// Общая логика модификации данных: вставка и обновление в зависимости от статуса. - /// Используется в Post (вставка) и Patch (обновление). - /// - /// Модифицируемая сущность. - /// Ключ сущности. Использовать, если не задан в сущности, но специфичен (не д.б. сгенерирован). - /// Созданная сущность. - private DataObject UpdateObject(EdmEntityObject edmEntity, object key) - { - Init(); - - // Список объектов для обновления. - List objs = new List(); - - try - { - // Создадим объект данных по пришедшей сущности. - // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. - DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs); - - for (int i = 0; i < objs.Count; i++) - { - ObjectStatus status = objs[i].GetStatus(false); - if (status == ObjectStatus.Created) - { - if (!ExecuteCallbackBeforeCreate(objs[i])) - { - objs.RemoveAt(i); - i++; - } - } - else - { - if (!ExecuteCallbackBeforeUpdate(objs[i])) - { - objs.RemoveAt(i); - i++; - } - } - } - - if (!OfflineManager.UnlockObjects(QueryOptions, objs)) - throw new OperationCanceledException(); // TODO - - // Обработка объектов данных в хранилище средствами сервиса данных. - // Статусы объектов должны автоматически получиться верными, т.к. в GetDataObjectByEdmEntity объект создаем - // только при неудачной попытке вычитки и лишь затем инициализируем свойства пришедшими значениями. - var objsArr = objs.ToArray(); - - // Список объектов для обновления без UnAltered. - var objsArrSmall = objsArr.Where(t => t.GetStatus() != ObjectStatus.UnAltered).ToArray(); - if (IsBatchChangeSetRequest) - { -#if NETFRAMEWORK - List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; -#elif NETSTANDARD - List dataObjectsToUpdate = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; -#endif - dataObjectsToUpdate.AddRange(objsArrSmall); - allProcessedObjects.Add(obj); - } - else - { - _dataService.UpdateObjects(ref objsArrSmall); - } - - // При успешном обновлении вычищаем из файловой системы, файлы подлежащие удалению. - _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); - - return obj; - } - catch (Exception) - { - _removingFileDescriptions.Clear(); - throw; - } - } - - /// - /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению по умолчанию, иначе - создаётся новый. - /// - /// Тип объекта, не может быть null. - /// Значение ключа.> - /// Объект данных. - private DataObject ReturnDataObject(Type objType, object keyValue) - { - if (objType == null) - { - throw new ArgumentNullException(nameof(objType)); - } - - if (keyValue != null) - { - DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); - View view = _model.GetDataObjectDefaultView(objType); - - if (dataObjectFromCache != 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; - } - } - - // Значение ключа автоматически создаётся. - DataObject obj; - - if (keyValue != null) - { - obj = DataObjectCache.CreateDataObject(objType, keyValue); - } - else - { - obj = (DataObject)Activator.CreateInstance(objType); - DataObjectCache.AddDataObject(obj); - } - - return obj; - } - - /// - /// Построение объекта данных по сущности OData. - /// - /// Сущность OData. - /// Значение ключевого поля сущности. - /// Список объектов для обновления. - /// Признак, что объект добавляется в конец списка обновления. - /// Объект данных. - private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false) - { - if (edmEntity == null) - { - return null; - } - - IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; - Type objType = _model.GetDataObjectType(_model.GetEdmEntitySet(entityType).Name); - - // Значение свойства. - object value; - - // Получим значение ключа. - 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); - } - - // Загрузим объект из хранилища, если он там есть (используем представление по умолчанию), или создадим, если нет, но только для POST. - // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. - DataObject obj = ReturnDataObject(objType, value); - - // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. - var objInList = dObjs.FirstOrDefault(o => PKHelper.EQDataObject(o, obj, false)); - if (objInList == null) - { - if (!endObject) - { - // Добавляем объект в начало списка. - dObjs.Insert(0, obj); - } - else - { - // Добавляем в конец списка. - dObjs.Add(obj); - } - } - - // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). - string agregatorPropertyName = Information.GetAgregatePropertyName(objType); - IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); - - // Обрабатываем агрегатор первым. - List changedProps = entityProps - .Where(ep => changedPropNames.Contains(ep.Name)) - .OrderBy(ep => ep.Name != agregatorPropertyName) - .ToList(); - foreach (var prop in changedProps) - { - string dataObjectPropName; - try - { - dataObjectPropName = _model.GetDataObjectProperty(entityType.FullTypeName(), prop.Name).Name; - } - catch (KeyNotFoundException) - { - // Check if prop value is the link from master to pseudodetail (pseudoproperty). - if (HasPseudoproperty(entityType, prop.Name)) - { - continue; - } - - throw; - } - - // Обработка мастеров и детейлов. - if (prop is EdmNavigationProperty navProp) - { - edmEntity.TryGetPropertyValue(prop.Name, out value); - - EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity(); - - // Обработка мастеров. - if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne) - { - if (value is EdmEntityObject edmMaster) - { - // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. - bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); - DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd); - - Information.SetPropValueByName(obj, dataObjectPropName, master); - - if (dataObjectPropName == agregatorPropertyName) - { - master.AddDetail(obj); - - // Нужно обязательно обозначить детейловое свойство загруженным, поскольку мы вносим в него изменения. - string detailPropName = Information.GetDetailArrayPropertyName(master.GetType(), obj.GetType()); - if (!string.IsNullOrEmpty(detailPropName) && !master.CheckLoadedProperty(detailPropName)) - { - master.AddLoadedProperties(detailPropName); - } - } - } - else - { - Information.SetPropValueByName(obj, dataObjectPropName, null); - } - } - - // Обработка детейлов. - if (edmMultiplicity == EdmMultiplicity.Many) - { - DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName); - - if (value is EdmEntityObjectCollection coll) - { - if (coll != null && coll.Count > 0) - { - foreach (var edmEnt in coll) - { - DataObject det = GetDataObjectByEdmEntity( - (EdmEntityObject)edmEnt, - null, - dObjs, - true); - - if (det.__PrimaryKey == null) - { - detarr.AddObject(det); - } - else - { - detarr.SetByKey(det.__PrimaryKey, det); - } - } - } - } - else - { - detarr.Clear(); - } - } - } - else - { - // Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj). - if (prop.Name != keyProperty.Name) - { - Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName); - edmEntity.TryGetPropertyValue(prop.Name, out value); - - // Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств, - // значит свойство файловое, и его нужно обработать особым образом. - if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType)) - { - IDataObjectFileProvider dataObjectFileProvider = _dataObjectFileAccessor.GetDataObjectFileProvider(dataObjectPropertyType); - - // Обработка файловых свойств объектов данных. - string serializedFileDescription = value as string; - if (serializedFileDescription == null) - { - // Файловое свойство было сброшено на клиенте. - // Ассоциированный файл должен быть удален, после успешного сохранения изменений. - // Для этого запоминаем метаданные ассоциированного файла, до того как свойство будет сброшено - // (для получения метаданных свойство будет дочитано в объект данных). - // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, - // соответственно из файловой системы просто нечего удалять, - // поэтому обходим его стороной, чтобы избежать лишных вычиток файлов из БД. - if (dataObjectPropertyType != typeof(File)) - { - var fileDescription = dataObjectFileProvider.GetFileDescription(_dataService, obj, dataObjectPropName); - _removingFileDescriptions.Add(fileDescription); - } - - // Сбрасываем файловое свойство в изменяемом объекте данных. - Information.SetPropValueByName(obj, dataObjectPropName, null); - } - else - { - // Файловое свойство было изменено, но не сброшено. - // Если в метаданных файла присутствует FileUploadKey значит файл был загружен на сервер, - // но еще не был ассоциирован с объектом данных, и это нужно сделать. - FileDescription fileDescription = FileDescription.FromJson(serializedFileDescription); - fileDescription.FilePropertyType = dataObjectPropertyType; - if (!(string.IsNullOrEmpty(fileDescription.FileUploadKey) || string.IsNullOrEmpty(fileDescription.FileName))) - { - var fileProperty = dataObjectFileProvider.GetFileProperty(_dataService, fileDescription); - Information.SetPropValueByName(obj, dataObjectPropName, fileProperty); - - // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, - // поэтому после успешного сохранения объекта данных, оссоциированный с ним файл должен быть удален из файловой системы. - // Для этого запоминаем описание загруженного файла. - if (dataObjectPropertyType == typeof(File)) - { - _removingFileDescriptions.Add(fileDescription); - } - } - } - } - else - { - // Преобразование типов для примитивных свойств. - if (value is DateTimeOffset) - value = ((DateTimeOffset)value).UtcDateTime; - if (value is EdmEnumObject) - value = ((EdmEnumObject)value).Value; - - Information.SetPropValueByName(obj, dataObjectPropName, value); - } - } - } - } - - if (!string.IsNullOrEmpty(agregatorPropertyName)) - { - DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); - - 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); - } - } - } - } - - return obj; - } - } -} +namespace NewPlatform.Flexberry.ORM.ODataService.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Text; + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET.FunctionalLanguage; + using Microsoft.AspNet.OData; + using Microsoft.OData.Edm; + using NewPlatform.Flexberry.ORM.ODataService.Batch; + using NewPlatform.Flexberry.ORM.ODataService.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Files; + using NewPlatform.Flexberry.ORM.ODataService.Files.Providers; + using NewPlatform.Flexberry.ORM.ODataService.Formatter; + using Newtonsoft.Json; + using File = ICSSoft.STORMNET.FileType.File; + using KeySegment = Microsoft.OData.UriParser.KeySegment; + +#if NETFRAMEWORK + using System.Net.Http.Formatting; + using System.Web.Http; + using System.Web.Http.Results; + using System.Web.Http.Validation; + using NewPlatform.Flexberry.ORM.ODataService.Events; + using NewPlatform.Flexberry.ORM.ODataService.Handlers; +#endif +#if NETSTANDARD + using Microsoft.AspNet.OData.Formatter; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Formatters; + using Microsoft.Extensions.Primitives; + using NewPlatform.Flexberry.ORM.ODataService.Middleware; +#endif + + /// + /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. + /// + public partial class DataObjectController + { + /// + /// Метаданные файлов, временно загруженных в каталог файлового хранилища и привязанных к свойствам обрабатываемых объектов данных. + /// Файлы будут удалены из файловой системы + /// в случае успешного сохранения объектов данных. + /// + private List _removingFileDescriptions = new List(); + + /// + /// Кэш типов, у которых одинакового типа детейлы и мастера. + /// + private List _typesWithSameDetailAndMaster = new List(); + + /// + /// Кэш типов, у которых нет одинакового типа детейлов и мастеров. + /// + private List _typesWithNotSameDetailAndMaster = new List(); + + /// + /// Создание сущности и всех связанных. При существовании в БД произойдёт обновление. + /// + /// Создаваемая сущность. + /// Созданная сущность. +#if NETFRAMEWORK + public HttpResponseMessage Post([FromBody] EdmEntityObject edmEntity) +#elif NETSTANDARD + public IActionResult Post([FromBody] EdmEntityObject edmEntity) +#endif + { + try + { + if (edmEntity == null) + { + edmEntity = ReplaceOdataBindNull(); + } + + DataObject obj = UpdateObject(edmEntity, null); + ExecuteCallbackAfterCreate(obj); + + edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); + var responseForPreferMinimal = TestPreferMinimal(); + if (responseForPreferMinimal != null) + { + return responseForPreferMinimal; + } + +#if NETFRAMEWORK + var result = Request.CreateResponse(HttpStatusCode.Created, edmEntity); + if (Request.Headers.Contains("Prefer")) + { + result.Headers.Add("Preference-Applied", "return=representation"); + } +#elif NETSTANDARD + var result = new ObjectResult(edmEntity) { StatusCode = StatusCodes.Status201Created }; + if (Request.Headers.ContainsKey("Prefer")) + { + Response.Headers.Add("Preference-Applied", "return=representation"); + } +#endif + + return result; + } + catch (Exception ex) + { +#if NETFRAMEWORK + return InternalServerErrorMessage(ex); +#elif NETSTANDARD + throw CustomException(ex); +#endif + } + } + + /// + /// Обновление сущности (свойства могут быть заданы частично, т.е. указывать можно значения только измененных свойств). + /// Если сущности с заданным ключом нет в БД происходит Upsert (в соответствии со стандартом). + /// + /// Ключ обновляемой сущности. + /// Обновляемая сущность. + /// Обновлённая сущность. +#if NETFRAMEWORK + public HttpResponseMessage Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) +#elif NETSTANDARD + public IActionResult Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) +#endif + { + try + { + if (key == null) + { + throw new ArgumentNullException("key"); + } + + if (edmEntity == null) + { + edmEntity = ReplaceOdataBindNull(); + } + + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; + +#if NETFRAMEWORK + var dictionary = Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? + (Dictionary)Request.Properties[ExtendedODataEntityDeserializer.Dictionary] : + new Dictionary(); +#elif NETSTANDARD + var dictionary = Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? + (Dictionary)Request.HttpContext.Items[ExtendedODataEntityDeserializer.Dictionary] : + new Dictionary(); +#endif + + foreach (var prop in entityType.Properties()) + { + if (!dictionary.ContainsKey(prop.Name) && edmEntity.GetChangedPropertyNames().Contains(prop.Name) && prop is EdmNavigationProperty) + { + const string msg = "Error processing request stream. Deep updates are not supported in PUT or PATCH operations."; +#if NETFRAMEWORK + return Request.CreateResponse(HttpStatusCode.BadRequest, msg); +#elif NETSTANDARD + return BadRequest(msg); +#endif + } + + if (dictionary.ContainsKey(prop.Name) && dictionary[prop.Name] == null && + (!prop.Type.IsNullable || prop.Type.IsCollection())) + { + string msg = $"The property {prop.Name} can not be null."; +#if NETFRAMEWORK + return Request.CreateResponse(HttpStatusCode.BadRequest, msg); +#elif NETSTANDARD + return BadRequest(msg); +#endif + } + } + + DataObject obj = UpdateObject(edmEntity, key); + ExecuteCallbackAfterUpdate(obj); + + var responseForPreferMinimal = TestPreferMinimal(); + if (responseForPreferMinimal != null) + { + return responseForPreferMinimal; + } + +#if NETFRAMEWORK + if (!Request.Headers.Contains("Prefer")) + { + return Request.CreateResponse(HttpStatusCode.NoContent); + } +#elif NETSTANDARD + if (!Request.Headers.ContainsKey("Prefer")) + { + return NoContent(); + } +#endif + + edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); +#if NETFRAMEWORK + var result = Request.CreateResponse(HttpStatusCode.OK, edmEntity); + result.Headers.Add("Preference-Applied", "return=representation"); +#elif NETSTANDARD + var result = Ok(edmEntity); + Response.Headers.Add("Preference-Applied", "return=representation"); +#endif + return result; + } + catch (Exception ex) + { +#if NETFRAMEWORK + return InternalServerErrorMessage(ex); +#elif NETSTANDARD + throw CustomException(ex); +#endif + } + } + + /// + /// Осуществляет удаление сущности. + /// + /// + /// Результат выполнения запроса типа , соответствующий статусу . + /// +#if NETFRAMEWORK + public HttpResponseMessage DeleteString() +#elif NETSTANDARD + public NoContentResult DeleteString() +#endif + { + var keySegment = ODataPath.Segments[1] as KeySegment; + string key = keySegment.Keys.First().Value.ToString().Trim().Replace("'", string.Empty); + return DeleteEntity(key); + } + + /// + /// Осуществляет удаление сущности. + /// + /// + /// Результат выполнения запроса типа , соответствующий статусу . + /// +#if NETFRAMEWORK + public HttpResponseMessage DeleteGuid() +#elif NETSTANDARD + public NoContentResult DeleteGuid() +#endif + { + var keySegment = ODataPath.Segments[1] as KeySegment; + Guid key = new Guid(keySegment.Keys.First().Value.ToString()); + return DeleteEntity(key); + } + +#if NETFRAMEWORK + private HttpResponseMessage DeleteEntity(object key) +#elif NETSTANDARD + private NoContentResult DeleteEntity(object key) +#endif + { + try + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + Init(); + + /* В ситуации, когда мастер и детейл одного типа, ORM без подгрузки копии данных не может корректно разобрать порядок, + * в котором объекты должны быть удалены. + */ + bool needDataCopyLoad = _typesWithSameDetailAndMaster.Contains(type); + if (!_typesWithNotSameDetailAndMaster.Contains(type) && !needDataCopyLoad) + { + string[] props = Information.GetAllPropertyNames(type); + int length = props.Length; + int index = 0; + + while(!needDataCopyLoad && index < length) + { + string prop = props[index]; + Type propType = Information.GetPropertyType(type, prop); + needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; + + index++; + } + } + + DataObject obj = null; + if (!needDataCopyLoad) + { + obj = DataObjectCache.CreateDataObject(type, key); + _typesWithNotSameDetailAndMaster.Add(type); + } + else + { + obj = LoadObject(type, key.ToString()); + _typesWithSameDetailAndMaster.Add(type); + } + + // Удаляем объект с заданным ключем. + // Детейлы удалятся вместе с агрегатором автоматически. + // Если удаляемый объект является мастером для какого-либо объекта, то + // спецификация предполагает, что зависимые объекты будут каскадно удалены либо ссылки в них заменены + // на null/значению по умолчанию, если это задано в модели через ReferentialConstraints. + // Но если это задано в модели, то соответвующие объекты данных реализуют интерфейсы + // IReferencesCascadeDelete/IReferencesNullDelete и требуемые действия будут выполнены автоматически. + // В данный момент ReferentialConstraints не создаются в модели. + obj.SetStatus(ObjectStatus.Deleted); + + // Раз объект данных удаляется, то и все ассоциированные с ним файлы должны быть удалены. + // Запоминаем метаданные всех ассоциированных файлов, кроме файлов соответствующих файловым свойствам типа File + // (файлы соответствующие свойствам типа File хранятся в БД, и из файловой системы просто нечего удалять). + // TODO: подумать как быть с детейлами, детейлами детейлов, и т д. + var descriptions = _dataObjectFileAccessor.GetDataObjectFileDescriptions(_dataService, obj, new List { typeof(File) }); + _removingFileDescriptions.AddRange(descriptions); + + List objs = new List(); + + if (ExecuteCallbackBeforeDelete(obj)) + { + string agregatorPropertyName = Information.GetAgregatePropertyName(type); + if (!string.IsNullOrEmpty(agregatorPropertyName)) + { + DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); + + if (agregator != null) + { + objs.Add(agregator); + } + } + + objs.Add(obj); + + if (IsBatchChangeSetRequest) + { +#if NETFRAMEWORK + List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; + +#elif NETSTANDARD + List dataObjectsToUpdate = (List)HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; +#endif + dataObjectsToUpdate.AddRange(objs); + allProcessedObjects.Add(obj); + } + else + { + DataObject[] dataObjects = objs.ToArray(); + _dataService.UpdateObjects(ref dataObjects); + } + } + + // При успешном удалении вычищаем из файловой системы, файлы подлежащие удалению. + _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); + ExecuteCallbackAfterDelete(obj); + +#if NETFRAMEWORK + return Request.CreateResponse(HttpStatusCode.NoContent); +#elif NETSTANDARD + return NoContent(); +#endif + } + catch (Exception ex) + { + _removingFileDescriptions.Clear(); +#if NETFRAMEWORK + return InternalServerErrorMessage(ex); +#elif NETSTANDARD + throw CustomException(ex); +#endif + } + } + +#if NETFRAMEWORK + /// + /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. + /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. + /// + /// Ошибка сервиса. + /// Http-ответ. + private HttpResponseMessage InternalServerErrorMessage(Exception ex) + { + return InternalServerErrorMessage(ex, _events, Request); + } + + /// + /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. + /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. + /// + /// Ошибка сервиса. + /// The container with registered events. + /// Original HTTP request message for create a response. + /// Http-ответ. + public static HttpResponseMessage InternalServerErrorMessage(Exception ex, IEventHandlerContainer events, HttpRequestMessage request) + { + HttpStatusCode code = HttpStatusCode.InternalServerError; + Exception originalEx = ex; + + if (events?.CallbackAfterInternalServerError != null) + { + ex = events?.CallbackAfterInternalServerError(ex, ref code); + } + + if (ex == null) + { + ex = new Exception("Exception is null."); + } + + StringBuilder details = new StringBuilder(); + StringBuilder trace = new StringBuilder(); + var ex2 = ex; + while (ex2.InnerException != null) + { + string detailsItem = + "{" + + $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + + $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.InnerException.Message)}" + + "}"; + if (details.Length > 0) + details.Append(", "); + details.Append(detailsItem); + ex2 = ex2.InnerException; + } + + ex2 = ex; + do + { + string traceItem = + "{" + + $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.Message)}, " + + $"{JsonConvert.ToString("stack")}: {JsonConvert.ToString(ex2.StackTrace)}" + + "}"; + if (trace.Length > 0) + trace.Append(", "); + trace.Append(traceItem); + ex2 = ex2.InnerException; + } + while (ex2 != null); + + details.Insert(0, "[").Append("]"); + trace.Insert(0, $"{{{JsonConvert.ToString("trace")}: [").Append("]}"); + + HttpResponseMessage msg = request.CreateResponse(code); + msg.Content = new StringContent( + "{" + + $"{JsonConvert.ToString("error")}: " + + "{ " + + $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + + $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex.Message)}, " + + $"{JsonConvert.ToString("details")}: {details.ToString()}, " + + $"{JsonConvert.ToString("innererror")}: {trace.ToString()}" + + "}" + + "}", + Encoding.UTF8, + "application/json"); + LogService.LogError(originalEx.Message, originalEx); + return msg; + } +#endif + +#if NETFRAMEWORK + private HttpResponseMessage TestPreferMinimal() + { + if (Request.Headers.Contains("Prefer")) + { + KeyValuePair> header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); + if (header.Value != null && header.Value.ToString().ToLower().Contains("return=minimal")) + { + HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.NoContent); + result.Headers.Add("Preference-Applied", "return=minimal"); + return result; + } + } + + return null; + } +#elif NETSTANDARD + + private NoContentResult TestPreferMinimal() + { + if (Request.Headers.ContainsKey("Prefer")) + { + KeyValuePair header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); + if (header.Value.ToString() != null && header.Value.ToString().ToLower().Contains("return=minimal")) + { + NoContentResult result = NoContent(); + Request.Headers.Add("Preference-Applied", "return=minimal"); + + return result; + } + } + + return null; + } +#endif + +#if NETFRAMEWORK + /// + /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. + /// + /// Возвращается EdmEntityObject преобразованный из JSON-строки. + private EdmEntityObject ReplaceOdataBindNull() + { + if (!Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) + { + if (Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; + throw new Exception("ReplaceOdataBindNull: edmEntity is null."); + } + + Stream stream; + + string requestContentKey = PostPatchHandler.RequestContent; + if (Request.Properties.ContainsKey(PostPatchHandler.PropertyKeyBatchRequest) && (bool)Request.Properties[PostPatchHandler.PropertyKeyBatchRequest] == true) + { + requestContentKey = PostPatchHandler.RequestContent + $"_{PostPatchHandler.PropertyKeyContentId}_{Request.Properties[PostPatchHandler.PropertyKeyContentId]}"; + } + + string json = (string)Request.Properties[requestContentKey]; + + Dictionary props = + JsonConvert.DeserializeObject>(json, new JsonSerializerSettings() { FloatParseHandling = FloatParseHandling.Decimal }); + var keys = props.Keys.ToArray(); + var odataBindNullList = new List(); + foreach (var key in keys) + { + var p = key.IndexOf("@odata.bind"); + if (p != -1 && props[key] == null) + { + props.Remove(key); + var newKey = key.Substring(0, p); + if (props.ContainsKey(newKey)) + { + props.Remove(newKey); + } + + var type = (EdmEntityTypeReference)Request.Properties[ExtendedODataEntityDeserializer.OdataBindNull]; + + var prop = type.FindNavigationProperty(newKey); + if (prop.Type.IsCollection()) + { + odataBindNullList.Add(newKey); + } + else + { + props.Add(newKey, null); + } + } + } + + json = JsonConvert.SerializeObject(props); + Request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + IContentNegotiator negotiator = (IContentNegotiator)Configuration.Services.GetService(typeof(IContentNegotiator)); + var resultNegotiate = negotiator.Negotiate(typeof(EdmEntityObject), Request, Configuration.Formatters); + + stream = Request.Content.ReadAsStreamAsync().Result; + var formatter = resultNegotiate.Formatter; + + /* + // Другой вариант получения форматтера. + var formatter = ((ODataMediaTypeFormatter)Configuration.Formatters[0]).GetPerRequestFormatterInstance( + typeof(EdmEntityObject), Request, Request.Content.Headers.ContentType); + + */ + + var edmEntity = (EdmEntityObject)formatter.ReadFromStreamAsync( + typeof(EdmEntityObject), + stream, + Request.Content, + new ModelStateFormatterLogger(ModelState, "edmEntity")).Result; + if (edmEntity == null && Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + { + throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; + } + + foreach (var prop in odataBindNullList) + { + edmEntity.TrySetPropertyValue(prop, null); + } + + return edmEntity; + } +#elif NETSTANDARD + /// + /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. + /// + /// Возвращается EdmEntityObject преобразованный из JSON-строки. + private EdmEntityObject ReplaceOdataBindNull() + { + if (!Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) + { + if (Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; + throw new Exception("ReplaceOdataBindNull: edmEntity is null."); + } + + string json = (string)Request.HttpContext.Items[RequestHeadersHookMiddleware.PropertyKeyRequestContent]; + + Dictionary props = JsonConvert.DeserializeObject>(json); + var keys = props.Keys.ToArray(); + var odataBindNullList = new List(); + foreach (var key in keys) + { + var p = key.IndexOf("@odata.bind"); + if (p != -1 && props[key] == null) + { + props.Remove(key); + var newKey = key.Substring(0, p); + if (props.ContainsKey(newKey)) + { + props.Remove(newKey); + } + + var type = (EdmEntityTypeReference)Request.HttpContext.Items[ExtendedODataEntityDeserializer.OdataBindNull]; + + var prop = type.FindNavigationProperty(newKey); + if (prop.Type.IsCollection()) + { + odataBindNullList.Add(newKey); + } + else + { + props.Add(newKey, null); + } + } + } + + json = JsonConvert.SerializeObject(props); + Request.Body = new StringContent(json, Encoding.UTF8, "application/json").ReadAsStreamAsync().Result; + + var ictx = new InputFormatterContext( + HttpContext, + string.Empty, + ModelState, + MetadataProvider.GetMetadataForType(typeof(EdmEntityObject)), + (x, y) => new StreamReader(x, y)); + + IList formatters = ODataInputFormatterFactory.Create(); + + // The JSON input formatter is the first formatter in the OData input formatters list. + InputFormatterResult formatterResult = formatters.First().ReadRequestBodyAsync(ictx, Encoding.UTF8).Result; + + var edmEntity = (EdmEntityObject)formatterResult.Model; + + if (edmEntity == null && Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + { + throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; + } + + foreach (var prop in odataBindNullList) + { + edmEntity.TrySetPropertyValue(prop, null); + } + + return edmEntity; + } +#endif + + /// + /// Общая логика модификации данных: вставка и обновление в зависимости от статуса. + /// Используется в Post (вставка) и Patch (обновление). + /// + /// Модифицируемая сущность. + /// Ключ сущности. Использовать, если не задан в сущности, но специфичен (не д.б. сгенерирован). + /// Созданная сущность. + private DataObject UpdateObject(EdmEntityObject edmEntity, object key) + { + Init(); + + // Список объектов для обновления. + List objs = new List(); + + try + { + // Создадим объект данных по пришедшей сущности. + // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. + DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true); + + for (int i = 0; i < objs.Count; i++) + { + ObjectStatus status = objs[i].GetStatus(false); + if (status == ObjectStatus.Created) + { + if (!ExecuteCallbackBeforeCreate(objs[i])) + { + objs.RemoveAt(i); + i++; + } + } + else + { + if (!ExecuteCallbackBeforeUpdate(objs[i])) + { + objs.RemoveAt(i); + i++; + } + } + } + + if (!OfflineManager.UnlockObjects(QueryOptions, objs)) + throw new OperationCanceledException(); // TODO + + // Обработка объектов данных в хранилище средствами сервиса данных. + // Статусы объектов должны автоматически получиться верными, т.к. в GetDataObjectByEdmEntity объект создаем + // только при неудачной попытке вычитки и лишь затем инициализируем свойства пришедшими значениями. + var objsArr = objs.ToArray(); + + // Список объектов для обновления без UnAltered. + var objsArrSmall = objsArr.Where(t => t.GetStatus() != ObjectStatus.UnAltered).ToArray(); + if (IsBatchChangeSetRequest) + { +#if NETFRAMEWORK + List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; +#elif NETSTANDARD + List dataObjectsToUpdate = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; +#endif + dataObjectsToUpdate.AddRange(objsArrSmall); + allProcessedObjects.Add(obj); + } + else + { + _dataService.UpdateObjects(ref objsArrSmall); + } + + // При успешном обновлении вычищаем из файловой системы, файлы подлежащие удалению. + _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); + + return obj; + } + catch (Exception) + { + _removingFileDescriptions.Clear(); + throw; + } + } + + /// + /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый. + /// + /// Тип объекта, не может быть null. + /// Значение ключа. + /// Представление для загрузки объекта. + /// Объект данных. + private DataObject ReturnDataObject(Type objType, object keyValue, View view) + { + if (objType == null) + { + throw new ArgumentNullException(nameof(objType)); + } + + if (keyValue != null) + { + DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); + + if (dataObjectFromCache != 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; + } + } + + // Значение ключа автоматически создаётся. + DataObject obj; + + if (keyValue != null) + { + obj = DataObjectCache.CreateDataObject(objType, keyValue); + } + else + { + obj = (DataObject)Activator.CreateInstance(objType); + DataObjectCache.AddDataObject(obj); + } + + 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. + /// + /// Сущность OData. + /// Значение ключевого поля сущности. + /// Список объектов для обновления. + /// Признак, что объект добавляется в конец списка обновления. + /// Использовать представление для обновления (вместо представления по умолчанию). + /// Объект данных. + private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false) + { + if (edmEntity == null) + { + 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); + } + + // Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST. + // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. + Type objType = _model.GetDataObjectType(edmEntity); + + View view = _model.GetDataObjectDefaultView(objType); + if (useUpdateView) + { + view = _model.GetDataObjectUpdateView(objType) ?? view; + } + + DataObject obj = ReturnDataObject(objType, value, view); + + // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. + AddDataObject(dObjs, obj, endObject); + + // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). + string agregatorPropertyName = Information.GetAgregatePropertyName(objType); + IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); + + // Обрабатываем агрегатор первым. + List changedProps = entityProps + .Where(ep => changedPropNames.Contains(ep.Name)) + .OrderBy(ep => ep.Name != agregatorPropertyName) + .ToList(); + foreach (var prop in changedProps) + { + string dataObjectPropName; + try + { + dataObjectPropName = _model.GetDataObjectProperty(entityType.FullTypeName(), prop.Name).Name; + } + catch (KeyNotFoundException) + { + // Check if prop value is the link from master to pseudodetail (pseudoproperty). + if (HasPseudoproperty(entityType, prop.Name)) + { + continue; + } + + throw; + } + + // Обработка мастеров и детейлов. + if (prop is EdmNavigationProperty navProp) + { + edmEntity.TryGetPropertyValue(prop.Name, out value); + + EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity(); + + // Обработка мастеров. + if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne) + { + if (value is EdmEntityObject edmMaster) + { + // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. + bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); + DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); + + Information.SetPropValueByName(obj, dataObjectPropName, master); + + if (dataObjectPropName == agregatorPropertyName) + { + master.AddDetail(obj); + + // Нужно обязательно обозначить детейловое свойство загруженным, поскольку мы вносим в него изменения. + string detailPropName = Information.GetDetailArrayPropertyName(master.GetType(), obj.GetType()); + if (!string.IsNullOrEmpty(detailPropName) && !master.CheckLoadedProperty(detailPropName)) + { + master.AddLoadedProperties(detailPropName); + } + } + } + else + { + Information.SetPropValueByName(obj, dataObjectPropName, null); + } + } + + // Обработка детейлов. + if (edmMultiplicity == EdmMultiplicity.Many) + { + DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName); + + if (value is EdmEntityObjectCollection coll) + { + if (coll != null && coll.Count > 0) + { + foreach (var edmEnt in coll) + { + DataObject det = GetDataObjectByEdmEntity( + (EdmEntityObject)edmEnt, + null, + dObjs, + true, + useUpdateView); + + if (det.__PrimaryKey == null) + { + detarr.AddObject(det); + } + else + { + detarr.SetByKey(det.__PrimaryKey, det); + } + } + } + } + else + { + detarr.Clear(); + } + } + } + else + { + // Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj). + if (prop.Name != keyProperty.Name) + { + Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName); + edmEntity.TryGetPropertyValue(prop.Name, out value); + + // Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств, + // значит свойство файловое, и его нужно обработать особым образом. + if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType)) + { + IDataObjectFileProvider dataObjectFileProvider = _dataObjectFileAccessor.GetDataObjectFileProvider(dataObjectPropertyType); + + // Обработка файловых свойств объектов данных. + string serializedFileDescription = value as string; + if (serializedFileDescription == null) + { + // Файловое свойство было сброшено на клиенте. + // Ассоциированный файл должен быть удален, после успешного сохранения изменений. + // Для этого запоминаем метаданные ассоциированного файла, до того как свойство будет сброшено + // (для получения метаданных свойство будет дочитано в объект данных). + // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, + // соответственно из файловой системы просто нечего удалять, + // поэтому обходим его стороной, чтобы избежать лишных вычиток файлов из БД. + if (dataObjectPropertyType != typeof(File)) + { + var fileDescription = dataObjectFileProvider.GetFileDescription(_dataService, obj, dataObjectPropName); + _removingFileDescriptions.Add(fileDescription); + } + + // Сбрасываем файловое свойство в изменяемом объекте данных. + Information.SetPropValueByName(obj, dataObjectPropName, null); + } + else + { + // Файловое свойство было изменено, но не сброшено. + // Если в метаданных файла присутствует FileUploadKey значит файл был загружен на сервер, + // но еще не был ассоциирован с объектом данных, и это нужно сделать. + FileDescription fileDescription = FileDescription.FromJson(serializedFileDescription); + fileDescription.FilePropertyType = dataObjectPropertyType; + if (!(string.IsNullOrEmpty(fileDescription.FileUploadKey) || string.IsNullOrEmpty(fileDescription.FileName))) + { + var fileProperty = dataObjectFileProvider.GetFileProperty(_dataService, fileDescription); + Information.SetPropValueByName(obj, dataObjectPropName, fileProperty); + + // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, + // поэтому после успешного сохранения объекта данных, оссоциированный с ним файл должен быть удален из файловой системы. + // Для этого запоминаем описание загруженного файла. + if (dataObjectPropertyType == typeof(File)) + { + _removingFileDescriptions.Add(fileDescription); + } + } + } + } + else + { + // Преобразование типов для примитивных свойств. + if (value is DateTimeOffset) + value = ((DateTimeOffset)value).UtcDateTime; + if (value is EdmEnumObject) + value = ((EdmEnumObject)value).Value; + + Information.SetPropValueByName(obj, dataObjectPropName, value); + } + } + } + } + + if (!string.IsNullOrEmpty(agregatorPropertyName)) + { + DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); + + if (agregator != null) + { + AddDataObject(dObjs, agregator, endObject); + } + } + + return obj; + } + } +} diff --git a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs index e0245aa6..a299aa6c 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs @@ -226,6 +226,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 66353bfb..82210a42 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs @@ -1,1778 +1,1925 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing; - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET.Business.LINQProvider; - using ICSSoft.STORMNET.Exceptions; - using ICSSoft.STORMNET.KeyGen; - using ICSSoft.STORMNET.Windows.Forms; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; - - using Newtonsoft.Json; - using Xunit; - - /// - /// Класс тестов для тестирования операций модификации данных OData-сервисом (вставка, обновление, удаление). - /// - public class ModifyDataTest : BaseODataServiceIntegratedTest - { -#if NETCOREAPP - /// - /// Конструктор по-умолчанию. - /// - /// Фабрика для приложения. - /// Вывод отладочной информации. - public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) - : base(factory, output) - { - } -#endif - - /// - /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. - /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . - /// Тест проверяет следующие факты: - /// - /// Вставка связи мастерового объекта. - /// Удаление связи мастеровго объекта путём присвоения null свойству. - /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. - /// - /// - [Fact] - public void PatchNavigationPropertiesTest() - { - ActODataService(args => - { - ExternalLangDef.LanguageDef.DataService = args.DataService; - string[] берлогаPropertiesNames = - { - Information.ExtractPropertyPath<Берлога>(x => x.ПолеБС), - Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.Наименование), - Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) - }; - string[] лесPropertiesNames = - { - Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь), - Information.ExtractPropertyPath<Лес>(x => x.Название), - Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) - }; - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Медведь>(x => x.ПолеБС), - Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Медведь>(x => x.Вес), - - // Information.ExtractPropertyPath<Медведь>(x => x.Пол), - Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), - Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - - // Объекты для тестирования создания. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - Лес лес2 = new Лес { Название = "Березовая роща" }; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; - медв.Берлога.Add(берлога1); - var objs = new DataObject[] { медв, лес1, лес2, берлога1 }; - args.DataService.UpdateObjects(ref objs); - string requestUrl; - - string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - - objJson.Add("ЛесОбитания@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, - ((KeyGuid)лес1.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); - objJson.Add("Медведь", null); - requestJsonDataБерлога = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) - { - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - - requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - objJson.Add("ЛесОбитания@odata.bind", null); - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - objJson.Add("ЛесОбитания", null); - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - objJson.Add("Берлога@odata.bind", null); - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах происходит вставка и удаление связей объекта. - /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . - /// Тест проверяет следующие факты: - /// - /// Вставка связи мастерового объекта. - /// Удаление связи мастеровго объекта путём присвоения null свойству. - /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. - /// - /// - [Fact] - public void PostNavigationPropertiesTest() - { - string[] берлогаPropertiesNames = - { - // Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.Наименование), - Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) - }; - string[] лесPropertiesNames = - { - // Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь), - Information.ExtractPropertyPath<Лес>(x => x.Название), - Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) - }; - string[] медвPropertiesNames = - { - // Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Медведь>(x => x.Вес), - - // Information.ExtractPropertyPath<Медведь>(x => x.Пол), - Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), - Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - - // Объекты для тестирования создания. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - ActODataService(args => - { - string requestUrl; - string receivedJsonЛес1, receivedJsonМедв; - string requestJsonDataЛес1 = лес1.ToJson(лесDynamicView, args.Token.Model); - requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataЛес1).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом (в ней должна вернуться созданная сущность). - receivedJsonЛес1 = response.Content.ReadAsStringAsync().Result.Beautify(); - } - - string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedJsonЛес1); - - objJsonМедв.Add("ЛесОбитания@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, - receivedDict["__PrimaryKey"])); - objJsonМедв.Add("Берлога@odata.bind", null); - - requestJsonDataМедв = objJsonМедв.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом (в ней должна вернуться созданная сущность). - receivedJsonМедв = response.Content.ReadAsStringAsync().Result.Beautify(); - } - - var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); - var objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); - receivedDict = JsonConvert.DeserializeObject>(receivedJsonМедв); - objJson.Add("Медведь@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, - receivedDict["__PrimaryKey"])); - requestJsonDataБерлога = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name); - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) - { - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах происходит вставка объекта, - /// зависимые объекты (мастера, детейлы) обрабатываются в зависимости от наличия в БД - вставляются или обновляются. - /// - [Fact] - public void PostComplexObjectTest() - { - ActODataService(args => - { - string[] берлогаPropertiesNames = - { - Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.Наименование), - Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения), - Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения.Название), - }; - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Медведь>(x => x.Вес), - Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания), - Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания.Название), - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); - - // Объекты для тестирования создания. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - Лес лес2 = new Лес { Название = "Березовая роща" }; - медв.ЛесОбитания = лес1; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; - медв.Берлога.Add(берлога1); - медв.Берлога.Add(берлога2); - - string json = медв.ToJson(медвDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; - - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом. - string receivedJsonObjs = response.Content.ReadAsStringAsync().Result.Beautify(); - - // В ответе приходит объект с созданной сущностью. - // Преобразуем полученный объект в словарь. - Dictionary receivedObjs = JsonConvert.DeserializeObject>(receivedJsonObjs); - - // Проверяем созданный объект, вычитав с помощью DataService - DataObject createdObj = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - args.DataService.LoadObject(createdObj); - - Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); - Assert.Equal(((Медведь)createdObj).Вес, (int)(long)receivedObjs["Вес"]); - - // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService - var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); - lcs.LoadingTypes = new[] { typeof(Лес) }; - DataObject[] dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(2, dobjs.Length); - - lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); - lcs.LoadingTypes = new[] { typeof(Берлога) }; - dobjs = args.DataService.LoadObjects(lcs); - Assert.Equal(2, dobjs.Length); - - // Создание объекта и обновление связанных - // Создаем нового медведя: в его мастере ЛесОбитания - лес1, но в нём изменим Название; в детейлы заберем от первого медведя детейл2, изменив Название в мастере детейла. - // Подготовка тестовых данных в формате OData. - Медведь медвежонок = new Медведь { Вес = 12 }; - var берлога3 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - медвежонок.Берлога.Add(берлога3); - - медв.Берлога.Remove(берлога2); - медвежонок.Берлога.Add(берлога2); - - лес1.Название = лес1.Название + "(обновл)"; - лес2.Название = лес2.Название + "(обновл)"; - - json = медвежонок.ToJson(медвDynamicView, args.Token.Model); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; - - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Проверяем созданный объект, вычитав с помощью DataService - createdObj = new Медведь { __PrimaryKey = медвежонок.__PrimaryKey }; - args.DataService.LoadObject(createdObj); - - Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); - Assert.Equal(12, ((Медведь)createdObj).Вес); - - // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService - ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; - lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); - lcs.LoadingTypes = new[] { typeof(Лес) }; - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), - лес1.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Single(dobjs); - Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); - - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), - лес2.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Single(dobjs); - Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); - - lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); - lcs.LoadingTypes = new[] { typeof(Берлога) }; - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), - медв.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Single(dobjs); - - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), - медвежонок.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(2, dobjs.Length); - }); - } - - /// - /// Осуществляет проверку создания сущности с датой и незаданным первичным ключом. - /// - [Fact] - public void PostObjDateTimeNoPKTest() - { - ActODataService(args => - { - // Создаем объект данных. - Лес country = new Лес { Площадь = 10, Название = "Бор", ДатаПоследнегоОсмотра = (ICSSoft.STORMNET.UserDataTypes.NullableDateTime)DateTime.Now }; - - // Преобразуем объект данных в JSON-строку. - string[] contryPropertiesNames = - { - Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь), - Information.ExtractPropertyPath<Лес>(x => x.Название), - Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) - }; - var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Лес)); - - string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. - /// - [Fact] - public void PostDataTimeValueTest() - { - ActODataService(args => - { - // Создаем объект данных. - КлассСМножествомТипов класс = new КлассСМножествомТипов() { PropertyDateTime = DateTime.Now }; - - // Преобразуем объект данных в JSON-строку. - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.PropertyDateTime) - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(КлассСМножествомТипов)); - - string requestJsonData = класс.ToJson(classDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(КлассСМножествомТипов)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. - /// - [Fact] - public void PostSimpleObjectTest() - { - ActODataService(args => - { - // Создаем объект данных. - Страна country = new Страна { Название = "Russia" }; - - // Преобразуем объект данных в JSON-строку. - string[] contryPropertiesNames = - { - Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Страна>(x => x.Название) - }; - var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Страна)); - - string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Страна)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом (в ней должна вернуться созданная сущность). - string receivedJsonCountry = response.Content.ReadAsStringAsync().Result.Beautify(); - - // Преобразуем полученный объект в словарь (c приведением типов значений к типам свойств объекта данных). - DataObjectDictionary receivedDictionaryCountry = DataObjectDictionary.Parse(receivedJsonCountry, contryDynamicView, args.Token.Model); - - // Сравним значения полученного и исходного объектов. - Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); - Assert.Equal(country.__PrimaryKey, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); - - Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.Название))); - Assert.Equal(country.Название, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.Название))); - - // Проверяем что объект данных был корректно создан в базе. - Страна createdCountry = new Страна { __PrimaryKey = country.__PrimaryKey }; - args.DataService.LoadObject(contryDynamicView, createdCountry); - - Assert.Equal(country.Название, createdCountry.Название); - } - }); - } - - /// - /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) - /// для простейшего объекта, т.е. мастера и детейлы не заданы и не модифицируются. - /// Объект с изменениями передается JSON-строкой. - /// - [Fact] - public void PatchSimpleObjectTest() - { - ActODataService(args => - { - // Создаем объект данных, который потом будем обновлять, и добавляем в базу обычным сервисом данных. - Лес лес = new Лес { Название = "Чаща", Площадь = 100 }; - args.DataService.UpdateObject(лес); - - // Обновляем часть атрибутов. - лес.Площадь = 150; - - // Представление, по которому будем обновлять. - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь) - }; - var лесDynamicView = new View(new ViewAttribute("лесDynamicView", медвPropertiesNames), typeof(Лес)); - - // Преобразуем объект данных в JSON-строку. - string requestJsonData = лес.ToJson(лесDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)лес.__PrimaryKey).Guid.ToString()); - - // Обращаемся к 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(лес.Площадь, updatedЛес.Площадь); - Assert.Equal(лес.Название, updatedЛес.Название); - } - }); - } - - /// - /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) - /// для мастера в детейле. - /// По стандарту сервер OData не должен обрабатывать такой запрос и поэтому вернёт HTTP Код 400. - /// Объект с изменениями передается JSON-строкой. - /// - [Fact] - public void PatchComplexObjectTest() - { - ActODataService(args => - { - // Объекты для тестирования обновления. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - Лес лес2 = new Лес { Название = "Березовая роща" }; - медв.ЛесОбитания = лес1; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; - медв.Берлога.Add(берлога1); - медв.Берлога.Add(берлога2); - - var objs = new DataObject[] { медв, лес1, лес2, берлога1, берлога2 }; - - args.DataService.UpdateObjects(ref objs); - - // Преобразуем объект данных в JSON-строку. - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - }; - 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(Берлога)); - - медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); - - Медведь медвДляЗапроса = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - Берлога берлогаДляЗапроса = new Берлога { __PrimaryKey = берлога1.__PrimaryKey, ЛесРасположения = лес2 }; - медвДляЗапроса.Берлога.Add(берлогаДляЗапроса); - - string requestJsonData = медвДляЗапроса.ToJson(медвDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку удаления данных. - /// - [Fact] - public void DeleteObjectTest() - { - ActODataService(args => - { - // ------------------ Удаление простого объекта с ключом __PrimaryKey в виде строки ----------------------------- - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - var класс = new КлассСоСтроковымКлючом(); - args.DataService.UpdateObject(класс); - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}('{1}')", args.Token.Model.GetEdmEntitySet(typeof(КлассСоСтроковымКлючом)).Name, класс.__PrimaryKey); - - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - bool exists = true; - КлассСоСтроковымКлючом deletedКлассСоСтроковымКлючом = new КлассСоСтроковымКлючом { __PrimaryKey = класс.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedКлассСоСтроковымКлючом); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - } - - // ------------------ Удаление простого объекта ----------------------------- - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - Медведь медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; - args.DataService.UpdateObject(медв); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - bool exists = true; - Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedМедв); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - } - - // ------------------ Удаление детейла и объекта с детейлами ----------------------------- - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; - медв.Берлога.Add(new Берлога { Наименование = "Берлога для хорошего настроения" }); - медв.Берлога.Add(new Берлога { Наименование = "Берлога для плохого настроения" }); - Берлога delБерлога = new Берлога { Наименование = "Отдельно удаляемая берлога" }; - медв.Берлога.Add(delБерлога); - args.DataService.UpdateObject(медв); - - // Проверяем что до вызова удалений в базе есть все детейлы. - var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); - lcs.LoadingTypes = new[] { typeof(Берлога) }; - ICSSoft.STORMNET.DataObject[] dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(3, dobjs.Length); - - // Формируем URL запроса к OData-сервису для удаления объекта-детейла (с идентификатором удаляемой сущности). - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)delБерлога.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект-детейл был удален из базы. - bool exists = true; - Берлога deletedБерлога = new Берлога { __PrimaryKey = delБерлога.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedБерлога); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - - // Проверяем что объект-агрегатор остался в базе. - exists = true; - Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedМедв); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.True(exists); - - // Проверяем что детейлов объекта в базе осталось на 1 меньше, чем создавали. - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(2, dobjs.Length); - } - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису для удаления объекта с детейлами и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - bool exists = true; - Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedМедв); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - - // Проверяем что детейлов объекта в базе не осталось. - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(0, dobjs.Length); - } - }); - } - - /// - /// Осуществляет проверку обновления мастера с иерархическими детейлами. - /// Мастер и детейлы заданы и модифицируются. - /// Объект с изменениями передается JSON-строкой. - /// - [Fact] - public void UpdateCicleDeteilTest() - { - ActODataService(args => - { - // Мастер тестирования обновления. - TestMaster testMaster1 = new TestMaster { TestMasterName = "TestMasterName" }; - var objs = new DataObject[] { testMaster1 }; - args.DataService.UpdateObjects(ref objs); - - // Колличество создаваемых детейлов. - int deteilCount = 20; - - // Детейлы тестирования обновления. - TestDetailWithCicle[] testDetailWithCicleArray = new TestDetailWithCicle[deteilCount]; - TestDetailWithCicle testDetailWithCicle = null; - - for (int i = 0; i < deteilCount; i++) - { - if (i == 0) - { - testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName0", TestMaster = testMaster1 }; - } - else - { - testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName" + i.ToString(), TestMaster = testMaster1, Parent = testDetailWithCicle }; - } - - testDetailWithCicleArray[i] = testDetailWithCicle; - objs = new DataObject[] { testDetailWithCicle }; - args.DataService.UpdateObjects(ref objs); - } - - // Обновляем атрибут мастера. - testMaster1.TestMasterName = "TestMasterNameUpdate"; - - // Представление, по которому будем обновлять. - string[] testMasterPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.TestMasterName) - }; - var testMasterDynamicView = new View(new ViewAttribute("testMasterDynamicView", testMasterPropertiesNames), typeof(TestMaster)); - - // Преобразуем объект данных в JSON-строку. - string requestJsonData = testMaster1.ToJson(testMasterDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)testMaster1.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. - TestMaster updatedTestMaster = new TestMaster { __PrimaryKey = testMaster1.__PrimaryKey }; - args.DataService.LoadObject(updatedTestMaster); - - Assert.Equal(testMaster1.TestMasterName, updatedTestMaster.TestMasterName); - } - - // Обновление атрибутов Детейлов. - for (int i = 0; i < deteilCount; i++) - { - testDetailWithCicleArray[i].TestDetailName += "Update"; - } - - // Представление, по которому будем обновлять. - string[] testDetailWithCiclePropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.TestDetailName) - }; - - var testDetailWithCicleDynamicView = new View(new ViewAttribute("testDetailWithCicleDynamicView", testDetailWithCiclePropertiesNames), typeof(TestDetailWithCicle)); - - for (int i = 0; i < deteilCount; i++) - { - // Преобразуем объект данных в JSON-строку. - string requestJsonDatatestDetailWithCicle = testDetailWithCicleArray[i].ToJson(testDetailWithCicleDynamicView, args.Token.Model); - DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDatatestDetailWithCicle, testDetailWithCicleDynamicView, args.Token.Model); - - objJson.Add("TestMaster@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, - ((KeyGuid)testMaster1.__PrimaryKey).Guid.ToString("D"))); - - if (i != 0) - { - objJson.Add("Parent@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, - ((KeyGuid)testDetailWithCicleArray[i - 1].__PrimaryKey).Guid.ToString("D"))); - } - - requestJsonDatatestDetailWithCicle = objJson.Serialize(); - - // Формируем URL запроса к OData-сервису. - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, ((KeyGuid)testDetailWithCicleArray[i].__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDatatestDetailWithCicle).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. - TestDetailWithCicle updatedTestDetailWithCicle = new TestDetailWithCicle { __PrimaryKey = testDetailWithCicleArray[i].__PrimaryKey }; - args.DataService.LoadObject(updatedTestDetailWithCicle); - - Assert.Equal(testDetailWithCicleArray[i].TestDetailName, updatedTestDetailWithCicle.TestDetailName); - } - } - }); - } - - /// - /// Test save details with inheritance. - /// - [Fact] - public void SaveDetailWithInheritanceTest() - { - ActODataService(args => - { - var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; - var детейл = new ДетейлНаследник() { prop1 = 1 }; - базовыйКласс.Детейл.Add(детейл); - - args.DataService.UpdateObject(базовыйКласс); - int newValue = 2; - детейл.prop1 = newValue; - - const string baseUrl = "http://localhost/odata"; - - string detJson = детейл.ToJson(ДетейлНаследник.Views.ДетейлНаследникE, args.Token.Model); - detJson = detJson.Replace(nameof(ДетейлНаследник.БазовыйКласс), $"{nameof(ДетейлНаследник.БазовыйКласс)}@odata.bind"); - detJson = detJson.Replace("{\"__PrimaryKey\":\"", $"\"{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}("); - detJson = detJson.Replace("\"}", ")\""); - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", - "{}", - базовыйКласс), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ДетейлНаследник)).Name}", - detJson, - детейл), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - args.DataService.LoadObject(БазовыйКласс.Views.БазовыйКлассE, базовыйКласс); - - var детейлы = базовыйКласс.Детейл.Cast<ДетейлНаследник>(); - - Assert.Equal(1, детейлы.Count()); - Assert.Equal(newValue, детейлы.First().prop1); - } - }); - } - - /// - /// Test update details with Aggregator. - /// - [Fact] - public void UpdateDetailWithAggregatorTest() - { - ActODataService(args => - { - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - }; - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; - var лапа = new Лапа() { Размер = 50 }; - кошка.Лапа.Add(лапа); - - args.DataService.UpdateObject(кошка); - - кошка.Кличка = "100"; - кошка.Тип = ТипКошки.Дикая; - лапа.Размер = 100; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", - кошка.ToJson(кошкаDynamicView, args.Token.Model), - кошка), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа, - лапа), - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); - - args.DataService.LoadObject(кошкаDynamicView, кошка); - - var лапы = кошка.Лапа.Cast<Лапа>(); - - Assert.Equal("100", кошка.Кличка); - Assert.Equal(ТипКошки.Дикая, кошка.Тип); - Assert.Equal(1, лапы.Count(б => б.Размер == 100)); - } - }); - } - - /// - /// Test update details with Aggregator. - /// - [Fact] - public void UpdateSecondDetailWithAggregatorTest() - { - ActODataService(args => - { - // Arrange. - DateTime date = new DateTime(2010, 10, 10, 10, 10, 10, DateTimeKind.Local); - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода }; - var лапа = new Лапа() { Размер = 50 }; - кошка.Лапа.Add(лапа); - var перелом = new Перелом() { Дата = DateTime.UtcNow, Тип = ТипПерелома.Открытый }; - лапа.Перелом.Add(перелом); - - args.DataService.UpdateObject(кошка); - - 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.Размер), - Information.ExtractPropertyPath<Лапа>(x => x.РазмерСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - лапа.Размер = 100; - перелом.Дата = date; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - string requestJsonDataПерелом = перелом.ToJson(переломDynamicView, args.Token.Model); - DataObjectDictionary objJsonПерелом = DataObjectDictionary.Parse(requestJsonDataПерелом, переломDynamicView, args.Token.Model); - - objJsonПерелом.Add( - $"{nameof(Перелом.Лапа)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name, - ((KeyGuid)лапа.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataПерелом = objJsonПерелом.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа, - лапа), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Перелом)).Name}", - requestJsonDataПерелом, - перелом), - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); - лапаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Лапа>(x => x.Перелом), переломDynamicView, true); - - args.DataService.LoadObject(кошкаDynamicView, кошка); - - var лапы = кошка.Лапа.Cast<Лапа>(); - - var переломы = лапы.FirstOrDefault().Перелом.Cast<Перелом>(); - - Assert.Equal("50", кошка.Кличка); - Assert.Equal(1, лапы.Count(б => б.Размер == 100)); - Assert.Equal(1, переломы.Count(б => б.Дата == date.ToUniversalTime())); - } - }); - } - - /// - /// Test delete and add detail. - /// - [Fact] - public void UpdateDeletedAndAddedDetailWithAggregatorTest() - { - ActODataService(args => - { - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - }; - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; - var лапа = new Лапа() { Размер = 50 }; - var лапа2 = new Лапа() { Размер = 1000 }; - var лапа3 = new Лапа() { Размер = 2000 }; - кошка.Лапа.Add(лапа); - кошка.Лапа.Add(лапа2); - - args.DataService.UpdateObject(кошка); - - кошка.Кличка = "100"; - кошка.Тип = ТипКошки.Дикая; - лапа.Размер = 100; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - лапа2.SetStatus(ObjectStatus.Deleted); - - string requestJsonDataЛапа3 = лапа3.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа3 = DataObjectDictionary.Parse(requestJsonDataЛапа3, лапаDynamicView, args.Token.Model); - - objJsonЛапа3.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа3 = objJsonЛапа3.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - string.Empty, - лапа2), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа3, - лапа3), - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.NoContent, HttpStatusCode.Created }); - - кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); - - args.DataService.LoadObject(кошкаDynamicView, кошка); - - var лапы = кошка.Лапа.Cast<Лапа>(); - - Assert.Equal(2, лапы.Count()); - Assert.Equal(1, лапы.Count(x => x.Размер == 2000)); - Assert.Equal(0, лапы.Count(x => x.Размер == 1000)); - } - }); - } - - /// - /// Test batch update error handling when business server throws exception. - /// - [Fact] - public void BatchUpdateErrorHandlingTest() - { - ActODataService(args => - { - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - }; - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; - var лапа = new Лапа() { Размер = 50 }; - кошка.Лапа.Add(лапа); - - args.DataService.UpdateObject(кошка); - - кошка.Кличка = "100"; - кошка.Тип = ТипКошки.Дикая; - - // Этот размер лапы указан в CatsBS как недопустимый размер, будет сгенерировано исключение. - лапа.Размер = 100899; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", - кошка.ToJson(кошкаDynamicView, args.Token.Model), - кошка), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа, - лапа), - }; - - int exceptionHandled = 0; - - args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => - { - Exception currentException = exception; - - while (currentException != null) - { - if (currentException.Message == "Недопустимый размер кошачьей лапы!") - { - exceptionHandled++; - } - - currentException = currentException.InnerException; - } - - return exception; - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); - Assert.Equal(1, exceptionHandled); - } - }); - } - - /// - /// Test update agregator with inheritance details. - /// - [Fact] - public void UpdateAgregatorWithInheritanceDetailsTest() - { - ActODataService(args => - { - var son = new Son() { Name = "Yakov", SuspendersColor = "Brown" }; - var daughter = new Daughter() { Name = "Yana", DressColor = "Red" }; - var person = new Person() { Name = "Yan" }; - person.Childrens.AddRange(son, daughter); - - args.DataService.UpdateObject(person); - - person.Name = "Yan Yakovlevich"; - - // Преобразуем объект данных в JSON-строку. - string[] personPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name) - }; - - var personDynamicView = new View(new ViewAttribute("personDynamicView", personPropertiesNames), typeof(Person)); - - string requestJsonData = person.ToJson(personDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Person)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Test batch update detail of detail. - /// - [Fact] - public void UpdateDetailOfDetailTest() - { - ActODataService(args => - { - var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; - var детейл = new Детейл() { prop1 = 1 }; - базовыйКласс.Детейл.Add(детейл); - var детейл2 = new Детейл2() { prop2 = "2" }; - детейл.Детейл2.Add(детейл2); - - args.DataService.UpdateObject(базовыйКласс); - string newValue = "new"; - базовыйКласс.Свойство1 = newValue; - детейл2.prop2 = newValue; - - const string baseUrl = "http://localhost/odata"; - - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), - }; - - string[] detail2PropertiesNames = - { - Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Детейл2>(x => x.prop2), - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); - var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); - - string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); - string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); - - DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); - - objJson.Add( - $"{nameof(Детейл2.Детейл)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, - ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); - - detJson = objJson.Serialize(); - - detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", - classJson, - базовыйКласс), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", - detJson, - детейл2), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - args.DataService.LoadObject(detail2DynamicView, детейл2); - - Assert.Equal(newValue, детейл2.prop2); - } - }); - } - - /// - /// Test batch update detail of detail. - /// - [Fact] - public void UpdateDetailOfDetailDescOrderTest() - { - ActODataService(args => - { - var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; - var детейл = new Детейл() { prop1 = 1 }; - базовыйКласс.Детейл.Add(детейл); - var детейл2 = new Детейл2() { prop2 = "2" }; - детейл.Детейл2.Add(детейл2); - - args.DataService.UpdateObject(базовыйКласс); - string newValue = "new"; - базовыйКласс.Свойство1 = newValue; - детейл2.prop2 = newValue; - - const string baseUrl = "http://localhost/odata"; - - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), - }; - - string[] detail2PropertiesNames = - { - Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Детейл2>(x => x.prop2), - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); - var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); - - string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); - string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); - - DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); - - objJson.Add( - $"{nameof(Детейл2.Детейл)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, - ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); - - detJson = objJson.Serialize(); - - detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", - detJson, - детейл2), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", - classJson, - базовыйКласс), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - args.DataService.LoadObject(detail2DynamicView, детейл2); - - Assert.Equal(newValue, детейл2.prop2); - } - }); - } - - /// - /// Test batch update detail and another type detail. - /// - [Fact] - public void UpdateDetailAndDetailTest() - { - ActODataService(args => - { - var базовыйКласс = new Библиотека() { Адрес = "ул. Пушкина" }; - var мастер = new Автор() { Имя = "Александр" }; - var детейл1 = new Книга() { Название = "Му-Му", Автор1 = мастер }; - базовыйКласс.Книга.Add(детейл1); - var детейл2 = new Журнал() { Номер = 2, Автор2 = мастер }; - базовыйКласс.Журнал.Add(детейл2); - - args.DataService.UpdateObject(базовыйКласс); - string newValue = "ул. Лермонтова"; - int newIntValue = 3; - базовыйКласс.Адрес = newValue; - детейл1.Название = newValue; - детейл2.Номер = newIntValue; - - const string baseUrl = "http://localhost/odata"; - - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<Библиотека>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Библиотека>(x => x.Адрес), - }; - - string[] detail1PropertiesNames = - { - Information.ExtractPropertyPath<Книга>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Книга>(x => x.Название), - }; - - string[] detail2PropertiesNames = - { - Information.ExtractPropertyPath<Журнал>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Журнал>(x => x.Номер), - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(Библиотека)); - var detail1DynamicView = new View(new ViewAttribute("detailDynamicView", detail1PropertiesNames), typeof(Книга)); - var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Журнал)); - - string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); - string det1Json = детейл1.ToJson(detail1DynamicView, args.Token.Model); - string det2Json = детейл2.ToJson(detail2DynamicView, args.Token.Model); - - DataObjectDictionary obj1Json = DataObjectDictionary.Parse(det1Json, detail1DynamicView, args.Token.Model); - - obj1Json.Add( - $"{nameof(Книга.Библиотека1)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, - ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); - - det1Json = obj1Json.Serialize(); - - DataObjectDictionary obj2Json = DataObjectDictionary.Parse(det2Json, detail2DynamicView, args.Token.Model); - - obj2Json.Add( - $"{nameof(Журнал.Библиотека2)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, - ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); - - det2Json = obj2Json.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name}", - classJson, - базовыйКласс), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Книга)).Name}", - det1Json, - детейл1), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Журнал)).Name}", - det2Json, - детейл2), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK }); - - classDynamicView.AddDetailInView(nameof(Библиотека.Книга), detail1DynamicView, true); - classDynamicView.AddDetailInView(nameof(Библиотека.Журнал), detail2DynamicView, true); - - args.DataService.LoadObject(classDynamicView, базовыйКласс); - - Assert.Equal(newValue, базовыйКласс.Адрес = newValue); - Assert.Equal(newValue, базовыйКласс.Книга[0].Название); - Assert.Equal(newIntValue, базовыйКласс.Журнал[0].Номер); - - } - }); - } - - /// - /// Осуществляет проверку удаления данных. - /// - [Fact] - public void DeletePlainObjectTest() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - Медведь agregator = new Медведь() { МедведьСтрокой = "Agregator" }; - args.DataService.UpdateObject(agregator); - - View view = new View(typeof(Медведь), View.ReadType.OnlyThatObject); - Медведь foundAgregator0 = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - //Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - Медведь foundAgregator = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - - /// - /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но при этом пустые. - /// - [Fact] - public void DeleteObjectWithSameDetailAndMasterEmptyTest() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; - args.DataService.UpdateObject(agregator); - - View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); - AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - - /// - /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но лишь детейл заполнен. - /// - [Fact] - public void DeleteObjectWithSameDetailAndMasterTest() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; - args.DataService.UpdateObject(agregator); - - DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; - agregator.Details.Add(dm); - args.DataService.UpdateObject(dm); - - AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; - args.DataService.UpdateObject(agregator2); - DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; - agregator2.Details.Add(dm2); - args.DataService.UpdateObject(dm2); - - View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); - AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - - /// - /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но не пустые. - /// - [Fact] - public void DeleteObjectWithSameDetailAndMaster2Test() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; - args.DataService.UpdateObject(agregator); - - DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; - agregator.Details.Add(dm); - args.DataService.UpdateObject(dm); - - AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; - args.DataService.UpdateObject(agregator2); - DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; - agregator2.Details.Add(dm2); - args.DataService.UpdateObject(dm2); - - agregator.Master = dm2; - args.DataService.UpdateObject(agregator); - - View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); - AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - } -} +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing; + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET.Business.LINQProvider; + using ICSSoft.STORMNET.Exceptions; + using ICSSoft.STORMNET.KeyGen; + using ICSSoft.STORMNET.Windows.Forms; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; + + using Newtonsoft.Json; + using Xunit; + + /// + /// Класс тестов для тестирования операций модификации данных OData-сервисом (вставка, обновление, удаление). + /// + public class ModifyDataTest : BaseODataServiceIntegratedTest + { +#if NETCOREAPP + /// + /// Конструктор по-умолчанию. + /// + /// Фабрика для приложения. + /// Вывод отладочной информации. + public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + : base(factory, output) + { + } +#endif + + /// + /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. + /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . + /// Тест проверяет следующие факты: + /// + /// Вставка связи мастерового объекта. + /// Удаление связи мастеровго объекта путём присвоения null свойству. + /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. + /// + /// + [Fact] + public void PatchNavigationPropertiesTest() + { + ActODataService(args => + { + ExternalLangDef.LanguageDef.DataService = args.DataService; + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.ПолеБС), + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.Наименование), + Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) + }; + string[] лесPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь), + Information.ExtractPropertyPath<Лес>(x => x.Название), + Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) + }; + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Медведь>(x => x.ПолеБС), + Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Медведь>(x => x.Вес), + + // Information.ExtractPropertyPath<Медведь>(x => x.Пол), + Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), + Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + + // Объекты для тестирования создания. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + Лес лес2 = new Лес { Название = "Березовая роща" }; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; + медв.Берлога.Add(берлога1); + var objs = new DataObject[] { медв, лес1, лес2, берлога1 }; + args.DataService.UpdateObjects(ref objs); + string requestUrl; + + string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + + objJson.Add("ЛесОбитания@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, + ((KeyGuid)лес1.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); + objJson.Add("Медведь", null); + requestJsonDataБерлога = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) + { + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + objJson.Add("ЛесОбитания@odata.bind", null); + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + objJson.Add("ЛесОбитания", null); + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + objJson.Add("Берлога@odata.bind", null); + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах происходит вставка и удаление связей объекта. + /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . + /// Тест проверяет следующие факты: + /// + /// Вставка связи мастерового объекта. + /// Удаление связи мастеровго объекта путём присвоения null свойству. + /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. + /// + /// + [Fact] + public void PostNavigationPropertiesTest() + { + string[] берлогаPropertiesNames = + { + // Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.Наименование), + Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) + }; + string[] лесPropertiesNames = + { + // Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь), + Information.ExtractPropertyPath<Лес>(x => x.Название), + Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) + }; + string[] медвPropertiesNames = + { + // Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Медведь>(x => x.Вес), + + // Information.ExtractPropertyPath<Медведь>(x => x.Пол), + Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), + Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + + // Объекты для тестирования создания. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + ActODataService(args => + { + string requestUrl; + string receivedJsonЛес1, receivedJsonМедв; + string requestJsonDataЛес1 = лес1.ToJson(лесDynamicView, args.Token.Model); + requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataЛес1).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом (в ней должна вернуться созданная сущность). + receivedJsonЛес1 = response.Content.ReadAsStringAsync().Result.Beautify(); + } + + string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedJsonЛес1); + + objJsonМедв.Add("ЛесОбитания@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, + receivedDict["__PrimaryKey"])); + objJsonМедв.Add("Берлога@odata.bind", null); + + requestJsonDataМедв = objJsonМедв.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом (в ней должна вернуться созданная сущность). + receivedJsonМедв = response.Content.ReadAsStringAsync().Result.Beautify(); + } + + var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + var objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); + receivedDict = JsonConvert.DeserializeObject>(receivedJsonМедв); + objJson.Add("Медведь@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, + receivedDict["__PrimaryKey"])); + requestJsonDataБерлога = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name); + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) + { + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах происходит вставка объекта, + /// зависимые объекты (мастера, детейлы) обрабатываются в зависимости от наличия в БД - вставляются или обновляются. + /// + [Fact] + public void PostComplexObjectTest() + { + ActODataService(args => + { + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.Наименование), + Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения), + Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения.Название), + }; + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Медведь>(x => x.Вес), + Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания), + Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания.Название), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); + + // Объекты для тестирования создания. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + Лес лес2 = new Лес { Название = "Березовая роща" }; + медв.ЛесОбитания = лес1; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; + медв.Берлога.Add(берлога1); + медв.Берлога.Add(берлога2); + + string json = медв.ToJson(медвDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; + + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом. + string receivedJsonObjs = response.Content.ReadAsStringAsync().Result.Beautify(); + + // В ответе приходит объект с созданной сущностью. + // Преобразуем полученный объект в словарь. + Dictionary receivedObjs = JsonConvert.DeserializeObject>(receivedJsonObjs); + + // Проверяем созданный объект, вычитав с помощью DataService + DataObject createdObj = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + args.DataService.LoadObject(createdObj); + + Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); + Assert.Equal(((Медведь)createdObj).Вес, (int)(long)receivedObjs["Вес"]); + + // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService + var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); + lcs.LoadingTypes = new[] { typeof(Лес) }; + DataObject[] dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(2, dobjs.Length); + + lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); + lcs.LoadingTypes = new[] { typeof(Берлога) }; + dobjs = args.DataService.LoadObjects(lcs); + Assert.Equal(2, dobjs.Length); + + // Создание объекта и обновление связанных + // Создаем нового медведя: в его мастере ЛесОбитания - лес1, но в нём изменим Название; в детейлы заберем от первого медведя детейл2, изменив Название в мастере детейла. + // Подготовка тестовых данных в формате OData. + Медведь медвежонок = new Медведь { Вес = 12 }; + var берлога3 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + медвежонок.Берлога.Add(берлога3); + + медв.Берлога.Remove(берлога2); + медвежонок.Берлога.Add(берлога2); + + лес1.Название = лес1.Название + "(обновл)"; + лес2.Название = лес2.Название + "(обновл)"; + + json = медвежонок.ToJson(медвDynamicView, args.Token.Model); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; + + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Проверяем созданный объект, вычитав с помощью DataService + createdObj = new Медведь { __PrimaryKey = медвежонок.__PrimaryKey }; + args.DataService.LoadObject(createdObj); + + Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); + Assert.Equal(12, ((Медведь)createdObj).Вес); + + // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService + ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; + lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); + lcs.LoadingTypes = new[] { typeof(Лес) }; + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), + лес1.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Single(dobjs); + Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); + + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), + лес2.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Single(dobjs); + Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); + + lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); + lcs.LoadingTypes = new[] { typeof(Берлога) }; + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), + медв.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Single(dobjs); + + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), + медвежонок.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(2, dobjs.Length); + }); + } + + /// + /// Осуществляет проверку создания сущности с датой и незаданным первичным ключом. + /// + [Fact] + public void PostObjDateTimeNoPKTest() + { + ActODataService(args => + { + // Создаем объект данных. + Лес country = new Лес { Площадь = 10, Название = "Бор", ДатаПоследнегоОсмотра = (ICSSoft.STORMNET.UserDataTypes.NullableDateTime)DateTime.Now }; + + // Преобразуем объект данных в JSON-строку. + string[] contryPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь), + Information.ExtractPropertyPath<Лес>(x => x.Название), + Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) + }; + var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Лес)); + + string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. + /// + [Fact] + public void PostDataTimeValueTest() + { + ActODataService(args => + { + // Создаем объект данных. + КлассСМножествомТипов класс = new КлассСМножествомТипов() { PropertyDateTime = DateTime.Now }; + + // Преобразуем объект данных в JSON-строку. + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.PropertyDateTime) + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(КлассСМножествомТипов)); + + string requestJsonData = класс.ToJson(classDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(КлассСМножествомТипов)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. + /// + [Fact] + public void PostSimpleObjectTest() + { + ActODataService(args => + { + // Создаем объект данных. + Страна country = new Страна { Название = "Russia" }; + + // Преобразуем объект данных в JSON-строку. + string[] contryPropertiesNames = + { + Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Страна>(x => x.Название) + }; + var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Страна)); + + string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Страна)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом (в ней должна вернуться созданная сущность). + string receivedJsonCountry = response.Content.ReadAsStringAsync().Result.Beautify(); + + // Преобразуем полученный объект в словарь (c приведением типов значений к типам свойств объекта данных). + DataObjectDictionary receivedDictionaryCountry = DataObjectDictionary.Parse(receivedJsonCountry, contryDynamicView, args.Token.Model); + + // Сравним значения полученного и исходного объектов. + Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); + Assert.Equal(country.__PrimaryKey, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); + + Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.Название))); + Assert.Equal(country.Название, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.Название))); + + // Проверяем что объект данных был корректно создан в базе. + Страна createdCountry = new Страна { __PrimaryKey = country.__PrimaryKey }; + args.DataService.LoadObject(contryDynamicView, createdCountry); + + Assert.Equal(country.Название, createdCountry.Название); + } + }); + } + + /// + /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) + /// для простейшего объекта, т.е. мастера и детейлы не заданы и не модифицируются. + /// Объект с изменениями передается JSON-строкой. + /// + [Fact] + public void PatchSimpleObjectTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем обновлять, и добавляем в базу обычным сервисом данных. + Лес лес = new Лес { Название = "Чаща", Площадь = 100 }; + args.DataService.UpdateObject(лес); + + // Обновляем часть атрибутов. + лес.Площадь = 150; + + // Представление, по которому будем обновлять. + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь) + }; + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", медвPropertiesNames), typeof(Лес)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = лес.ToJson(лесDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)лес.__PrimaryKey).Guid.ToString()); + + // Обращаемся к 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(лес.Площадь, updatedЛес.Площадь); + Assert.Equal(лес.Название, updatedЛес.Название); + } + }); + } + + /// + /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) + /// для мастера в детейле. + /// По стандарту сервер OData не должен обрабатывать такой запрос и поэтому вернёт HTTP Код 400. + /// Объект с изменениями передается JSON-строкой. + /// + [Fact] + public void PatchComplexObjectTest() + { + ActODataService(args => + { + // Объекты для тестирования обновления. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + Лес лес2 = new Лес { Название = "Березовая роща" }; + медв.ЛесОбитания = лес1; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; + медв.Берлога.Add(берлога1); + медв.Берлога.Add(берлога2); + + var objs = new DataObject[] { медв, лес1, лес2, берлога1, берлога2 }; + + args.DataService.UpdateObjects(ref objs); + + // Преобразуем объект данных в JSON-строку. + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + }; + 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(Берлога)); + + медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); + + Медведь медвДляЗапроса = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + Берлога берлогаДляЗапроса = new Берлога { __PrimaryKey = берлога1.__PrimaryKey, ЛесРасположения = лес2 }; + медвДляЗапроса.Берлога.Add(берлогаДляЗапроса); + + string requestJsonData = медвДляЗапроса.ToJson(медвDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку удаления данных. + /// + [Fact] + public void DeleteObjectTest() + { + ActODataService(args => + { + // ------------------ Удаление простого объекта с ключом __PrimaryKey в виде строки ----------------------------- + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + var класс = new КлассСоСтроковымКлючом(); + args.DataService.UpdateObject(класс); + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}('{1}')", args.Token.Model.GetEdmEntitySet(typeof(КлассСоСтроковымКлючом)).Name, класс.__PrimaryKey); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + bool exists = true; + КлассСоСтроковымКлючом deletedКлассСоСтроковымКлючом = new КлассСоСтроковымКлючом { __PrimaryKey = класс.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedКлассСоСтроковымКлючом); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + } + + // ------------------ Удаление простого объекта ----------------------------- + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + Медведь медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; + args.DataService.UpdateObject(медв); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + bool exists = true; + Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedМедв); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + } + + // ------------------ Удаление детейла и объекта с детейлами ----------------------------- + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; + медв.Берлога.Add(new Берлога { Наименование = "Берлога для хорошего настроения" }); + медв.Берлога.Add(new Берлога { Наименование = "Берлога для плохого настроения" }); + Берлога delБерлога = new Берлога { Наименование = "Отдельно удаляемая берлога" }; + медв.Берлога.Add(delБерлога); + args.DataService.UpdateObject(медв); + + // Проверяем что до вызова удалений в базе есть все детейлы. + var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); + lcs.LoadingTypes = new[] { typeof(Берлога) }; + ICSSoft.STORMNET.DataObject[] dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(3, dobjs.Length); + + // Формируем URL запроса к OData-сервису для удаления объекта-детейла (с идентификатором удаляемой сущности). + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)delБерлога.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект-детейл был удален из базы. + bool exists = true; + Берлога deletedБерлога = new Берлога { __PrimaryKey = delБерлога.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedБерлога); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + + // Проверяем что объект-агрегатор остался в базе. + exists = true; + Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedМедв); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.True(exists); + + // Проверяем что детейлов объекта в базе осталось на 1 меньше, чем создавали. + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(2, dobjs.Length); + } + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису для удаления объекта с детейлами и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + bool exists = true; + Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedМедв); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + + // Проверяем что детейлов объекта в базе не осталось. + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(0, dobjs.Length); + } + }); + } + + /// + /// Осуществляет проверку обновления мастера с иерархическими детейлами. + /// Мастер и детейлы заданы и модифицируются. + /// Объект с изменениями передается JSON-строкой. + /// + [Fact] + public void UpdateCicleDeteilTest() + { + ActODataService(args => + { + // Мастер тестирования обновления. + TestMaster testMaster1 = new TestMaster { TestMasterName = "TestMasterName" }; + var objs = new DataObject[] { testMaster1 }; + args.DataService.UpdateObjects(ref objs); + + // Колличество создаваемых детейлов. + int deteilCount = 20; + + // Детейлы тестирования обновления. + TestDetailWithCicle[] testDetailWithCicleArray = new TestDetailWithCicle[deteilCount]; + TestDetailWithCicle testDetailWithCicle = null; + + for (int i = 0; i < deteilCount; i++) + { + if (i == 0) + { + testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName0", TestMaster = testMaster1 }; + } + else + { + testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName" + i.ToString(), TestMaster = testMaster1, Parent = testDetailWithCicle }; + } + + testDetailWithCicleArray[i] = testDetailWithCicle; + objs = new DataObject[] { testDetailWithCicle }; + args.DataService.UpdateObjects(ref objs); + } + + // Обновляем атрибут мастера. + testMaster1.TestMasterName = "TestMasterNameUpdate"; + + // Представление, по которому будем обновлять. + string[] testMasterPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.TestMasterName) + }; + var testMasterDynamicView = new View(new ViewAttribute("testMasterDynamicView", testMasterPropertiesNames), typeof(TestMaster)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = testMaster1.ToJson(testMasterDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)testMaster1.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + TestMaster updatedTestMaster = new TestMaster { __PrimaryKey = testMaster1.__PrimaryKey }; + args.DataService.LoadObject(updatedTestMaster); + + Assert.Equal(testMaster1.TestMasterName, updatedTestMaster.TestMasterName); + } + + // Обновление атрибутов Детейлов. + for (int i = 0; i < deteilCount; i++) + { + testDetailWithCicleArray[i].TestDetailName += "Update"; + } + + // Представление, по которому будем обновлять. + string[] testDetailWithCiclePropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.TestDetailName) + }; + + var testDetailWithCicleDynamicView = new View(new ViewAttribute("testDetailWithCicleDynamicView", testDetailWithCiclePropertiesNames), typeof(TestDetailWithCicle)); + + for (int i = 0; i < deteilCount; i++) + { + // Преобразуем объект данных в JSON-строку. + string requestJsonDatatestDetailWithCicle = testDetailWithCicleArray[i].ToJson(testDetailWithCicleDynamicView, args.Token.Model); + DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDatatestDetailWithCicle, testDetailWithCicleDynamicView, args.Token.Model); + + objJson.Add("TestMaster@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, + ((KeyGuid)testMaster1.__PrimaryKey).Guid.ToString("D"))); + + if (i != 0) + { + objJson.Add("Parent@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, + ((KeyGuid)testDetailWithCicleArray[i - 1].__PrimaryKey).Guid.ToString("D"))); + } + + requestJsonDatatestDetailWithCicle = objJson.Serialize(); + + // Формируем URL запроса к OData-сервису. + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, ((KeyGuid)testDetailWithCicleArray[i].__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDatatestDetailWithCicle).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + TestDetailWithCicle updatedTestDetailWithCicle = new TestDetailWithCicle { __PrimaryKey = testDetailWithCicleArray[i].__PrimaryKey }; + args.DataService.LoadObject(updatedTestDetailWithCicle); + + Assert.Equal(testDetailWithCicleArray[i].TestDetailName, updatedTestDetailWithCicle.TestDetailName); + } + } + }); + } + + /// + /// Test save details with inheritance. + /// + [Fact] + public void SaveDetailWithInheritanceTest() + { + ActODataService(args => + { + var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; + var детейл = new ДетейлНаследник() { prop1 = 1 }; + базовыйКласс.Детейл.Add(детейл); + + args.DataService.UpdateObject(базовыйКласс); + int newValue = 2; + детейл.prop1 = newValue; + + const string baseUrl = "http://localhost/odata"; + + string detJson = детейл.ToJson(ДетейлНаследник.Views.ДетейлНаследникE, args.Token.Model); + detJson = detJson.Replace(nameof(ДетейлНаследник.БазовыйКласс), $"{nameof(ДетейлНаследник.БазовыйКласс)}@odata.bind"); + detJson = detJson.Replace("{\"__PrimaryKey\":\"", $"\"{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}("); + detJson = detJson.Replace("\"}", ")\""); + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", + "{}", + базовыйКласс), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ДетейлНаследник)).Name}", + detJson, + детейл), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + args.DataService.LoadObject(БазовыйКласс.Views.БазовыйКлассE, базовыйКласс); + + var детейлы = базовыйКласс.Детейл.Cast<ДетейлНаследник>(); + + Assert.Equal(1, детейлы.Count()); + Assert.Equal(newValue, детейлы.First().prop1); + } + }); + } + + /// + /// Test update details with Aggregator. + /// + [Fact] + public void UpdateDetailWithAggregatorTest() + { + ActODataService(args => + { + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + }; + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; + var лапа = new Лапа() { Размер = 50 }; + кошка.Лапа.Add(лапа); + + args.DataService.UpdateObject(кошка); + + кошка.Кличка = "100"; + кошка.Тип = ТипКошки.Дикая; + лапа.Размер = 100; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + кошка.ToJson(кошкаDynamicView, args.Token.Model), + кошка), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа, + лапа), + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); + + args.DataService.LoadObject(кошкаDynamicView, кошка); + + var лапы = кошка.Лапа.Cast<Лапа>(); + + Assert.Equal("100", кошка.Кличка); + Assert.Equal(ТипКошки.Дикая, кошка.Тип); + Assert.Equal(1, лапы.Count(б => б.Размер == 100)); + } + }); + } + + /// + /// Test update details with Aggregator. + /// + [Fact] + public void UpdateSecondDetailWithAggregatorTest() + { + ActODataService(args => + { + // Arrange. + DateTime date = new DateTime(2010, 10, 10, 10, 10, 10, DateTimeKind.Local); + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода }; + var лапа = new Лапа() { Размер = 50 }; + кошка.Лапа.Add(лапа); + var перелом = new Перелом() { Дата = DateTime.UtcNow, Тип = ТипПерелома.Открытый }; + лапа.Перелом.Add(перелом); + + args.DataService.UpdateObject(кошка); + + 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.Размер), + Information.ExtractPropertyPath<Лапа>(x => x.РазмерСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + лапа.Размер = 100; + перелом.Дата = date; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + string requestJsonDataПерелом = перелом.ToJson(переломDynamicView, args.Token.Model); + DataObjectDictionary objJsonПерелом = DataObjectDictionary.Parse(requestJsonDataПерелом, переломDynamicView, args.Token.Model); + + objJsonПерелом.Add( + $"{nameof(Перелом.Лапа)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name, + ((KeyGuid)лапа.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataПерелом = objJsonПерелом.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа, + лапа), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Перелом)).Name}", + requestJsonDataПерелом, + перелом), + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); + лапаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Лапа>(x => x.Перелом), переломDynamicView, true); + + args.DataService.LoadObject(кошкаDynamicView, кошка); + + var лапы = кошка.Лапа.Cast<Лапа>(); + + var переломы = лапы.FirstOrDefault().Перелом.Cast<Перелом>(); + + Assert.Equal("50", кошка.Кличка); + Assert.Equal(1, лапы.Count(б => б.Размер == 100)); + Assert.Equal(1, переломы.Count(б => б.Дата == date.ToUniversalTime())); + } + }); + } + + /// + /// Test delete and add detail. + /// + [Fact] + public void UpdateDeletedAndAddedDetailWithAggregatorTest() + { + ActODataService(args => + { + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + }; + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; + var лапа = new Лапа() { Размер = 50 }; + var лапа2 = new Лапа() { Размер = 1000 }; + var лапа3 = new Лапа() { Размер = 2000 }; + кошка.Лапа.Add(лапа); + кошка.Лапа.Add(лапа2); + + args.DataService.UpdateObject(кошка); + + кошка.Кличка = "100"; + кошка.Тип = ТипКошки.Дикая; + лапа.Размер = 100; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + лапа2.SetStatus(ObjectStatus.Deleted); + + string requestJsonDataЛапа3 = лапа3.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа3 = DataObjectDictionary.Parse(requestJsonDataЛапа3, лапаDynamicView, args.Token.Model); + + objJsonЛапа3.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа3 = objJsonЛапа3.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + string.Empty, + лапа2), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа3, + лапа3), + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.NoContent, HttpStatusCode.Created }); + + кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); + + args.DataService.LoadObject(кошкаDynamicView, кошка); + + var лапы = кошка.Лапа.Cast<Лапа>(); + + Assert.Equal(2, лапы.Count()); + Assert.Equal(1, лапы.Count(x => x.Размер == 2000)); + Assert.Equal(0, лапы.Count(x => x.Размер == 1000)); + } + }); + } + + /// + /// Test batch update error handling when business server throws exception. + /// + [Fact] + public void BatchUpdateErrorHandlingTest() + { + ActODataService(args => + { + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + }; + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; + var лапа = new Лапа() { Размер = 50 }; + кошка.Лапа.Add(лапа); + + args.DataService.UpdateObject(кошка); + + кошка.Кличка = "100"; + кошка.Тип = ТипКошки.Дикая; + + // Этот размер лапы указан в CatsBS как недопустимый размер, будет сгенерировано исключение. + лапа.Размер = 100899; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + кошка.ToJson(кошкаDynamicView, args.Token.Model), + кошка), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа, + лапа), + }; + + int exceptionHandled = 0; + + args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => + { + Exception currentException = exception; + + while (currentException != null) + { + if (currentException.Message == "Недопустимый размер кошачьей лапы!") + { + exceptionHandled++; + } + + currentException = currentException.InnerException; + } + + return exception; + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Equal(1, exceptionHandled); + } + }); + } + + /// + /// Test update agregator with inheritance details. + /// + [Fact] + public void UpdateAgregatorWithInheritanceDetailsTest() + { + ActODataService(args => + { + var son = new Son() { Name = "Yakov", SuspendersColor = "Brown" }; + var daughter = new Daughter() { Name = "Yana", DressColor = "Red" }; + var person = new Person() { Name = "Yan" }; + person.Childrens.AddRange(son, daughter); + + args.DataService.UpdateObject(person); + + person.Name = "Yan Yakovlevich"; + + // Преобразуем объект данных в JSON-строку. + string[] personPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name) + }; + + var personDynamicView = new View(new ViewAttribute("personDynamicView", personPropertiesNames), typeof(Person)); + + string requestJsonData = person.ToJson(personDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Person)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Test batch update detail of detail. + /// + [Fact] + public void UpdateDetailOfDetailTest() + { + ActODataService(args => + { + var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; + var детейл = new Детейл() { prop1 = 1 }; + базовыйКласс.Детейл.Add(детейл); + var детейл2 = new Детейл2() { prop2 = "2" }; + детейл.Детейл2.Add(детейл2); + + args.DataService.UpdateObject(базовыйКласс); + string newValue = "new"; + базовыйКласс.Свойство1 = newValue; + детейл2.prop2 = newValue; + + const string baseUrl = "http://localhost/odata"; + + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), + }; + + string[] detail2PropertiesNames = + { + Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Детейл2>(x => x.prop2), + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); + var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); + + string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); + string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); + + DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); + + objJson.Add( + $"{nameof(Детейл2.Детейл)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, + ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); + + detJson = objJson.Serialize(); + + detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", + classJson, + базовыйКласс), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", + detJson, + детейл2), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + args.DataService.LoadObject(detail2DynamicView, детейл2); + + Assert.Equal(newValue, детейл2.prop2); + } + }); + } + + /// + /// Test batch update detail of detail. + /// + [Fact] + public void UpdateDetailOfDetailDescOrderTest() + { + ActODataService(args => + { + var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; + var детейл = new Детейл() { prop1 = 1 }; + базовыйКласс.Детейл.Add(детейл); + var детейл2 = new Детейл2() { prop2 = "2" }; + детейл.Детейл2.Add(детейл2); + + args.DataService.UpdateObject(базовыйКласс); + string newValue = "new"; + базовыйКласс.Свойство1 = newValue; + детейл2.prop2 = newValue; + + const string baseUrl = "http://localhost/odata"; + + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), + }; + + string[] detail2PropertiesNames = + { + Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Детейл2>(x => x.prop2), + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); + var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); + + string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); + string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); + + DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); + + objJson.Add( + $"{nameof(Детейл2.Детейл)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, + ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); + + detJson = objJson.Serialize(); + + detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", + detJson, + детейл2), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", + classJson, + базовыйКласс), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + args.DataService.LoadObject(detail2DynamicView, детейл2); + + Assert.Equal(newValue, детейл2.prop2); + } + }); + } + + /// + /// Test batch update detail and another type detail. + /// + [Fact] + public void UpdateDetailAndDetailTest() + { + ActODataService(args => + { + var базовыйКласс = new Библиотека() { Адрес = "ул. Пушкина" }; + var мастер = new Автор() { Имя = "Александр" }; + var детейл1 = new Книга() { Название = "Му-Му", Автор1 = мастер }; + базовыйКласс.Книга.Add(детейл1); + var детейл2 = new Журнал() { Номер = 2, Автор2 = мастер }; + базовыйКласс.Журнал.Add(детейл2); + + args.DataService.UpdateObject(базовыйКласс); + string newValue = "ул. Лермонтова"; + int newIntValue = 3; + базовыйКласс.Адрес = newValue; + детейл1.Название = newValue; + детейл2.Номер = newIntValue; + + const string baseUrl = "http://localhost/odata"; + + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<Библиотека>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Библиотека>(x => x.Адрес), + }; + + string[] detail1PropertiesNames = + { + Information.ExtractPropertyPath<Книга>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Книга>(x => x.Название), + }; + + string[] detail2PropertiesNames = + { + Information.ExtractPropertyPath<Журнал>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Журнал>(x => x.Номер), + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(Библиотека)); + var detail1DynamicView = new View(new ViewAttribute("detailDynamicView", detail1PropertiesNames), typeof(Книга)); + var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Журнал)); + + string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); + string det1Json = детейл1.ToJson(detail1DynamicView, args.Token.Model); + string det2Json = детейл2.ToJson(detail2DynamicView, args.Token.Model); + + DataObjectDictionary obj1Json = DataObjectDictionary.Parse(det1Json, detail1DynamicView, args.Token.Model); + + obj1Json.Add( + $"{nameof(Книга.Библиотека1)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, + ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); + + det1Json = obj1Json.Serialize(); + + DataObjectDictionary obj2Json = DataObjectDictionary.Parse(det2Json, detail2DynamicView, args.Token.Model); + + obj2Json.Add( + $"{nameof(Журнал.Библиотека2)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, + ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); + + det2Json = obj2Json.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name}", + classJson, + базовыйКласс), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Книга)).Name}", + det1Json, + детейл1), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Журнал)).Name}", + det2Json, + детейл2), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK }); + + classDynamicView.AddDetailInView(nameof(Библиотека.Книга), detail1DynamicView, true); + classDynamicView.AddDetailInView(nameof(Библиотека.Журнал), detail2DynamicView, true); + + args.DataService.LoadObject(classDynamicView, базовыйКласс); + + Assert.Equal(newValue, базовыйКласс.Адрес = newValue); + Assert.Equal(newValue, базовыйКласс.Книга[0].Название); + Assert.Equal(newIntValue, базовыйКласс.Журнал[0].Номер); + + } + }); + } + + /// + /// Осуществляет проверку удаления данных. + /// + [Fact] + public void DeletePlainObjectTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + Медведь agregator = new Медведь() { МедведьСтрокой = "Agregator" }; + args.DataService.UpdateObject(agregator); + + View view = new View(typeof(Медведь), View.ReadType.OnlyThatObject); + Медведь foundAgregator0 = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + Медведь foundAgregator = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но при этом пустые. + /// + [Fact] + public void DeleteObjectWithSameDetailAndMasterEmptyTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; + args.DataService.UpdateObject(agregator); + + View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); + AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Осуществляет проверку удаления данных, если детейл и мастер одного типа? но лишь детейл заполнен. + /// + [Fact] + public void DeleteObjectWithSameDetailAndMasterTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; + args.DataService.UpdateObject(agregator); + + DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; + agregator.Details.Add(dm); + args.DataService.UpdateObject(dm); + + AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; + args.DataService.UpdateObject(agregator2); + DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; + agregator2.Details.Add(dm2); + args.DataService.UpdateObject(dm2); + + View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); + AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но не пустые. + /// + [Fact] + public void DeleteObjectWithSameDetailAndMaster2Test() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; + args.DataService.UpdateObject(agregator); + + DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; + agregator.Details.Add(dm); + args.DataService.UpdateObject(dm); + + AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; + args.DataService.UpdateObject(agregator2); + DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; + agregator2.Details.Add(dm2); + args.DataService.UpdateObject(dm2); + + agregator.Master = dm2; + args.DataService.UpdateObject(agregator); + + View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); + AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Тест на изменение ссылки. + /// + [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 = ODataTestHelper.AddEntryRelationship(requestJsonDataЛес, лесDynamicView, args.Token.Model, страна2, nameof(Лес.Страна)); + + // Получаем адрес для PATCH запроса. + var requestUrl = ODataTestHelper.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 = ODataTestHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.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, Date = DateTime.Now }; + 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 = ODataTestHelper.AddEntryRelationship(requestJsonData, patentDynamicView, args.Token.Model, device2, nameof(LegoPatent.BaseLegoBlock)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.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 7b58aff7..3bbb0984 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,1333 +1,1333 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ 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 c379db99..f16467de 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. /// From 5d79d736235e0d378d6d6d9357263b9dcf3d9ee1 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Tue, 23 Jan 2024 11:35:42 +0500 Subject: [PATCH 03/18] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20Assert=20=D0=B2=20UpdateViewsTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CRUD/Update/UpdateViewsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs index 2979cc0d..2172ff08 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs @@ -69,7 +69,7 @@ public void UpdateViewNoMastersTest() var requestUrl = ODataHelper.GetRequestUrl(args.Token.Model, берлога1); // Сейчас обновление мастеров не поддерживается. - Assert.Throws(() => args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result); // Если падает Exception, значит представление поменялось и работает. + Assert.ThrowsAsync(() => args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData)); // Если падает Exception, значит представление поменялось и работает. }); } } From 883e232846f5ece30a12e21f8345e36dac7d3bc0 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Thu, 25 Jan 2024 16:40:31 +0500 Subject: [PATCH 04/18] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D1=83=D0=BA=D0=B0=D0=B7=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20Startup=20=D0=B2=20=D1=82=D0=B5=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BaseIntegratedTest.cs | 8 ++- .../BaseODataServiceIntegratedTest.cs | 8 ++- .../CRUD/Create/BusinessServersTest.cs | 7 ++- .../CRUD/Create/ChangeMasterInBSTest.cs | 7 ++- .../CreateWithPseudoDetailDefinedTest.cs | 7 ++- .../CRUD/GisCRUDTest.cs | 7 ++- .../CRUD/MultiThreadTests.cs | 9 ++- .../CRUD/Read/BuiltinQueryFunctionsTest.cs | 7 ++- .../CRUD/Read/Excel/ExcelExportTest.cs | 7 ++- .../Read/FilterByMasterDetailFieldTest.cs | 55 ++++++++++--------- .../CRUD/Read/FilterByMasterFieldTest.cs | 9 ++- .../FilterByMasterMasterDetailFieldTest.cs | 7 ++- .../Read/FilterByPseudoDetailFieldTest.cs | 7 ++- .../CRUD/Read/FilterTest.cs | 7 ++- .../CRUD/Read/GetTest.cs | 17 ++++-- .../CRUD/Read/MetaDataTest.cs | 7 ++- .../CRUD/Read/ReferenceToMasterTest.cs | 7 ++- .../CRUD/Read/SkipTopOrderByTest.cs | 7 ++- .../CRUD/Read/UtfRequestsTest.cs | 9 ++- .../CRUD/Update/BusinessServersTest.cs | 7 ++- .../CRUD/Update/ModifyDataTest.cs | 29 ++++++---- .../CRUD/Update/WebFileTest.cs | 9 ++- .../CustomWebApplicationFactory.cs | 2 +- .../Events/AfterGetTest.cs | 7 ++- .../Events/AfterInternalServerErrorTest.cs | 11 +++- .../Events/AfterSaveTest.cs | 7 ++- .../Events/BeforeGetTest.cs | 33 ++++++----- .../Events/BeforeSaveTest.cs | 7 ++- .../Files/FileControllerTest.cs | 7 ++- .../Functions/ActionsTest.cs | 7 ++- .../Functions/DelegateFunctionsTest.cs | 7 ++- .../Functions/FunctionsTest.cs | 7 ++- .../Model/CustomizationEdmModelNames.cs | 7 ++- .../DefaultOfflineManagerIntegratedTest.cs | 7 ++- .../OfflineAuditServiceIntegratedTest.cs | 7 ++- 35 files changed, 264 insertions(+), 96 deletions(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs index 22cac7b0..61d3c46b 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs @@ -30,9 +30,11 @@ public abstract class BaseIntegratedTest : IDisposable /// /// Base class for integration tests. /// - public abstract class BaseIntegratedTest : IClassFixture>, IDisposable + /// Startup class used for booting the application. + public abstract class BaseIntegratedTest : IClassFixture>, IDisposable + where TStartup : class { - protected readonly WebApplicationFactory _factory; + protected readonly WebApplicationFactory _factory; #endif protected ITestOutputHelper _output; @@ -139,7 +141,7 @@ protected BaseIntegratedTest(string tempDbNamePrefix, bool useGisDataService = f /// Unit tests debug output. /// Prefix for temp database name. /// Use DataService with Gis support. - protected BaseIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output, string tempDbNamePrefix, bool useGisDataService = false) + protected BaseIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output, string tempDbNamePrefix, bool useGisDataService = false) { _factory = factory; _output = output; diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs index e904d023..eee8605f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs @@ -35,7 +35,13 @@ /// /// Базовый класс для тестирования работы с данными через ODataService. /// +#if NETFRAMEWORK public class BaseODataServiceIntegratedTest : BaseIntegratedTest +#endif +#if NETCOREAPP + public class BaseODataServiceIntegratedTest : BaseIntegratedTest + where TStartup : class +#endif { protected IDataObjectEdmModelBuilder _builder; @@ -72,7 +78,7 @@ public BaseODataServiceIntegratedTest( } #endif #if NETCOREAPP - public BaseODataServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output = null, bool useNamespaceInEntitySetName = false, bool useGisDataService = false, PseudoDetailDefinitions pseudoDetailDefinitions = null) + public BaseODataServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output = null, bool useNamespaceInEntitySetName = false, bool useGisDataService = false, PseudoDetailDefinitions pseudoDetailDefinitions = null) : base(factory, output, "ODataDB", useGisDataService) { Init(useNamespaceInEntitySetName, pseudoDetailDefinitions); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs index 6f1bfb16..27b8c4c4 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs @@ -13,7 +13,12 @@ /// /// Класс тестов для тестирования бизнес-серверов. /// +#if NETFRAMEWORK public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class BusinessServersTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs index a340a454..ccae2f74 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs @@ -11,7 +11,12 @@ /// /// Класс тестов для тестирования изменения мастера при создании детейла. /// +#if NETFRAMEWORK public class ChangeMasterInBSTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ChangeMasterInBSTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -19,7 +24,7 @@ public class ChangeMasterInBSTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ChangeMasterInBSTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ChangeMasterInBSTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs index f1aedc2b..7a364423 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs @@ -11,7 +11,12 @@ /// /// Unit-test class for creation entity instance with pseudodetail field defined through OData service. /// +#if NETFRAMEWORK public class CreateWithPseudoDetailDefinedTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class CreateWithPseudoDetailDefinedTest : BaseODataServiceIntegratedTest +#endif { private static PseudoDetailDefinitions GetPseudoDetailDefinitions() { @@ -31,7 +36,7 @@ public CreateWithPseudoDetailDefinedTest() : base(pseudoDetailDefinitions: GetPs } #endif #if NETCOREAPP - public CreateWithPseudoDetailDefinedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output, pseudoDetailDefinitions: GetPseudoDetailDefinitions()) + public CreateWithPseudoDetailDefinedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output, pseudoDetailDefinitions: GetPseudoDetailDefinitions()) { } #endif diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs index 104a1681..cd2d9985 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs @@ -21,7 +21,12 @@ /// /// Класс тестов для тестирования работы с гео-данными. /// +#if NETFRAMEWORK public class GisCRUDTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class GisCRUDTest : BaseODataServiceIntegratedTest +#endif { #if NETFRAMEWORK /// @@ -37,7 +42,7 @@ public GisCRUDTest() /// /// Фабрика для приложения. /// Вывод диагностической информации по тестам. - public GisCRUDTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + public GisCRUDTest(CustomWebApplicationFactory factory, ITestOutputHelper output) : base(factory, output, false, true) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs index 238df9a2..6bcbfb42 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs @@ -33,7 +33,12 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD /// /// Тесты CRUD операций с множеством пользователей. /// +#if NETFRAMEWORK public class MultiThreadTests : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class MultiThreadTests : BaseODataServiceIntegratedTest +#endif { private const int ThreadCount = 50; @@ -54,7 +59,7 @@ public MultiThreadTests(Xunit.Abstractions.ITestOutputHelper output) /// /// Фабрика для приложения. /// Вывод отладочной информации. - public MultiThreadTests(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public MultiThreadTests(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -337,7 +342,7 @@ private static void RegisterCustomUser(IUnityContainer container) container.RegisterType(); #if NETCOREAPP container.RegisterType(); - #endif +#endif container.RegisterType(); } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs index 70df3068..0922578b 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs @@ -20,7 +20,12 @@ /// /// Класс тестов для тестирования применения $filter в OData-сервисе. /// +#if NETFRAMEWORK public class BuiltinQueryFunctionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BuiltinQueryFunctionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -28,7 +33,7 @@ public class BuiltinQueryFunctionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BuiltinQueryFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BuiltinQueryFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs index 9eabb8aa..1ab78bf1 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs @@ -13,7 +13,12 @@ /// /// A class for testing exports from Excel. /// +#if NETFRAMEWORK public class ExcelExportTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ExcelExportTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class ExcelExportTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ExcelExportTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ExcelExportTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs index 478014a6..4df72efb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs @@ -16,7 +16,12 @@ /// /// Unit-test class for filtering data through OData service by master details fields. /// +#if NETFRAMEWORK public class FilterByMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterByMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -24,7 +29,7 @@ public class FilterByMasterDetailFieldTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterByMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -145,42 +150,42 @@ public void TestFilterByDetailMaster() Медведь медведь3 = new Медведь() { ПорядковыйНомер = 3 }; Лес лес1 = new Лес() { Название = "Шишкин" }; - Лес лес2 = new Лес() { Название = "Ёжкин" }; + Лес лес2 = new Лес() { Название = "Ёжкин" }; Лес лес3 = new Лес() { Название = "Пыжкин" }; Берлога берлога1 = new Берлога() { Наименование = "Берлога 1", ЛесРасположения = лес1, Заброшена = true }; Берлога берлога2 = new Берлога() { Наименование = "Берлога 2", ЛесРасположения = лес1, Заброшена = false }; Берлога берлога3 = new Берлога() { Наименование = "Берлога 3", ЛесРасположения = лес2, Заброшена = false }; - Берлога берлога4 = new Берлога() { Наименование = "Берлога 4", ЛесРасположения = лес2, Заброшена = false }; + Берлога берлога4 = new Берлога() { Наименование = "Берлога 4", ЛесРасположения = лес2, Заброшена = false }; Берлога берлога5 = new Берлога() { Наименование = "Берлога 5", ЛесРасположения = лес3, Заброшена = false }; Берлога берлога6 = new Берлога() { Наименование = "Берлога 6", ЛесРасположения = лес3, Заброшена = true}; медведь1.Берлога.AddRange(берлога1, берлога2); - медведь2.Берлога.AddRange(берлога3, берлога4); + медведь2.Берлога.AddRange(берлога3, берлога4); медведь3.Берлога.AddRange(берлога5, берлога6); DataObject[] newDataObjects = new DataObject[] { лес1, лес2, лес3, медведь1, медведь2, берлога1, берлога2, берлога3, берлога4, берлога5, берлога6 }; args.DataService.UpdateObjects(ref newDataObjects); - ExternalLangDef.LanguageDef.DataService = args.DataService; - - // Act. + ExternalLangDef.LanguageDef.DataService = args.DataService; + + // Act. string requestUrl = string.Format( "http://localhost/odata/{0}?$filter={1}", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, "(Медведь/Берлога/any(f:(f/Заброшена eq true)))"); using (var response = args.HttpClient.GetAsync(requestUrl).Result) - { - // Assert. + { + // Assert. string receivedStr = response.Content.ReadAsStringAsync().Result.Beautify(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedStr); Assert.Equal(4, ((JArray)receivedDict["value"]).Count); } }); - } - + } + /// /// Tests filtering data by detail enum field with complex predicate. /// @@ -188,39 +193,39 @@ public void TestFilterByDetailMaster() public void TestFilterByEnumDetailMaster() { ActODataService(args => - { - // Arrange. - Driver driver1 = new Driver { CarCount = 2, Documents = true, Name = "Driver1" }; - Driver driver2 = new Driver { CarCount = 2, Documents = true, Name = "Driver2" }; - Driver driver3 = new Driver { CarCount = 2, Documents = true, Name = "Driver3" }; + { + // Arrange. + Driver driver1 = new Driver { CarCount = 2, Documents = true, Name = "Driver1" }; + Driver driver2 = new Driver { CarCount = 2, Documents = true, Name = "Driver2" }; + Driver driver3 = new Driver { CarCount = 2, Documents = true, Name = "Driver3" }; Car car1d1 = new Car { Model = "ВАЗ", TipCar = tTip.sedan }; Car car2d1 = new Car { Model = "ГАЗ", TipCar = tTip.sedan }; Car car1d2 = new Car { Model = "BMW", TipCar = tTip.crossover }; Car car2d2 = new Car { Model = "Porsche", TipCar = tTip.sedan }; - + Car car1d3 = new Car { Model = "Lamborghini", TipCar = tTip.crossover }; - Car car2d3 = new Car { Model = "Subaru", TipCar = tTip.sedan }; - + Car car2d3 = new Car { Model = "Subaru", TipCar = tTip.sedan }; + driver1.Car.AddRange(car1d1, car2d1); - driver2.Car.AddRange(car1d2, car2d2); + driver2.Car.AddRange(car1d2, car2d2); driver3.Car.AddRange(car1d3, car2d3); DataObject[] newDataObjects = new DataObject[] { driver1, driver2, driver3, car1d1, car2d1, car1d2, car2d2, car1d3, car2d3 }; args.DataService.UpdateObjects(ref newDataObjects); - ExternalLangDef.LanguageDef.DataService = args.DataService; - - // Act. + ExternalLangDef.LanguageDef.DataService = args.DataService; + + // Act. string requestUrl = string.Format( "http://localhost/odata/{0}?$filter={1}", args.Token.Model.GetEdmEntitySet(typeof(Car)).Name, "(Driver/Car/any(f:(f/TipCar eq NewPlatform.Flexberry.ORM.ODataService.Tests.tTip'crossover')))"); using (var response = args.HttpClient.GetAsync(requestUrl).Result) - { - // Assert. + { + // Assert. string receivedStr = response.Content.ReadAsStringAsync().Result.Beautify(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedStr); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs index 283adb51..d6ae5e9b 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs @@ -15,15 +15,20 @@ /// /// Unit-test class for filtering data through OData service by master fields. /// +#if NETFRAMEWORK public class FilterByMasterFieldTest : BaseODataServiceIntegratedTest - { +#endif +#if NETCOREAPP + public class FilterByMasterFieldTest : BaseODataServiceIntegratedTest +#endif + { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterByMasterFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByMasterFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs index f8298cab..8d983e31 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs @@ -16,7 +16,12 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Read /// /// Unit-test class for filtering data through OData service by master master details fields. /// +#if NETFRAMEWORK public class FilterByMasterMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterByMasterMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -24,7 +29,7 @@ public class FilterByMasterMasterDetailFieldTest : BaseODataServiceIntegratedTes /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterByMasterMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByMasterMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs index c9474cff..aa9a6fb4 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs @@ -16,7 +16,12 @@ /// /// Unit-test class for filtering data through OData service by pseudodetail field. /// +#if NETFRAMEWORK public class FilterByPseudoDetailFieldTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterByPseudoDetailFieldTest : BaseODataServiceIntegratedTest +#endif { private static PseudoDetailDefinitions GetPseudoDetailDefinitions() { @@ -36,7 +41,7 @@ public FilterByPseudoDetailFieldTest() : base(pseudoDetailDefinitions: GetPseudo } #endif #if NETCOREAPP - public FilterByPseudoDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByPseudoDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output, pseudoDetailDefinitions: GetPseudoDetailDefinitions()) { IUnityContainer container = UnityFactory.GetContainer(); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs index d6cdbb27..affba784 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs @@ -19,7 +19,12 @@ /// /// Класс тестов для тестирования применения $filter в OData-сервисе. /// +#if NETFRAMEWORK public class FilterTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -27,7 +32,7 @@ public class FilterTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs index 019014ad..8b69831f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs @@ -19,7 +19,12 @@ /// /// Класс тестов для проверки корректной обработки Get-запросов. /// +#if NETFRAMEWORK public class GetTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class GetTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -27,15 +32,15 @@ public class GetTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public GetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public GetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } #endif - /// - /// Проверка получения данных для классов, в которых есть нехранимые поля, который не содержат setter'ов. - /// (Такие варианты присутствуют в старом коде). + /// + /// Проверка получения данных для классов, в которых есть нехранимые поля, который не содержат setter'ов. + /// (Такие варианты присутствуют в старом коде). /// [Fact] public void TestGetNotStored() @@ -67,8 +72,8 @@ public void TestGetNotStored() }); } - /// - /// Проверка значение в атрибуте @odata.type. + /// + /// Проверка значение в атрибуте @odata.type. /// [Fact] public void TestGetWithMaster() diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs index e90fbf41..e3c9c00d 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs @@ -15,7 +15,12 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class MetaDataTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class MetaDataTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -23,7 +28,7 @@ public class MetaDataTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public MetaDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public MetaDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs index ecbed583..14963a4f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs @@ -18,7 +18,12 @@ /// /// Unit-test class for read data with reference to master through OData service. /// +#if NETFRAMEWORK public class ReferenceToMasterTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ReferenceToMasterTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -26,7 +31,7 @@ public class ReferenceToMasterTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ReferenceToMasterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ReferenceToMasterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs index bc7f74ac..cc3c4ac9 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs @@ -20,7 +20,12 @@ /// /// Класс тестов для тестирования $skip, $top, $orderby. /// +#if NETFRAMEWORK public class SkipTopOrderByTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class SkipTopOrderByTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -28,7 +33,7 @@ public class SkipTopOrderByTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public SkipTopOrderByTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + public SkipTopOrderByTest(CustomWebApplicationFactory factory, ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs index 931f1a75..adacb75c 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs @@ -18,15 +18,20 @@ /// /// Unit-test class for read data through OData service with using UTF8 requests. /// +#if NETFRAMEWORK public class UtfRequestsTest : BaseODataServiceIntegratedTest - { +#endif +#if NETCOREAPP + public class UtfRequestsTest : BaseODataServiceIntegratedTest +#endif + { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// /// Фабрика для приложения. /// Вывод отладочной информации. - public UtfRequestsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public UtfRequestsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs index c10ae918..847d48e0 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs @@ -19,7 +19,12 @@ /// /// Класс тестов для тестирования бизнес-серверов. /// +#if NETFRAMEWORK public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -27,7 +32,7 @@ public class BusinessServersTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } 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 82210a42..9b65e4fb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs @@ -21,7 +21,12 @@ /// /// Класс тестов для тестирования операций модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class ModifyDataTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ModifyDataTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -29,23 +34,23 @@ public class ModifyDataTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } #endif - /// - /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. - /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . - /// Тест проверяет следующие факты: - /// - /// Вставка связи мастерового объекта. - /// Удаление связи мастеровго объекта путём присвоения null свойству. - /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. - /// - /// - [Fact] + /// + /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. + /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . + /// Тест проверяет следующие факты: + /// + /// Вставка связи мастерового объекта. + /// Удаление связи мастеровго объекта путём присвоения null свойству. + /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. + /// + /// + [Fact] public void PatchNavigationPropertiesTest() { ActODataService(args => diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs index 708d2bb5..672fe9ac 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs @@ -13,7 +13,12 @@ using Unity; using Xunit; - public class WebFileTest: BaseODataServiceIntegratedTest +#if NETFRAMEWORK + public class WebFileTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class WebFileTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class WebFileTest: BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public WebFileTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public WebFileTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs index 926a4c23..98e815cc 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs @@ -22,7 +22,7 @@ protected override IWebHostBuilder CreateWebHostBuilder() var webHostBuilder = new WebHostBuilder() .UseUnityServiceProvider(container) .UseContentRoot(contentRootDirectory) - .UseStartup(); + .UseStartup(); return webHostBuilder; } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs index 4e108989..1cd43dcb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs @@ -11,7 +11,12 @@ /// Класс тестов для тестирования логики после операции считывания данных OData-сервисом. /// +#if NETFRAMEWORK public class AfterGetTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class AfterGetTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -19,7 +24,7 @@ public class AfterGetTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public AfterGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public AfterGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs index 428eccb4..aa771b4a 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs @@ -12,7 +12,12 @@ /// /// Класс тестов для тестирования логики после возникновения исключения. /// +#if NETFRAMEWORK public class AfterInternalServerErrorTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class AfterInternalServerErrorTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -20,7 +25,7 @@ public class AfterInternalServerErrorTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public AfterInternalServerErrorTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public AfterInternalServerErrorTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -48,13 +53,13 @@ public Exception AfterInternalServerError(Exception e, ref HttpStatusCode code) public void TestAfterInternalServerError() { ActODataService(args => - { + { #if NETFRAMEWORK args.Token.Events.CallbackAfterInternalServerError = AfterInternalServerError; #elif NETCOREAPP CustomExceptionFilter.CallbackAfterInternalServerError = AfterInternalServerError; #endif - + Медведь медв = new Медведь { Вес = 48, Пол = tПол.Мужской }; Медведь медв2 = new Медведь { Вес = 148, Пол = tПол.Мужской }; Лес лес = new Лес { Название = "Бор" }; diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs index 52d93515..3779edd7 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs @@ -23,7 +23,12 @@ /// /// Класс тестов для тестирования логики после операций модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class AfterSaveTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class AfterSaveTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -31,7 +36,7 @@ public class AfterSaveTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public AfterSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public AfterSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs index 5644de97..ab3ef94a 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs @@ -1,14 +1,14 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.Events { - using System; - using System.Collections.Generic; + using System; + using System.Collections.Generic; using System.Net; using System.Net.Http; using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; - using Newtonsoft.Json; + using ICSSoft.STORMNET.Business; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -16,7 +16,12 @@ /// /// Класс тестов для тестирования логики после операций модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class BeforeGetTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BeforeGetTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -24,7 +29,7 @@ public class BeforeGetTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BeforeGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BeforeGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -43,14 +48,14 @@ public bool BeforeGet(ref LoadingCustomizationStruct lcs) return true; } - /// - /// Блокирует получение объектов. - /// - /// LCS. - /// false - public bool FalseBeforeGet(ref LoadingCustomizationStruct lcs) - { - return false; + /// + /// Блокирует получение объектов. + /// + /// LCS. + /// false + public bool FalseBeforeGet(ref LoadingCustomizationStruct lcs) + { + return false; } /// diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs index 153301b1..0e17c3f2 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs @@ -17,7 +17,12 @@ /// /// Класс тестов для тестирования логики перед операциями модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class BeforeSaveTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BeforeSaveTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -25,7 +30,7 @@ public class BeforeSaveTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BeforeSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BeforeSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs index 9ba02a91..3c2f67bb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs @@ -26,7 +26,12 @@ /// /// Тесты файлового контроллера , отвечающего за загрузку файлов на сервер и их скачивание. /// +#if NETFRAMEWORK public class FileControllerTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FileControllerTest : BaseODataServiceIntegratedTest +#endif { private const string FileBaseUrl = "http://localhost/api/File"; @@ -61,7 +66,7 @@ public class FileControllerTest : BaseODataServiceIntegratedTest #if NETFRAMEWORK public FileControllerTest() #elif NETCOREAPP - public FileControllerTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FileControllerTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) #endif { diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs index f5431b2a..8bba6c0e 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs @@ -29,7 +29,12 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class ActionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ActionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -37,7 +42,7 @@ public class ActionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ActionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ActionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs index f18e1521..fc083751 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs @@ -13,7 +13,12 @@ /// /// Unit test class for OData Service user-defined functions /// +#if NETFRAMEWORK public class DelegateFunctionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class DelegateFunctionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class DelegateFunctionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public DelegateFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public DelegateFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs index a43b8ef3..ac38d289 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs @@ -28,7 +28,12 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class FunctionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FunctionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -36,7 +41,7 @@ public class FunctionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs index 0b8b0703..00c8b141 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs @@ -15,14 +15,19 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class CustomizationEdmModelNames : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class CustomizationEdmModelNames : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// /// Фабрика для приложения. - public CustomizationEdmModelNames(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public CustomizationEdmModelNames(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs index 53e435ae..b1bf2388 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs @@ -15,13 +15,18 @@ using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; using NewPlatform.Flexberry.Services; +#if NETFRAMEWORK public class DefaultOfflineManagerIntegratedTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class DefaultOfflineManagerIntegratedTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// - public DefaultOfflineManagerIntegratedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public DefaultOfflineManagerIntegratedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } #endif diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs index 96e4b461..042cf1ae 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs @@ -13,7 +13,12 @@ /// ORM-integrated unit test for . /// /// +#if NETFRAMEWORK public class OfflineAuditServiceIntegratedTest : BaseIntegratedTest +#endif +#if NETCOREAPP + public class OfflineAuditServiceIntegratedTest : BaseIntegratedTest +#endif { #if NETFRAMEWORK /// @@ -28,7 +33,7 @@ public OfflineAuditServiceIntegratedTest() /// /// Initializes a new instance of the class. /// - public OfflineAuditServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + public OfflineAuditServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output) : base(factory, output, "offline") { } From fa03e022647e05030cf703050573937946fc0dc9 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Thu, 25 Jan 2024 17:29:47 +0500 Subject: [PATCH 05/18] =?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=D1=82=D0=B5=D1=81=D1=82=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20MS=20SQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CRUD/Update/ModifyDataTest.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 9b65e4fb..1fd0c9f3 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs @@ -1,4 +1,4 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update { using System; using System.Collections.Generic; @@ -40,17 +40,17 @@ public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Ab } #endif - /// - /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. - /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . - /// Тест проверяет следующие факты: - /// - /// Вставка связи мастерового объекта. - /// Удаление связи мастеровго объекта путём присвоения null свойству. - /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. - /// - /// - [Fact] + /// + /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. + /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . + /// Тест проверяет следующие факты: + /// + /// Вставка связи мастерового объекта. + /// Удаление связи мастеровго объекта путём присвоения null свойству. + /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. + /// + /// + [Fact] public void PatchNavigationPropertiesTest() { ActODataService(args => From 2957a3e38a989f569cee11e0ce9595a13c8bfbdc Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Thu, 25 Jan 2024 18:00:37 +0500 Subject: [PATCH 06/18] =?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=B8=D1=8F=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataObjectController.ModifyData.cs | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index fd604103..4c70a39a 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -266,35 +266,35 @@ private NoContentResult DeleteEntity(object key) Init(); /* В ситуации, когда мастер и детейл одного типа, ORM без подгрузки копии данных не может корректно разобрать порядок, - * в котором объекты должны быть удалены. - */ + * в котором объекты должны быть удалены. + */ bool needDataCopyLoad = _typesWithSameDetailAndMaster.Contains(type); if (!_typesWithNotSameDetailAndMaster.Contains(type) && !needDataCopyLoad) { - string[] props = Information.GetAllPropertyNames(type); - int length = props.Length; - int index = 0; - - while(!needDataCopyLoad && index < length) - { - string prop = props[index]; - Type propType = Information.GetPropertyType(type, prop); - needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; - - index++; - } + string[] props = Information.GetAllPropertyNames(type); + int length = props.Length; + int index = 0; + + while (!needDataCopyLoad && index < length) + { + string prop = props[index]; + Type propType = Information.GetPropertyType(type, prop); + needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; + + index++; + } } DataObject obj = null; if (!needDataCopyLoad) { - obj = DataObjectCache.CreateDataObject(type, key); - _typesWithNotSameDetailAndMaster.Add(type); + obj = DataObjectCache.CreateDataObject(type, key); + _typesWithNotSameDetailAndMaster.Add(type); } else { - obj = LoadObject(type, key.ToString()); - _typesWithSameDetailAndMaster.Add(type); + obj = LoadObject(type, key.ToString()); + _typesWithSameDetailAndMaster.Add(type); } // Удаляем объект с заданным ключем. @@ -764,12 +764,12 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) { // Для обратной совместимости сравним перечень загруженных свойств и свойств в представлении. /* Данный код срабатывает, например, если в кэше был объект, который загрузился только на уровне первичного ключа. - * - * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В. - * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В. - * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded. - * Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены). - */ + * + * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В. + * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В. + * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded. + * Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены). + */ string[] loadedProps = dataObjectFromCache.GetLoadedProperties(); IEnumerable ownProps = view.Properties.Where(p => !p.Name.Contains('.')); if (!ownProps.All(p => loadedProps.Contains(p.Name))) @@ -835,7 +835,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) /// Список на обновление. /// Объект данных который добавляем. /// Добавлять в конец списка. - private static void AddDataObject(List objsToUpdate, DataObject dataObject, bool insertToEnd) + private static void AddObjectToUpdate(List objsToUpdate, DataObject dataObject, bool insertToEnd) { bool objAlreadyExists = objsToUpdate.Any(o => PKHelper.EQDataObject(o, dataObject, false)); if (!objAlreadyExists) @@ -887,16 +887,19 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. Type objType = _model.GetDataObjectType(edmEntity); - View view = _model.GetDataObjectDefaultView(objType); + View view = null; if (useUpdateView) { - view = _model.GetDataObjectUpdateView(objType) ?? view; + view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); + } else + { + view = _model.GetDataObjectDefaultView(objType); } DataObject obj = ReturnDataObject(objType, value, view); // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. - AddDataObject(dObjs, obj, endObject); + AddObjectToUpdate(dObjs, obj, endObject); // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). string agregatorPropertyName = Information.GetAgregatePropertyName(objType); @@ -1072,7 +1075,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke if (agregator != null) { - AddDataObject(dObjs, agregator, endObject); + AddObjectToUpdate(dObjs, agregator, endObject); } } From ba83fe5f2ce34b50b7ebea0cdc739b95c51550b9 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Thu, 25 Jan 2024 18:46:10 +0500 Subject: [PATCH 07/18] =?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=B8=D1=8F=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B9=202?= 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 b6e3bd18..de346ef2 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -224,7 +224,7 @@ private void SetUpdateView(Type dataObjectType, View updateView) return; } - if (dataObjectType != updateView.DefineClassType) + if (!Information.CheckViewForClasses(updateView.Name, dataObjectType)) { throw new ArgumentException($"View from DataObject {updateView.DefineClassType} can not be set for a DataObject of type {dataObjectType}.", nameof(updateView)); } From 62086b0f59dc600ed7f181e099827dcc5b3632a3 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Mon, 29 Jan 2024 14:37:37 +0500 Subject: [PATCH 08/18] =?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=B8=D1=8F=20=D0=BF=D0=BE=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataObjectController.ModifyData.cs | 2 +- .../Model/DefaultDataObjectEdmModelBuilder.cs | 2 +- .../CRUD/Update/UpdateViewsTest.cs | 9 ++++--- .../DotNetCoreTests.cs | 6 ++--- .../{ODataHelper.cs => ODataTestHelper.cs} | 26 +++++++++---------- .../Startups/UpdateViewsTestStartup.cs | 3 ++- 6 files changed, 24 insertions(+), 24 deletions(-) rename Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/{ODataHelper.cs => ODataTestHelper.cs} (74%) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 4c70a39a..a3e0e503 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -833,7 +833,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) /// Добавляет объект данных в список на обновление если его там ещё нет. /// /// Список на обновление. - /// Объект данных который добавляем. + /// Объект данных, который добавляем. /// Добавлять в конец списка. private static void AddObjectToUpdate(List objsToUpdate, DataObject dataObject, bool insertToEnd) { diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs index de346ef2..4eef8eac 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -213,7 +213,7 @@ private void SetUpdateView(IEnumerable> updateViews) /// 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)) + if (dataObjectType.IsSubclassOf(typeof(DataObject))) { throw new ArgumentException("Update view can be set only for a DataObject.", nameof(dataObjectType)); } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs index 2172ff08..17036da3 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs @@ -13,7 +13,8 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update using Xunit.Abstractions; /// - /// Тесты для проверки работы UpdateViews. + /// Тесты для проверки работы UpdateViews. Для запуска OData backend используется модифицированная версия Startup - , + /// которая задаёт UpdateView для Берлоги и Медведя. /// public class UpdateViewsTest : BaseODataServiceIntegratedTest { @@ -29,7 +30,7 @@ public UpdateViewsTest(CustomWebApplicationFactory facto } /// - /// Проверка работы UpdateView - случай когда в UpdateView не включен мастер. + /// Проверка работы UpdateView - случай, когда в UpdateView не включен мастер. /// [Fact] public void UpdateViewNoMastersTest() @@ -63,10 +64,10 @@ public void UpdateViewNoMastersTest() string requestJsonData = берлога1.ToJson(берлогаDynamicView, args.Token.Model); // Добавляем в payload информацию, что поменяли ссылку на медведя. - requestJsonData = ODataHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); + requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - var requestUrl = ODataHelper.GetRequestUrl(args.Token.Model, берлога1); + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, берлога1); // Сейчас обновление мастеров не поддерживается. Assert.ThrowsAsync(() => args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData)); // Если падает Exception, значит представление поменялось и работает. diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs index e7bb4b35..882334bf 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs @@ -13,11 +13,11 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests /// /// Тесты, специфичные для .NET Core. /// - public class DotNetCoreTests : IClassFixture> + public class DotNetCoreTests : IClassFixture> { - private readonly WebApplicationFactory _factory; + private readonly WebApplicationFactory _factory; - public DotNetCoreTests(CustomWebApplicationFactory factory) + public DotNetCoreTests(CustomWebApplicationFactory factory) { _factory = factory; } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs similarity index 74% rename from Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs rename to Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs index ea2100be..4f8bf49c 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataHelper.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs @@ -1,25 +1,23 @@ -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 +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers { - public class ODataHelper + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.KeyGen; + using NewPlatform.Flexberry.ORM.ODataService.Model; + + /// + /// Вспомогательные утилиты для тестирования OData. + /// + public static class ODataTestHelper { /// - /// Добавить запись об изменении ссылки в OData Payload. + /// Добавить запись об изменении ссылки у объекта в тело запроса к OData. /// - /// Исходный payload. + /// Исходное тело запроса. /// Представление исходного объекта. /// EDM модель. /// Новый объект данных (по ссылке). /// Ссылка на новый объект данных. - /// Новый OData Payload. + /// Новое тело запроса к OData. public static string AddEntryRelationship(string requestJsonData, View view, DataObjectEdmModel model, DataObject dataObject, string relationName) { DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonData, view, model); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs index d65fbf94..68239ad9 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs @@ -20,6 +20,7 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests /// /// Startup for testing UpdateView configuration. + /// Differs from TestStartup that it sets UpdateView for and data objects. /// public class UpdateViewsTestStartup : Startup { @@ -59,7 +60,7 @@ public override void Configure(IApplicationBuilder app, IHostingEnvironment env) }; PseudoDetailDefinitions pseudoDetailDefinitions = (PseudoDetailDefinitions)container.Resolve(typeof(PseudoDetailDefinitions)); - var updateViews = new Dictionary() + var updateViews = new Dictionary() // setting updateViews for testing { { typeof(Медведь), Медведь.Views.МедведьUpdateView }, { typeof(Берлога), Берлога.Views.БерлогаUpdateView }, From 504989b1c9eb8b7bcff0b51b198c9fb2db91850b Mon Sep 17 00:00:00 2001 From: inaidanov Date: Mon, 29 Jan 2024 15:19:05 +0500 Subject: [PATCH 09/18] =?UTF-8?q?fix=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B8=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=B0=20=D0=B2=20UpdateVie?= =?UTF-8?q?w?= 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 4eef8eac..3e6ed225 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -213,7 +213,7 @@ private void SetUpdateView(IEnumerable> updateViews) /// Update view to be used for objects of type . Setting removes update view for the type. private void SetUpdateView(Type dataObjectType, View updateView) { - if (dataObjectType.IsSubclassOf(typeof(DataObject))) + if (!dataObjectType.IsSubclassOf(typeof(DataObject))) { throw new ArgumentException("Update view can be set only for a DataObject.", nameof(dataObjectType)); } From 2bc0c002b0081377718e65ff60c61fab9a2a884f Mon Sep 17 00:00:00 2001 From: inaidanov Date: Mon, 29 Jan 2024 16:33:13 +0500 Subject: [PATCH 10/18] =?UTF-8?q?changelog=20=D0=B8=20nuspec=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=20-=207.2.0-alpha02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +- NewPlatform.Flexberry.ORM.ODataService.nuspec | 226 +++++++++--------- 2 files changed, 119 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 477c9832..5323ee85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). ### Changed 1. Updated Flexberry ORM up to 7.2.0-beta01. @@ -106,7 +107,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). 3. Support for limits on master details. 4. Support for limits on pseudodetails. 5. Decode Excel export column name. -6. HttpConfiguretion MapDataObjectRoute() extension method. +6. HttpConfiguretion MapDataObjectRoute() extension method. ### Changed @@ -160,13 +161,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). 3. Add support actions. 4. Add handler, called after exception appears. 5. In user functions and actions add possibility to return collections of primitive types and enums. In actions add possibility to use primitive types and enums as parameters. - + ### Fixed 1. Fix reading properties of files. 2. Fix error which occured in Mono in method `DefaultODataPathHandler.Parse(IEdmModel model, string serviceRoot, string odataPath)`. 3. Fix errors in work of user functions. 4. Fix error in association object enumeration filtration. - + ### Changed 1. Update dependencies. 2. Update ODataService package version to according ORM package version. diff --git a/NewPlatform.Flexberry.ORM.ODataService.nuspec b/NewPlatform.Flexberry.ORM.ODataService.nuspec index 5afc0ad2..3fcf28b3 100644 --- a/NewPlatform.Flexberry.ORM.ODataService.nuspec +++ b/NewPlatform.Flexberry.ORM.ODataService.nuspec @@ -1,111 +1,115 @@ - - - - NewPlatform.Flexberry.ORM.ODataService - 7.2.0-beta02 - Flexberry ORM ODataService - New Platform Ltd. - New Platform Ltd. - http://flexberry.ru/License-FlexberryOrm-Runtime - https://flexberry.net/ru/developers-flexberry-orm.html - https://flexberry.net/img/logo-color.png - true - Flexberry ORM OData Service Package. - - Changed - 1. Updated Flexberry ORM up to 7.2.0-beta01. - Fixed - 1. Fixed loading of object with crushing of already loaded masters. - 2. Fixed loading of details. - - Copyright New Platform Ltd 2023 - Flexberry ORM OData ODataService - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + NewPlatform.Flexberry.ORM.ODataService + 7.2.0-beta02 + Flexberry ORM ODataService + New Platform Ltd. + New Platform Ltd. + http://flexberry.ru/License-FlexberryOrm-Runtime + https://flexberry.net/ru/developers-flexberry-orm.html + https://flexberry.net/img/logo-color.png + true + Flexberry ORM OData Service Package. + + Added + 1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). + + Changed + 1. Updated Flexberry ORM up to 7.2.0-beta01. + + Fixed + 1. Fixed loading of object with crushing of already loaded masters. + 2. Fixed loading of details. + + Copyright New Platform Ltd 2023 + Flexberry ORM OData ODataService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bfeca1a6a34d0062e70f419cb96d671cb7fee923 Mon Sep 17 00:00:00 2001 From: inaidanov Date: Fri, 2 Feb 2024 17:44:01 +0500 Subject: [PATCH 11/18] =?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=B8=D1=8F=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++-- .../Controllers/DataObjectController.ModifyData.cs | 2 +- .../CRUD/Update/UpdateViewsTest.cs | 4 ++-- .../Startups/UpdateViewsTestStartup.cs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5323ee85..dc3351ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### 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). +1. Helper class `DataObjectEdmModelDependencies` (it helps send named settings of Unity to class `DataObjectEdmModel`). +2. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). ### Changed 1. Updated Flexberry ORM up to 7.2.0-beta01. ### Fixed 1. Fixed loading of object with crushing of already loaded masters. -2. Fixed loading of details. +2. Fixed loading of details. ## [7.1.1] - 2023.06.08 diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index a3e0e503..fd279b66 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -830,7 +830,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view) } /// - /// Добавляет объект данных в список на обновление если его там ещё нет. + /// Добавляет объект данных в список на обновление, если его там ещё нет. /// /// Список на обновление. /// Объект данных, который добавляем. diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs index 17036da3..b7a9f6ec 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs @@ -13,8 +13,8 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update using Xunit.Abstractions; /// - /// Тесты для проверки работы UpdateViews. Для запуска OData backend используется модифицированная версия Startup - , - /// которая задаёт UpdateView для Берлоги и Медведя. + /// Тесты для проверки работы UpdateViews - представлений, которые используются вместо представления по умолчанию при обновлении объекта через OData. + /// Для запуска OData backend используется модифицированная версия Startup - , которая задаёт UpdateView для Берлоги и Медведя. /// public class UpdateViewsTest : BaseODataServiceIntegratedTest { diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs index 68239ad9..85785963 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs @@ -19,7 +19,7 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests using Unity; /// - /// Startup for testing UpdateView configuration. + /// Startup for testing UpdateView configuration - a view that is used instead of a default view during data object updates. /// Differs from TestStartup that it sets UpdateView for and data objects. /// public class UpdateViewsTestStartup : Startup From 4d90a68ba3ad821ea4e8144bb1247b66a3b0f0a9 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 19 Feb 2024 14:51:03 +0500 Subject: [PATCH 12/18] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BB=D0=B8=D1=88=D0=BD=D0=B5=D0=B5=20=D0=B8=D0=B7=20?= =?UTF-8?q?changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3351ed..a62c646e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -1. Helper class `DataObjectEdmModelDependencies` (it helps send named settings of Unity to class `DataObjectEdmModel`). -2. `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). +1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). ### Changed 1. Updated Flexberry ORM up to 7.2.0-beta01. From 127e7b4ac9103d165f63e011f271d70ef8a6f202 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 4 Mar 2024 13:45:56 +0500 Subject: [PATCH 13/18] =?UTF-8?q?=D0=9E=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 --- .directory | 2 ++ .../Extensions/DataServiceExtensions.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .directory diff --git a/.directory b/.directory new file mode 100644 index 00000000..7d357e67 --- /dev/null +++ b/.directory @@ -0,0 +1,2 @@ +[Desktop Entry] +Icon=folder-violet diff --git a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs index a299aa6c..b7639b38 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs @@ -245,7 +245,7 @@ public static void SafeLoadObject(this IDataService dataService, DataObject data throw new ArgumentNullException(nameof(view)); } - // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказать отдельные операции с детейлами и перевычитка затрёт эти изменения. + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. View miniView = view.Clone(); DetailInView[] miniViewDetails = miniView.Details; miniView.Details = new DetailInView[0]; From e0fe97470bfbf3b6fc161be86fbe450d3aae360d Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 4 Mar 2024 14:31:56 +0500 Subject: [PATCH 14/18] =?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=B1=D0=B8=D0=BB=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CRUD/BatchTest.cs | 515 +++++++++--------- 1 file changed, 260 insertions(+), 255 deletions(-) diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs index c0de8995..8dc8960e 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs @@ -1,255 +1,260 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update -{ - using System; - using System.Collections.Generic; - 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.Tests.Extensions; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; - using Xunit; - using View = ICSSoft.STORMNET.View; - - /// - /// The class of tests for CRUD operations at Batch form. - /// There are extra batch tests at . - /// - public class BatchTest : BaseODataServiceIntegratedTest - { -#if NETCOREAPP - /// - /// Default constructor. - /// - /// Factory for application. - /// Output for debug information. - public BatchTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) - : base(factory, output) - { - } -#endif - - /// - /// Test batch update of master-class with class at the same time. - /// It checks that dataobject cache is not crashed. - /// There are a master and object with link to master at batch request. Master is the first at the batch request. The link between object and master is not changed. - /// It is necessary that during batch processing master stay the same and is not overwriten. - /// - [Fact] - public void UpdateMasterAndClassTest() - { - ActODataService(args => - { - // Arrange. - string[] porodaPropertiesNames = - { - Information.ExtractPropertyPath<Порода>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Порода>(x => x.Название), - }; - string[] koshkaPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - }; - View porodaDynamicView = new View(new ViewAttribute("porodaDynamicView", porodaPropertiesNames), typeof(Порода)); - View koshkaDynamicView = new View(new ViewAttribute("koshkaDynamicView", koshkaPropertiesNames), typeof(Кошка)); - - const string InitialName = "Initial"; - const string OtherName = "Other"; - Порода poroda = new Порода() { Название = InitialName }; - Кошка koshka = new Кошка() { Кличка = InitialName, Порода = poroda}; - args.DataService.UpdateObject(koshka); - - Порода poroda1 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); - Кошка koshka1 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); - Assert.NotNull(poroda1); - Assert.NotNull(koshka1); - - poroda.Название = OtherName; - koshka.Кличка = OtherName; - - string requestJsonDatakoshka = koshka.ToJson(koshkaDynamicView, args.Token.Model); - DataObjectDictionary objJsonKoshka = DataObjectDictionary.Parse(requestJsonDatakoshka, koshkaDynamicView, args.Token.Model); - - objJsonKoshka.Add( - $"{nameof(Кошка.Порода)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name, - ((KeyGuid)poroda.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDatakoshka = objJsonKoshka.Serialize(); - - const string baseUrl = "http://localhost/odata"; - string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. - { - - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name}", - poroda.ToJson(porodaDynamicView, args.Token.Model), - poroda), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", - requestJsonDatakoshka, - koshka), - }; - - // Act. - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - // Assert. - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - Порода poroda2 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); - Кошка koshka2 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); - Assert.NotNull(poroda2); - Assert.NotNull(koshka2); - Assert.Equal(OtherName, poroda2.Название); - Assert.Equal(OtherName, koshka2.Кличка); - } - }); - } - - /// - /// Test batch update with inheritance. - /// It checks that dataobject cache is not crashed. - /// There are classes A, its detail B, that has descendant C. During class A loading its details are loaded too, but details are loaded by View of class B, while details are of class C. - /// Thus there are objects of type C at the cache while they are loaded by properties of class B only. That's why the state of details is LightLoaded. - /// It is necessary to post-load only propertues that are not loaded before (loaded properties can be changed). - /// - [Fact] - public void UpdateWithInheritanceAndDetailsTest() - { - ActODataService(args => - { - // Arrange. - const string InitialName = "Initial"; - const string OtherName = "Other"; - TestConfiguration testConfiguration = new TestConfiguration() { Name = InitialName }; - FirstLevel first = new FirstLevel() { Name = InitialName, TestConfiguration = testConfiguration }; - TestClass second1 = new TestClass { Name = InitialName, FirstLevel = first }; - TestAssociation second2 = new TestAssociation { Name = InitialName, FirstLevel = first, SecondLevel1 = second1 }; - ThirdLevel third = new ThirdLevel { Name = InitialName, TestClass = second1 }; - DataObject[] updateObjects = new DataObject[] { testConfiguration, first, second1, second2, third }; - args.DataService.UpdateObjects(ref updateObjects); - - second2.Name = OtherName; // Изменение значения детейла одного типа, который имеет мастеровую ссылку на детейл второго типа (второй тип имеет детейл собственный). - ThirdLevel third2 = new ThirdLevel { Name = OtherName, TestClass = second1 }; // Добавление детейлов в детейл второго типа. - - string[] firstPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View firstLevelDynamicView = new View(new ViewAttribute("firstLevelDynamicView", firstPropertiesNames), typeof(FirstLevel)); - - string[] second1PropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View second1DynamicView = new View(new ViewAttribute("second1DynamicView", second1PropertiesNames), typeof(TestClass)); - - string[] second2PropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View second2DynamicView = new View(new ViewAttribute("second2DynamicView", second2PropertiesNames), typeof(TestAssociation)); - - string[] thirdPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View thirdLevelDynamicView = new View(new ViewAttribute("thirdDynamicView", thirdPropertiesNames), typeof(ThirdLevel)); - - // Операция изменения детейла второго типа (он попадает в батч-запрос как агрегатор к добавляемому детейлу второго уровня). - string requestJsonDataSecond1 = second1.ToJson(second1DynamicView, args.Token.Model); - DataObjectDictionary objJsonSecond1 = DataObjectDictionary.Parse(requestJsonDataSecond1, second1DynamicView, args.Token.Model); - objJsonSecond1.Add( // Добавляется ссылка на агрегатор. - $"{nameof(TestClass.FirstLevel)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); - requestJsonDataSecond1 = objJsonSecond1.Serialize(); - - // Операция вставки детейла второго уровня. - string requestJsonDataThird2 = third2.ToJson(thirdLevelDynamicView, args.Token.Model); - DataObjectDictionary objJsonThird2 = DataObjectDictionary.Parse(requestJsonDataThird2, thirdLevelDynamicView, args.Token.Model); - objJsonThird2.Add( // Добавляется ссылка на агрегатор. - $"{nameof(ThirdLevel.TestClass)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); - requestJsonDataThird2 = objJsonThird2.Serialize(); - - // Операция изменения детейла первого типа. - string requestJsonDataSecond2 = second2.ToJson(second2DynamicView, args.Token.Model); - DataObjectDictionary objJsonSecond2 = DataObjectDictionary.Parse(requestJsonDataSecond2, second2DynamicView, args.Token.Model); - objJsonSecond2.Add( // Добавляется ссылка на агрегатор. - $"{nameof(TestAssociation.FirstLevel)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); - objJsonSecond2.Add( // Добавляется ссылка мастеровая на другой детейл. - $"{nameof(TestAssociation.SecondLevel1)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); - requestJsonDataSecond2 = objJsonSecond2.Serialize(); - - const string baseUrl = "http://localhost/odata"; - string[] changesets = new[] - { - - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name}", - requestJsonDataSecond1, - second1), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ThirdLevel)).Name}", - requestJsonDataThird2, - third2), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestAssociation)).Name}", - requestJsonDataSecond2, - second2), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - - // Код для удобства отлавливания исключений. - args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => - { - Exception currentException = exception; - - while (currentException != null) - { - currentException = currentException.InnerException; - } - - return exception; - }; - - // Act. - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - // Assert. - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.Created, HttpStatusCode.OK }); - - string[] thirdPropertiesNames2 = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - Information.ExtractPropertyPath(x => x.TestClass), - }; - View thirdLevelDynamicView2 = new View(new ViewAttribute("thirdDynamicView2", thirdPropertiesNames2), typeof(ThirdLevel)); - List thirdLevelList = args.DataService.Query(thirdLevelDynamicView2).Where(x => x.TestClass.__PrimaryKey == second1.__PrimaryKey).ToList(); - Assert.NotNull(thirdLevelList); - Assert.True(thirdLevelList.Any()); - Assert.Equal(2, thirdLevelList.Count); - - TestAssociation checkAssociation = args.DataService.Query(second2DynamicView).FirstOrDefault(x => x.__PrimaryKey == second2.__PrimaryKey); - Assert.NotNull(checkAssociation); - Assert.Equal(OtherName, checkAssociation.Name); - } - }); - } - } -} - +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +{ + using System; + using System.Collections.Generic; + 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.Tests.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; + using Xunit; + using View = ICSSoft.STORMNET.View; + + /// + /// The class of tests for CRUD operations at Batch form. + /// There are extra batch tests at . + /// +#if NETFRAMEWORK + public class BatchTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BatchTest : BaseODataServiceIntegratedTest +#endif + { +#if NETCOREAPP + /// + /// Default constructor. + /// + /// Factory for application. + /// Output for debug information. + public BatchTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + : base(factory, output) + { + } +#endif + + /// + /// Test batch update of master-class with class at the same time. + /// It checks that dataobject cache is not crashed. + /// There are a master and object with link to master at batch request. Master is the first at the batch request. The link between object and master is not changed. + /// It is necessary that during batch processing master stay the same and is not overwriten. + /// + [Fact] + public void UpdateMasterAndClassTest() + { + ActODataService(args => + { + // Arrange. + string[] porodaPropertiesNames = + { + Information.ExtractPropertyPath<Порода>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Порода>(x => x.Название), + }; + string[] koshkaPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + }; + View porodaDynamicView = new View(new ViewAttribute("porodaDynamicView", porodaPropertiesNames), typeof(Порода)); + View koshkaDynamicView = new View(new ViewAttribute("koshkaDynamicView", koshkaPropertiesNames), typeof(Кошка)); + + const string InitialName = "Initial"; + const string OtherName = "Other"; + Порода poroda = new Порода() { Название = InitialName }; + Кошка koshka = new Кошка() { Кличка = InitialName, Порода = poroda}; + args.DataService.UpdateObject(koshka); + + Порода poroda1 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); + Кошка koshka1 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); + Assert.NotNull(poroda1); + Assert.NotNull(koshka1); + + poroda.Название = OtherName; + koshka.Кличка = OtherName; + + string requestJsonDatakoshka = koshka.ToJson(koshkaDynamicView, args.Token.Model); + DataObjectDictionary objJsonKoshka = DataObjectDictionary.Parse(requestJsonDatakoshka, koshkaDynamicView, args.Token.Model); + + objJsonKoshka.Add( + $"{nameof(Кошка.Порода)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name, + ((KeyGuid)poroda.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDatakoshka = objJsonKoshka.Serialize(); + + const string baseUrl = "http://localhost/odata"; + string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. + { + + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name}", + poroda.ToJson(porodaDynamicView, args.Token.Model), + poroda), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + requestJsonDatakoshka, + koshka), + }; + + // Act. + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + // Assert. + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + Порода poroda2 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); + Кошка koshka2 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); + Assert.NotNull(poroda2); + Assert.NotNull(koshka2); + Assert.Equal(OtherName, poroda2.Название); + Assert.Equal(OtherName, koshka2.Кличка); + } + }); + } + + /// + /// Test batch update with inheritance. + /// It checks that dataobject cache is not crashed. + /// There are classes A, its detail B, that has descendant C. During class A loading its details are loaded too, but details are loaded by View of class B, while details are of class C. + /// Thus there are objects of type C at the cache while they are loaded by properties of class B only. That's why the state of details is LightLoaded. + /// It is necessary to post-load only propertues that are not loaded before (loaded properties can be changed). + /// + [Fact] + public void UpdateWithInheritanceAndDetailsTest() + { + ActODataService(args => + { + // Arrange. + const string InitialName = "Initial"; + const string OtherName = "Other"; + TestConfiguration testConfiguration = new TestConfiguration() { Name = InitialName }; + FirstLevel first = new FirstLevel() { Name = InitialName, TestConfiguration = testConfiguration }; + TestClass second1 = new TestClass { Name = InitialName, FirstLevel = first }; + TestAssociation second2 = new TestAssociation { Name = InitialName, FirstLevel = first, SecondLevel1 = second1 }; + ThirdLevel third = new ThirdLevel { Name = InitialName, TestClass = second1 }; + DataObject[] updateObjects = new DataObject[] { testConfiguration, first, second1, second2, third }; + args.DataService.UpdateObjects(ref updateObjects); + + second2.Name = OtherName; // Изменение значения детейла одного типа, который имеет мастеровую ссылку на детейл второго типа (второй тип имеет детейл собственный). + ThirdLevel third2 = new ThirdLevel { Name = OtherName, TestClass = second1 }; // Добавление детейлов в детейл второго типа. + + string[] firstPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View firstLevelDynamicView = new View(new ViewAttribute("firstLevelDynamicView", firstPropertiesNames), typeof(FirstLevel)); + + string[] second1PropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View second1DynamicView = new View(new ViewAttribute("second1DynamicView", second1PropertiesNames), typeof(TestClass)); + + string[] second2PropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View second2DynamicView = new View(new ViewAttribute("second2DynamicView", second2PropertiesNames), typeof(TestAssociation)); + + string[] thirdPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View thirdLevelDynamicView = new View(new ViewAttribute("thirdDynamicView", thirdPropertiesNames), typeof(ThirdLevel)); + + // Операция изменения детейла второго типа (он попадает в батч-запрос как агрегатор к добавляемому детейлу второго уровня). + string requestJsonDataSecond1 = second1.ToJson(second1DynamicView, args.Token.Model); + DataObjectDictionary objJsonSecond1 = DataObjectDictionary.Parse(requestJsonDataSecond1, second1DynamicView, args.Token.Model); + objJsonSecond1.Add( // Добавляется ссылка на агрегатор. + $"{nameof(TestClass.FirstLevel)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); + requestJsonDataSecond1 = objJsonSecond1.Serialize(); + + // Операция вставки детейла второго уровня. + string requestJsonDataThird2 = third2.ToJson(thirdLevelDynamicView, args.Token.Model); + DataObjectDictionary objJsonThird2 = DataObjectDictionary.Parse(requestJsonDataThird2, thirdLevelDynamicView, args.Token.Model); + objJsonThird2.Add( // Добавляется ссылка на агрегатор. + $"{nameof(ThirdLevel.TestClass)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); + requestJsonDataThird2 = objJsonThird2.Serialize(); + + // Операция изменения детейла первого типа. + string requestJsonDataSecond2 = second2.ToJson(second2DynamicView, args.Token.Model); + DataObjectDictionary objJsonSecond2 = DataObjectDictionary.Parse(requestJsonDataSecond2, second2DynamicView, args.Token.Model); + objJsonSecond2.Add( // Добавляется ссылка на агрегатор. + $"{nameof(TestAssociation.FirstLevel)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); + objJsonSecond2.Add( // Добавляется ссылка мастеровая на другой детейл. + $"{nameof(TestAssociation.SecondLevel1)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); + requestJsonDataSecond2 = objJsonSecond2.Serialize(); + + const string baseUrl = "http://localhost/odata"; + string[] changesets = new[] + { + + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name}", + requestJsonDataSecond1, + second1), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ThirdLevel)).Name}", + requestJsonDataThird2, + third2), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestAssociation)).Name}", + requestJsonDataSecond2, + second2), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + + // Код для удобства отлавливания исключений. + args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => + { + Exception currentException = exception; + + while (currentException != null) + { + currentException = currentException.InnerException; + } + + return exception; + }; + + // Act. + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + // Assert. + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.Created, HttpStatusCode.OK }); + + string[] thirdPropertiesNames2 = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + Information.ExtractPropertyPath(x => x.TestClass), + }; + View thirdLevelDynamicView2 = new View(new ViewAttribute("thirdDynamicView2", thirdPropertiesNames2), typeof(ThirdLevel)); + List thirdLevelList = args.DataService.Query(thirdLevelDynamicView2).Where(x => x.TestClass.__PrimaryKey == second1.__PrimaryKey).ToList(); + Assert.NotNull(thirdLevelList); + Assert.True(thirdLevelList.Any()); + Assert.Equal(2, thirdLevelList.Count); + + TestAssociation checkAssociation = args.DataService.Query(second2DynamicView).FirstOrDefault(x => x.__PrimaryKey == second2.__PrimaryKey); + Assert.NotNull(checkAssociation); + Assert.Equal(OtherName, checkAssociation.Name); + } + }); + } + } +} + From db06e78c55587e2207649d43fe6f915e4b535fab Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 4 Mar 2024 16:53:55 +0500 Subject: [PATCH 15/18] =?UTF-8?q?=D0=9B=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=84=D0=B0=D0=B9=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .directory | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .directory diff --git a/.directory b/.directory deleted file mode 100644 index 7d357e67..00000000 --- a/.directory +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Icon=folder-violet From bd419f025a7a4f439d1b42bc05ee15db9e28b31f Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 4 Mar 2024 17:51:55 +0500 Subject: [PATCH 16/18] test --- .../DataObjectController.ModifyData.cs | 2170 ++++++++--------- 1 file changed, 1085 insertions(+), 1085 deletions(-) diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index fd279b66..e5b682bd 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -1,1085 +1,1085 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Controllers -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Text; - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET.FunctionalLanguage; - using Microsoft.AspNet.OData; - using Microsoft.OData.Edm; - using NewPlatform.Flexberry.ORM.ODataService.Batch; - using NewPlatform.Flexberry.ORM.ODataService.Extensions; - using NewPlatform.Flexberry.ORM.ODataService.Files; - using NewPlatform.Flexberry.ORM.ODataService.Files.Providers; - using NewPlatform.Flexberry.ORM.ODataService.Formatter; - using Newtonsoft.Json; - using File = ICSSoft.STORMNET.FileType.File; - using KeySegment = Microsoft.OData.UriParser.KeySegment; - -#if NETFRAMEWORK - using System.Net.Http.Formatting; - using System.Web.Http; - using System.Web.Http.Results; - using System.Web.Http.Validation; - using NewPlatform.Flexberry.ORM.ODataService.Events; - using NewPlatform.Flexberry.ORM.ODataService.Handlers; -#endif -#if NETSTANDARD - using Microsoft.AspNet.OData.Formatter; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Mvc.Formatters; - using Microsoft.Extensions.Primitives; - using NewPlatform.Flexberry.ORM.ODataService.Middleware; -#endif - - /// - /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. - /// - public partial class DataObjectController - { - /// - /// Метаданные файлов, временно загруженных в каталог файлового хранилища и привязанных к свойствам обрабатываемых объектов данных. - /// Файлы будут удалены из файловой системы - /// в случае успешного сохранения объектов данных. - /// - private List _removingFileDescriptions = new List(); - - /// - /// Кэш типов, у которых одинакового типа детейлы и мастера. - /// - private List _typesWithSameDetailAndMaster = new List(); - - /// - /// Кэш типов, у которых нет одинакового типа детейлов и мастеров. - /// - private List _typesWithNotSameDetailAndMaster = new List(); - - /// - /// Создание сущности и всех связанных. При существовании в БД произойдёт обновление. - /// - /// Создаваемая сущность. - /// Созданная сущность. -#if NETFRAMEWORK - public HttpResponseMessage Post([FromBody] EdmEntityObject edmEntity) -#elif NETSTANDARD - public IActionResult Post([FromBody] EdmEntityObject edmEntity) -#endif - { - try - { - if (edmEntity == null) - { - edmEntity = ReplaceOdataBindNull(); - } - - DataObject obj = UpdateObject(edmEntity, null); - ExecuteCallbackAfterCreate(obj); - - edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); - var responseForPreferMinimal = TestPreferMinimal(); - if (responseForPreferMinimal != null) - { - return responseForPreferMinimal; - } - -#if NETFRAMEWORK - var result = Request.CreateResponse(HttpStatusCode.Created, edmEntity); - if (Request.Headers.Contains("Prefer")) - { - result.Headers.Add("Preference-Applied", "return=representation"); - } -#elif NETSTANDARD - var result = new ObjectResult(edmEntity) { StatusCode = StatusCodes.Status201Created }; - if (Request.Headers.ContainsKey("Prefer")) - { - Response.Headers.Add("Preference-Applied", "return=representation"); - } -#endif - - return result; - } - catch (Exception ex) - { -#if NETFRAMEWORK - return InternalServerErrorMessage(ex); -#elif NETSTANDARD - throw CustomException(ex); -#endif - } - } - - /// - /// Обновление сущности (свойства могут быть заданы частично, т.е. указывать можно значения только измененных свойств). - /// Если сущности с заданным ключом нет в БД происходит Upsert (в соответствии со стандартом). - /// - /// Ключ обновляемой сущности. - /// Обновляемая сущность. - /// Обновлённая сущность. -#if NETFRAMEWORK - public HttpResponseMessage Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) -#elif NETSTANDARD - public IActionResult Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) -#endif - { - try - { - if (key == null) - { - throw new ArgumentNullException("key"); - } - - if (edmEntity == null) - { - edmEntity = ReplaceOdataBindNull(); - } - - IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; - -#if NETFRAMEWORK - var dictionary = Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? - (Dictionary)Request.Properties[ExtendedODataEntityDeserializer.Dictionary] : - new Dictionary(); -#elif NETSTANDARD - var dictionary = Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? - (Dictionary)Request.HttpContext.Items[ExtendedODataEntityDeserializer.Dictionary] : - new Dictionary(); -#endif - - foreach (var prop in entityType.Properties()) - { - if (!dictionary.ContainsKey(prop.Name) && edmEntity.GetChangedPropertyNames().Contains(prop.Name) && prop is EdmNavigationProperty) - { - const string msg = "Error processing request stream. Deep updates are not supported in PUT or PATCH operations."; -#if NETFRAMEWORK - return Request.CreateResponse(HttpStatusCode.BadRequest, msg); -#elif NETSTANDARD - return BadRequest(msg); -#endif - } - - if (dictionary.ContainsKey(prop.Name) && dictionary[prop.Name] == null && - (!prop.Type.IsNullable || prop.Type.IsCollection())) - { - string msg = $"The property {prop.Name} can not be null."; -#if NETFRAMEWORK - return Request.CreateResponse(HttpStatusCode.BadRequest, msg); -#elif NETSTANDARD - return BadRequest(msg); -#endif - } - } - - DataObject obj = UpdateObject(edmEntity, key); - ExecuteCallbackAfterUpdate(obj); - - var responseForPreferMinimal = TestPreferMinimal(); - if (responseForPreferMinimal != null) - { - return responseForPreferMinimal; - } - -#if NETFRAMEWORK - if (!Request.Headers.Contains("Prefer")) - { - return Request.CreateResponse(HttpStatusCode.NoContent); - } -#elif NETSTANDARD - if (!Request.Headers.ContainsKey("Prefer")) - { - return NoContent(); - } -#endif - - edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); -#if NETFRAMEWORK - var result = Request.CreateResponse(HttpStatusCode.OK, edmEntity); - result.Headers.Add("Preference-Applied", "return=representation"); -#elif NETSTANDARD - var result = Ok(edmEntity); - Response.Headers.Add("Preference-Applied", "return=representation"); -#endif - return result; - } - catch (Exception ex) - { -#if NETFRAMEWORK - return InternalServerErrorMessage(ex); -#elif NETSTANDARD - throw CustomException(ex); -#endif - } - } - - /// - /// Осуществляет удаление сущности. - /// - /// - /// Результат выполнения запроса типа , соответствующий статусу . - /// -#if NETFRAMEWORK - public HttpResponseMessage DeleteString() -#elif NETSTANDARD - public NoContentResult DeleteString() -#endif - { - var keySegment = ODataPath.Segments[1] as KeySegment; - string key = keySegment.Keys.First().Value.ToString().Trim().Replace("'", string.Empty); - return DeleteEntity(key); - } - - /// - /// Осуществляет удаление сущности. - /// - /// - /// Результат выполнения запроса типа , соответствующий статусу . - /// -#if NETFRAMEWORK - public HttpResponseMessage DeleteGuid() -#elif NETSTANDARD - public NoContentResult DeleteGuid() -#endif - { - var keySegment = ODataPath.Segments[1] as KeySegment; - Guid key = new Guid(keySegment.Keys.First().Value.ToString()); - return DeleteEntity(key); - } - -#if NETFRAMEWORK - private HttpResponseMessage DeleteEntity(object key) -#elif NETSTANDARD - private NoContentResult DeleteEntity(object key) -#endif - { - try - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - - Init(); - - /* В ситуации, когда мастер и детейл одного типа, ORM без подгрузки копии данных не может корректно разобрать порядок, - * в котором объекты должны быть удалены. - */ - bool needDataCopyLoad = _typesWithSameDetailAndMaster.Contains(type); - if (!_typesWithNotSameDetailAndMaster.Contains(type) && !needDataCopyLoad) - { - string[] props = Information.GetAllPropertyNames(type); - int length = props.Length; - int index = 0; - - while (!needDataCopyLoad && index < length) - { - string prop = props[index]; - Type propType = Information.GetPropertyType(type, prop); - needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; - - index++; - } - } - - DataObject obj = null; - if (!needDataCopyLoad) - { - obj = DataObjectCache.CreateDataObject(type, key); - _typesWithNotSameDetailAndMaster.Add(type); - } - else - { - obj = LoadObject(type, key.ToString()); - _typesWithSameDetailAndMaster.Add(type); - } - - // Удаляем объект с заданным ключем. - // Детейлы удалятся вместе с агрегатором автоматически. - // Если удаляемый объект является мастером для какого-либо объекта, то - // спецификация предполагает, что зависимые объекты будут каскадно удалены либо ссылки в них заменены - // на null/значению по умолчанию, если это задано в модели через ReferentialConstraints. - // Но если это задано в модели, то соответвующие объекты данных реализуют интерфейсы - // IReferencesCascadeDelete/IReferencesNullDelete и требуемые действия будут выполнены автоматически. - // В данный момент ReferentialConstraints не создаются в модели. - obj.SetStatus(ObjectStatus.Deleted); - - // Раз объект данных удаляется, то и все ассоциированные с ним файлы должны быть удалены. - // Запоминаем метаданные всех ассоциированных файлов, кроме файлов соответствующих файловым свойствам типа File - // (файлы соответствующие свойствам типа File хранятся в БД, и из файловой системы просто нечего удалять). - // TODO: подумать как быть с детейлами, детейлами детейлов, и т д. - var descriptions = _dataObjectFileAccessor.GetDataObjectFileDescriptions(_dataService, obj, new List { typeof(File) }); - _removingFileDescriptions.AddRange(descriptions); - - List objs = new List(); - - if (ExecuteCallbackBeforeDelete(obj)) - { - string agregatorPropertyName = Information.GetAgregatePropertyName(type); - if (!string.IsNullOrEmpty(agregatorPropertyName)) - { - DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); - - if (agregator != null) - { - objs.Add(agregator); - } - } - - objs.Add(obj); - - if (IsBatchChangeSetRequest) - { -#if NETFRAMEWORK - List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; - -#elif NETSTANDARD - List dataObjectsToUpdate = (List)HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; -#endif - dataObjectsToUpdate.AddRange(objs); - allProcessedObjects.Add(obj); - } - else - { - DataObject[] dataObjects = objs.ToArray(); - _dataService.UpdateObjects(ref dataObjects); - } - } - - // При успешном удалении вычищаем из файловой системы, файлы подлежащие удалению. - _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); - ExecuteCallbackAfterDelete(obj); - -#if NETFRAMEWORK - return Request.CreateResponse(HttpStatusCode.NoContent); -#elif NETSTANDARD - return NoContent(); -#endif - } - catch (Exception ex) - { - _removingFileDescriptions.Clear(); -#if NETFRAMEWORK - return InternalServerErrorMessage(ex); -#elif NETSTANDARD - throw CustomException(ex); -#endif - } - } - -#if NETFRAMEWORK - /// - /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. - /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. - /// - /// Ошибка сервиса. - /// Http-ответ. - private HttpResponseMessage InternalServerErrorMessage(Exception ex) - { - return InternalServerErrorMessage(ex, _events, Request); - } - - /// - /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. - /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. - /// - /// Ошибка сервиса. - /// The container with registered events. - /// Original HTTP request message for create a response. - /// Http-ответ. - public static HttpResponseMessage InternalServerErrorMessage(Exception ex, IEventHandlerContainer events, HttpRequestMessage request) - { - HttpStatusCode code = HttpStatusCode.InternalServerError; - Exception originalEx = ex; - - if (events?.CallbackAfterInternalServerError != null) - { - ex = events?.CallbackAfterInternalServerError(ex, ref code); - } - - if (ex == null) - { - ex = new Exception("Exception is null."); - } - - StringBuilder details = new StringBuilder(); - StringBuilder trace = new StringBuilder(); - var ex2 = ex; - while (ex2.InnerException != null) - { - string detailsItem = - "{" + - $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + - $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.InnerException.Message)}" + - "}"; - if (details.Length > 0) - details.Append(", "); - details.Append(detailsItem); - ex2 = ex2.InnerException; - } - - ex2 = ex; - do - { - string traceItem = - "{" + - $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.Message)}, " + - $"{JsonConvert.ToString("stack")}: {JsonConvert.ToString(ex2.StackTrace)}" + - "}"; - if (trace.Length > 0) - trace.Append(", "); - trace.Append(traceItem); - ex2 = ex2.InnerException; - } - while (ex2 != null); - - details.Insert(0, "[").Append("]"); - trace.Insert(0, $"{{{JsonConvert.ToString("trace")}: [").Append("]}"); - - HttpResponseMessage msg = request.CreateResponse(code); - msg.Content = new StringContent( - "{" + - $"{JsonConvert.ToString("error")}: " + - "{ " + - $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + - $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex.Message)}, " + - $"{JsonConvert.ToString("details")}: {details.ToString()}, " + - $"{JsonConvert.ToString("innererror")}: {trace.ToString()}" + - "}" + - "}", - Encoding.UTF8, - "application/json"); - LogService.LogError(originalEx.Message, originalEx); - return msg; - } -#endif - -#if NETFRAMEWORK - private HttpResponseMessage TestPreferMinimal() - { - if (Request.Headers.Contains("Prefer")) - { - KeyValuePair> header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); - if (header.Value != null && header.Value.ToString().ToLower().Contains("return=minimal")) - { - HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.NoContent); - result.Headers.Add("Preference-Applied", "return=minimal"); - return result; - } - } - - return null; - } -#elif NETSTANDARD - - private NoContentResult TestPreferMinimal() - { - if (Request.Headers.ContainsKey("Prefer")) - { - KeyValuePair header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); - if (header.Value.ToString() != null && header.Value.ToString().ToLower().Contains("return=minimal")) - { - NoContentResult result = NoContent(); - Request.Headers.Add("Preference-Applied", "return=minimal"); - - return result; - } - } - - return null; - } -#endif - -#if NETFRAMEWORK - /// - /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. - /// - /// Возвращается EdmEntityObject преобразованный из JSON-строки. - private EdmEntityObject ReplaceOdataBindNull() - { - if (!Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) - { - if (Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; - throw new Exception("ReplaceOdataBindNull: edmEntity is null."); - } - - Stream stream; - - string requestContentKey = PostPatchHandler.RequestContent; - if (Request.Properties.ContainsKey(PostPatchHandler.PropertyKeyBatchRequest) && (bool)Request.Properties[PostPatchHandler.PropertyKeyBatchRequest] == true) - { - requestContentKey = PostPatchHandler.RequestContent + $"_{PostPatchHandler.PropertyKeyContentId}_{Request.Properties[PostPatchHandler.PropertyKeyContentId]}"; - } - - string json = (string)Request.Properties[requestContentKey]; - - Dictionary props = - JsonConvert.DeserializeObject>(json, new JsonSerializerSettings() { FloatParseHandling = FloatParseHandling.Decimal }); - var keys = props.Keys.ToArray(); - var odataBindNullList = new List(); - foreach (var key in keys) - { - var p = key.IndexOf("@odata.bind"); - if (p != -1 && props[key] == null) - { - props.Remove(key); - var newKey = key.Substring(0, p); - if (props.ContainsKey(newKey)) - { - props.Remove(newKey); - } - - var type = (EdmEntityTypeReference)Request.Properties[ExtendedODataEntityDeserializer.OdataBindNull]; - - var prop = type.FindNavigationProperty(newKey); - if (prop.Type.IsCollection()) - { - odataBindNullList.Add(newKey); - } - else - { - props.Add(newKey, null); - } - } - } - - json = JsonConvert.SerializeObject(props); - Request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - - IContentNegotiator negotiator = (IContentNegotiator)Configuration.Services.GetService(typeof(IContentNegotiator)); - var resultNegotiate = negotiator.Negotiate(typeof(EdmEntityObject), Request, Configuration.Formatters); - - stream = Request.Content.ReadAsStreamAsync().Result; - var formatter = resultNegotiate.Formatter; - - /* - // Другой вариант получения форматтера. - var formatter = ((ODataMediaTypeFormatter)Configuration.Formatters[0]).GetPerRequestFormatterInstance( - typeof(EdmEntityObject), Request, Request.Content.Headers.ContentType); - - */ - - var edmEntity = (EdmEntityObject)formatter.ReadFromStreamAsync( - typeof(EdmEntityObject), - stream, - Request.Content, - new ModelStateFormatterLogger(ModelState, "edmEntity")).Result; - if (edmEntity == null && Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - { - throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; - } - - foreach (var prop in odataBindNullList) - { - edmEntity.TrySetPropertyValue(prop, null); - } - - return edmEntity; - } -#elif NETSTANDARD - /// - /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. - /// - /// Возвращается EdmEntityObject преобразованный из JSON-строки. - private EdmEntityObject ReplaceOdataBindNull() - { - if (!Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) - { - if (Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; - throw new Exception("ReplaceOdataBindNull: edmEntity is null."); - } - - string json = (string)Request.HttpContext.Items[RequestHeadersHookMiddleware.PropertyKeyRequestContent]; - - Dictionary props = JsonConvert.DeserializeObject>(json); - var keys = props.Keys.ToArray(); - var odataBindNullList = new List(); - foreach (var key in keys) - { - var p = key.IndexOf("@odata.bind"); - if (p != -1 && props[key] == null) - { - props.Remove(key); - var newKey = key.Substring(0, p); - if (props.ContainsKey(newKey)) - { - props.Remove(newKey); - } - - var type = (EdmEntityTypeReference)Request.HttpContext.Items[ExtendedODataEntityDeserializer.OdataBindNull]; - - var prop = type.FindNavigationProperty(newKey); - if (prop.Type.IsCollection()) - { - odataBindNullList.Add(newKey); - } - else - { - props.Add(newKey, null); - } - } - } - - json = JsonConvert.SerializeObject(props); - Request.Body = new StringContent(json, Encoding.UTF8, "application/json").ReadAsStreamAsync().Result; - - var ictx = new InputFormatterContext( - HttpContext, - string.Empty, - ModelState, - MetadataProvider.GetMetadataForType(typeof(EdmEntityObject)), - (x, y) => new StreamReader(x, y)); - - IList formatters = ODataInputFormatterFactory.Create(); - - // The JSON input formatter is the first formatter in the OData input formatters list. - InputFormatterResult formatterResult = formatters.First().ReadRequestBodyAsync(ictx, Encoding.UTF8).Result; - - var edmEntity = (EdmEntityObject)formatterResult.Model; - - if (edmEntity == null && Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) - { - throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; - } - - foreach (var prop in odataBindNullList) - { - edmEntity.TrySetPropertyValue(prop, null); - } - - return edmEntity; - } -#endif - - /// - /// Общая логика модификации данных: вставка и обновление в зависимости от статуса. - /// Используется в Post (вставка) и Patch (обновление). - /// - /// Модифицируемая сущность. - /// Ключ сущности. Использовать, если не задан в сущности, но специфичен (не д.б. сгенерирован). - /// Созданная сущность. - private DataObject UpdateObject(EdmEntityObject edmEntity, object key) - { - Init(); - - // Список объектов для обновления. - List objs = new List(); - - try - { - // Создадим объект данных по пришедшей сущности. - // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. - DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true); - - for (int i = 0; i < objs.Count; i++) - { - ObjectStatus status = objs[i].GetStatus(false); - if (status == ObjectStatus.Created) - { - if (!ExecuteCallbackBeforeCreate(objs[i])) - { - objs.RemoveAt(i); - i++; - } - } - else - { - if (!ExecuteCallbackBeforeUpdate(objs[i])) - { - objs.RemoveAt(i); - i++; - } - } - } - - if (!OfflineManager.UnlockObjects(QueryOptions, objs)) - throw new OperationCanceledException(); // TODO - - // Обработка объектов данных в хранилище средствами сервиса данных. - // Статусы объектов должны автоматически получиться верными, т.к. в GetDataObjectByEdmEntity объект создаем - // только при неудачной попытке вычитки и лишь затем инициализируем свойства пришедшими значениями. - var objsArr = objs.ToArray(); - - // Список объектов для обновления без UnAltered. - var objsArrSmall = objsArr.Where(t => t.GetStatus() != ObjectStatus.UnAltered).ToArray(); - if (IsBatchChangeSetRequest) - { -#if NETFRAMEWORK - List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; -#elif NETSTANDARD - List dataObjectsToUpdate = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; - List allProcessedObjects = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; -#endif - dataObjectsToUpdate.AddRange(objsArrSmall); - allProcessedObjects.Add(obj); - } - else - { - _dataService.UpdateObjects(ref objsArrSmall); - } - - // При успешном обновлении вычищаем из файловой системы, файлы подлежащие удалению. - _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); - - return obj; - } - catch (Exception) - { - _removingFileDescriptions.Clear(); - throw; - } - } - - /// - /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый. - /// - /// Тип объекта, не может быть null. - /// Значение ключа. - /// Представление для загрузки объекта. - /// Объект данных. - private DataObject ReturnDataObject(Type objType, object keyValue, View view) - { - if (objType == null) - { - throw new ArgumentNullException(nameof(objType)); - } - - if (keyValue != null) - { - DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); - - if (dataObjectFromCache != 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; - } - } - - // Значение ключа автоматически создаётся. - DataObject obj; - - if (keyValue != null) - { - obj = DataObjectCache.CreateDataObject(objType, keyValue); - } - else - { - obj = (DataObject)Activator.CreateInstance(objType); - DataObjectCache.AddDataObject(obj); - } - - return obj; - } - - /// - /// Добавляет объект данных в список на обновление, если его там ещё нет. - /// - /// Список на обновление. - /// Объект данных, который добавляем. - /// Добавлять в конец списка. - private static void AddObjectToUpdate(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. - /// - /// Сущность OData. - /// Значение ключевого поля сущности. - /// Список объектов для обновления. - /// Признак, что объект добавляется в конец списка обновления. - /// Использовать представление для обновления (вместо представления по умолчанию). - /// Объект данных. - private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false) - { - if (edmEntity == null) - { - 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); - } - - // Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST. - // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. - 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, value, view); - - // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. - AddObjectToUpdate(dObjs, obj, endObject); - - // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). - string agregatorPropertyName = Information.GetAgregatePropertyName(objType); - IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); - - // Обрабатываем агрегатор первым. - List changedProps = entityProps - .Where(ep => changedPropNames.Contains(ep.Name)) - .OrderBy(ep => ep.Name != agregatorPropertyName) - .ToList(); - foreach (var prop in changedProps) - { - string dataObjectPropName; - try - { - dataObjectPropName = _model.GetDataObjectProperty(entityType.FullTypeName(), prop.Name).Name; - } - catch (KeyNotFoundException) - { - // Check if prop value is the link from master to pseudodetail (pseudoproperty). - if (HasPseudoproperty(entityType, prop.Name)) - { - continue; - } - - throw; - } - - // Обработка мастеров и детейлов. - if (prop is EdmNavigationProperty navProp) - { - edmEntity.TryGetPropertyValue(prop.Name, out value); - - EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity(); - - // Обработка мастеров. - if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne) - { - if (value is EdmEntityObject edmMaster) - { - // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. - bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); - DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); - - Information.SetPropValueByName(obj, dataObjectPropName, master); - - if (dataObjectPropName == agregatorPropertyName) - { - master.AddDetail(obj); - - // Нужно обязательно обозначить детейловое свойство загруженным, поскольку мы вносим в него изменения. - string detailPropName = Information.GetDetailArrayPropertyName(master.GetType(), obj.GetType()); - if (!string.IsNullOrEmpty(detailPropName) && !master.CheckLoadedProperty(detailPropName)) - { - master.AddLoadedProperties(detailPropName); - } - } - } - else - { - Information.SetPropValueByName(obj, dataObjectPropName, null); - } - } - - // Обработка детейлов. - if (edmMultiplicity == EdmMultiplicity.Many) - { - DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName); - - if (value is EdmEntityObjectCollection coll) - { - if (coll != null && coll.Count > 0) - { - foreach (var edmEnt in coll) - { - DataObject det = GetDataObjectByEdmEntity( - (EdmEntityObject)edmEnt, - null, - dObjs, - true, - useUpdateView); - - if (det.__PrimaryKey == null) - { - detarr.AddObject(det); - } - else - { - detarr.SetByKey(det.__PrimaryKey, det); - } - } - } - } - else - { - detarr.Clear(); - } - } - } - else - { - // Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj). - if (prop.Name != keyProperty.Name) - { - Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName); - edmEntity.TryGetPropertyValue(prop.Name, out value); - - // Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств, - // значит свойство файловое, и его нужно обработать особым образом. - if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType)) - { - IDataObjectFileProvider dataObjectFileProvider = _dataObjectFileAccessor.GetDataObjectFileProvider(dataObjectPropertyType); - - // Обработка файловых свойств объектов данных. - string serializedFileDescription = value as string; - if (serializedFileDescription == null) - { - // Файловое свойство было сброшено на клиенте. - // Ассоциированный файл должен быть удален, после успешного сохранения изменений. - // Для этого запоминаем метаданные ассоциированного файла, до того как свойство будет сброшено - // (для получения метаданных свойство будет дочитано в объект данных). - // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, - // соответственно из файловой системы просто нечего удалять, - // поэтому обходим его стороной, чтобы избежать лишных вычиток файлов из БД. - if (dataObjectPropertyType != typeof(File)) - { - var fileDescription = dataObjectFileProvider.GetFileDescription(_dataService, obj, dataObjectPropName); - _removingFileDescriptions.Add(fileDescription); - } - - // Сбрасываем файловое свойство в изменяемом объекте данных. - Information.SetPropValueByName(obj, dataObjectPropName, null); - } - else - { - // Файловое свойство было изменено, но не сброшено. - // Если в метаданных файла присутствует FileUploadKey значит файл был загружен на сервер, - // но еще не был ассоциирован с объектом данных, и это нужно сделать. - FileDescription fileDescription = FileDescription.FromJson(serializedFileDescription); - fileDescription.FilePropertyType = dataObjectPropertyType; - if (!(string.IsNullOrEmpty(fileDescription.FileUploadKey) || string.IsNullOrEmpty(fileDescription.FileName))) - { - var fileProperty = dataObjectFileProvider.GetFileProperty(_dataService, fileDescription); - Information.SetPropValueByName(obj, dataObjectPropName, fileProperty); - - // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, - // поэтому после успешного сохранения объекта данных, оссоциированный с ним файл должен быть удален из файловой системы. - // Для этого запоминаем описание загруженного файла. - if (dataObjectPropertyType == typeof(File)) - { - _removingFileDescriptions.Add(fileDescription); - } - } - } - } - else - { - // Преобразование типов для примитивных свойств. - if (value is DateTimeOffset) - value = ((DateTimeOffset)value).UtcDateTime; - if (value is EdmEnumObject) - value = ((EdmEnumObject)value).Value; - - Information.SetPropValueByName(obj, dataObjectPropName, value); - } - } - } - } - - if (!string.IsNullOrEmpty(agregatorPropertyName)) - { - DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); - - if (agregator != null) - { - AddObjectToUpdate(dObjs, agregator, endObject); - } - } - - return obj; - } - } -} +namespace NewPlatform.Flexberry.ORM.ODataService.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Text; + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET.FunctionalLanguage; + using Microsoft.AspNet.OData; + using Microsoft.OData.Edm; + using NewPlatform.Flexberry.ORM.ODataService.Batch; + using NewPlatform.Flexberry.ORM.ODataService.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Files; + using NewPlatform.Flexberry.ORM.ODataService.Files.Providers; + using NewPlatform.Flexberry.ORM.ODataService.Formatter; + using Newtonsoft.Json; + using File = ICSSoft.STORMNET.FileType.File; + using KeySegment = Microsoft.OData.UriParser.KeySegment; + +#if NETFRAMEWORK + using System.Net.Http.Formatting; + using System.Web.Http; + using System.Web.Http.Results; + using System.Web.Http.Validation; + using NewPlatform.Flexberry.ORM.ODataService.Events; + using NewPlatform.Flexberry.ORM.ODataService.Handlers; +#endif +#if NETSTANDARD + using Microsoft.AspNet.OData.Formatter; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Formatters; + using Microsoft.Extensions.Primitives; + using NewPlatform.Flexberry.ORM.ODataService.Middleware; +#endif + + /// + /// Определяет класс контроллера OData, который поддерживает запись и чтение данных с использованием OData формата. + /// + public partial class DataObjectController + { + /// + /// Метаданные файлов, временно загруженных в каталог файлового хранилища и привязанных к свойствам обрабатываемых объектов данных. + /// Файлы будут удалены из файловой системы + /// в случае успешного сохранения объектов данных. + /// + private List _removingFileDescriptions = new List(); + + /// + /// Кэш типов, у которых одинакового типа детейлы и мастера. + /// + private List _typesWithSameDetailAndMaster = new List(); + + /// + /// Кэш типов, у которых нет одинакового типа детейлов и мастеров. + /// + private List _typesWithNotSameDetailAndMaster = new List(); + + /// + /// Создание сущности и всех связанных. При существовании в БД произойдёт обновление. + /// + /// Создаваемая сущность. + /// Созданная сущность. +#if NETFRAMEWORK + public HttpResponseMessage Post([FromBody] EdmEntityObject edmEntity) +#elif NETSTANDARD + public IActionResult Post([FromBody] EdmEntityObject edmEntity) +#endif + { + try + { + if (edmEntity == null) + { + edmEntity = ReplaceOdataBindNull(); + } + + DataObject obj = UpdateObject(edmEntity, null); + ExecuteCallbackAfterCreate(obj); + + edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); + var responseForPreferMinimal = TestPreferMinimal(); + if (responseForPreferMinimal != null) + { + return responseForPreferMinimal; + } + +#if NETFRAMEWORK + var result = Request.CreateResponse(HttpStatusCode.Created, edmEntity); + if (Request.Headers.Contains("Prefer")) + { + result.Headers.Add("Preference-Applied", "return=representation"); + } +#elif NETSTANDARD + var result = new ObjectResult(edmEntity) { StatusCode = StatusCodes.Status201Created }; + if (Request.Headers.ContainsKey("Prefer")) + { + Response.Headers.Add("Preference-Applied", "return=representation"); + } +#endif + + return result; + } + catch (Exception ex) + { +#if NETFRAMEWORK + return InternalServerErrorMessage(ex); +#elif NETSTANDARD + throw CustomException(ex); +#endif + } + } + + /// + /// Обновление сущности (свойства могут быть заданы частично, т.е. указывать можно значения только измененных свойств). + /// Если сущности с заданным ключом нет в БД происходит Upsert (в соответствии со стандартом). + /// + /// Ключ обновляемой сущности. + /// Обновляемая сущность. + /// Обновлённая сущность. +#if NETFRAMEWORK + public HttpResponseMessage Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) +#elif NETSTANDARD + public IActionResult Patch([FromODataUri] Guid key, [FromBody] EdmEntityObject edmEntity) +#endif + { + try + { + if (key == null) + { + throw new ArgumentNullException("key"); + } + + if (edmEntity == null) + { + edmEntity = ReplaceOdataBindNull(); + } + + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; + +#if NETFRAMEWORK + var dictionary = Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? + (Dictionary)Request.Properties[ExtendedODataEntityDeserializer.Dictionary] : + new Dictionary(); +#elif NETSTANDARD + var dictionary = Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.Dictionary) ? + (Dictionary)Request.HttpContext.Items[ExtendedODataEntityDeserializer.Dictionary] : + new Dictionary(); +#endif + + foreach (var prop in entityType.Properties()) + { + if (!dictionary.ContainsKey(prop.Name) && edmEntity.GetChangedPropertyNames().Contains(prop.Name) && prop is EdmNavigationProperty) + { + const string msg = "Error processing request stream. Deep updates are not supported in PUT or PATCH operations."; +#if NETFRAMEWORK + return Request.CreateResponse(HttpStatusCode.BadRequest, msg); +#elif NETSTANDARD + return BadRequest(msg); +#endif + } + + if (dictionary.ContainsKey(prop.Name) && dictionary[prop.Name] == null && + (!prop.Type.IsNullable || prop.Type.IsCollection())) + { + string msg = $"The property {prop.Name} can not be null."; +#if NETFRAMEWORK + return Request.CreateResponse(HttpStatusCode.BadRequest, msg); +#elif NETSTANDARD + return BadRequest(msg); +#endif + } + } + + DataObject obj = UpdateObject(edmEntity, key); + ExecuteCallbackAfterUpdate(obj); + + var responseForPreferMinimal = TestPreferMinimal(); + if (responseForPreferMinimal != null) + { + return responseForPreferMinimal; + } + +#if NETFRAMEWORK + if (!Request.Headers.Contains("Prefer")) + { + return Request.CreateResponse(HttpStatusCode.NoContent); + } +#elif NETSTANDARD + if (!Request.Headers.ContainsKey("Prefer")) + { + return NoContent(); + } +#endif + + edmEntity = GetEdmObject(_model.GetEdmEntityType(type), obj, 1, null, null); +#if NETFRAMEWORK + var result = Request.CreateResponse(HttpStatusCode.OK, edmEntity); + result.Headers.Add("Preference-Applied", "return=representation"); +#elif NETSTANDARD + var result = Ok(edmEntity); + Response.Headers.Add("Preference-Applied", "return=representation"); +#endif + return result; + } + catch (Exception ex) + { +#if NETFRAMEWORK + return InternalServerErrorMessage(ex); +#elif NETSTANDARD + throw CustomException(ex); +#endif + } + } + + /// + /// Осуществляет удаление сущности. + /// + /// + /// Результат выполнения запроса типа , соответствующий статусу . + /// +#if NETFRAMEWORK + public HttpResponseMessage DeleteString() +#elif NETSTANDARD + public NoContentResult DeleteString() +#endif + { + var keySegment = ODataPath.Segments[1] as KeySegment; + string key = keySegment.Keys.First().Value.ToString().Trim().Replace("'", string.Empty); + return DeleteEntity(key); + } + + /// + /// Осуществляет удаление сущности. + /// + /// + /// Результат выполнения запроса типа , соответствующий статусу . + /// +#if NETFRAMEWORK + public HttpResponseMessage DeleteGuid() +#elif NETSTANDARD + public NoContentResult DeleteGuid() +#endif + { + var keySegment = ODataPath.Segments[1] as KeySegment; + Guid key = new Guid(keySegment.Keys.First().Value.ToString()); + return DeleteEntity(key); + } + +#if NETFRAMEWORK + private HttpResponseMessage DeleteEntity(object key) +#elif NETSTANDARD + private NoContentResult DeleteEntity(object key) +#endif + { + try + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + Init(); + + /* В ситуации, когда мастер и детейл одного типа, ORM без подгрузки копии данных не может корректно разобрать порядок, + * в котором объекты должны быть удалены. + */ + bool needDataCopyLoad = _typesWithSameDetailAndMaster.Contains(type); + if (!_typesWithNotSameDetailAndMaster.Contains(type) && !needDataCopyLoad) + { + string[] props = Information.GetAllPropertyNames(type); + int length = props.Length; + int index = 0; + + while (!needDataCopyLoad && index < length) + { + string prop = props[index]; + Type propType = Information.GetPropertyType(type, prop); + needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; + + index++; + } + } + + DataObject obj = null; + if (!needDataCopyLoad) + { + obj = DataObjectCache.CreateDataObject(type, key); + _typesWithNotSameDetailAndMaster.Add(type); + } + else + { + obj = LoadObject(type, key.ToString()); + _typesWithSameDetailAndMaster.Add(type); + } + + // Удаляем объект с заданным ключем. + // Детейлы удалятся вместе с агрегатором автоматически. + // Если удаляемый объект является мастером для какого-либо объекта, то + // спецификация предполагает, что зависимые объекты будут каскадно удалены либо ссылки в них заменены + // на null/значению по умолчанию, если это задано в модели через ReferentialConstraints. + // Но если это задано в модели, то соответвующие объекты данных реализуют интерфейсы + // IReferencesCascadeDelete/IReferencesNullDelete и требуемые действия будут выполнены автоматически. + // В данный момент ReferentialConstraints не создаются в модели. + obj.SetStatus(ObjectStatus.Deleted); + + // Раз объект данных удаляется, то и все ассоциированные с ним файлы должны быть удалены. + // Запоминаем метаданные всех ассоциированных файлов, кроме файлов соответствующих файловым свойствам типа File + // (файлы соответствующие свойствам типа File хранятся в БД, и из файловой системы просто нечего удалять). + // TODO: подумать как быть с детейлами, детейлами детейлов, и т д. + var descriptions = _dataObjectFileAccessor.GetDataObjectFileDescriptions(_dataService, obj, new List { typeof(File) }); + _removingFileDescriptions.AddRange(descriptions); + + List objs = new List(); + + if (ExecuteCallbackBeforeDelete(obj)) + { + string agregatorPropertyName = Information.GetAgregatePropertyName(type); + if (!string.IsNullOrEmpty(agregatorPropertyName)) + { + DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); + + if (agregator != null) + { + objs.Add(agregator); + } + } + + objs.Add(obj); + + if (IsBatchChangeSetRequest) + { +#if NETFRAMEWORK + List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; + +#elif NETSTANDARD + List dataObjectsToUpdate = (List)HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; +#endif + dataObjectsToUpdate.AddRange(objs); + allProcessedObjects.Add(obj); + } + else + { + DataObject[] dataObjects = objs.ToArray(); + _dataService.UpdateObjects(ref dataObjects); + } + } + + // При успешном удалении вычищаем из файловой системы, файлы подлежащие удалению. + _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); + ExecuteCallbackAfterDelete(obj); + +#if NETFRAMEWORK + return Request.CreateResponse(HttpStatusCode.NoContent); +#elif NETSTANDARD + return NoContent(); +#endif + } + catch (Exception ex) + { + _removingFileDescriptions.Clear(); +#if NETFRAMEWORK + return InternalServerErrorMessage(ex); +#elif NETSTANDARD + throw CustomException(ex); +#endif + } + } + +#if NETFRAMEWORK + /// + /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. + /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. + /// + /// Ошибка сервиса. + /// Http-ответ. + private HttpResponseMessage InternalServerErrorMessage(Exception ex) + { + return InternalServerErrorMessage(ex, _events, Request); + } + + /// + /// Создаётся http-ответ с кодом 500 по-умолчанию, на возникшую в сервисе ошибку. + /// Для изменения возвращаемого кода необходимо реализовать обработчик CallbackAfterInternalServerError. + /// + /// Ошибка сервиса. + /// The container with registered events. + /// Original HTTP request message for create a response. + /// Http-ответ. + public static HttpResponseMessage InternalServerErrorMessage(Exception ex, IEventHandlerContainer events, HttpRequestMessage request) + { + HttpStatusCode code = HttpStatusCode.InternalServerError; + Exception originalEx = ex; + + if (events?.CallbackAfterInternalServerError != null) + { + ex = events?.CallbackAfterInternalServerError(ex, ref code); + } + + if (ex == null) + { + ex = new Exception("Exception is null."); + } + + StringBuilder details = new StringBuilder(); + StringBuilder trace = new StringBuilder(); + var ex2 = ex; + while (ex2.InnerException != null) + { + string detailsItem = + "{" + + $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + + $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.InnerException.Message)}" + + "}"; + if (details.Length > 0) + details.Append(", "); + details.Append(detailsItem); + ex2 = ex2.InnerException; + } + + ex2 = ex; + do + { + string traceItem = + "{" + + $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex2.Message)}, " + + $"{JsonConvert.ToString("stack")}: {JsonConvert.ToString(ex2.StackTrace)}" + + "}"; + if (trace.Length > 0) + trace.Append(", "); + trace.Append(traceItem); + ex2 = ex2.InnerException; + } + while (ex2 != null); + + details.Insert(0, "[").Append("]"); + trace.Insert(0, $"{{{JsonConvert.ToString("trace")}: [").Append("]}"); + + HttpResponseMessage msg = request.CreateResponse(code); + msg.Content = new StringContent( + "{" + + $"{JsonConvert.ToString("error")}: " + + "{ " + + $"{JsonConvert.ToString("code")}: {JsonConvert.ToString($"{(int)code}")}, " + + $"{JsonConvert.ToString("message")}: {JsonConvert.ToString(ex.Message)}, " + + $"{JsonConvert.ToString("details")}: {details.ToString()}, " + + $"{JsonConvert.ToString("innererror")}: {trace.ToString()}" + + "}" + + "}", + Encoding.UTF8, + "application/json"); + LogService.LogError(originalEx.Message, originalEx); + return msg; + } +#endif + +#if NETFRAMEWORK + private HttpResponseMessage TestPreferMinimal() + { + if (Request.Headers.Contains("Prefer")) + { + KeyValuePair> header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); + if (header.Value != null && header.Value.ToString().ToLower().Contains("return=minimal")) + { + HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.NoContent); + result.Headers.Add("Preference-Applied", "return=minimal"); + return result; + } + } + + return null; + } +#elif NETSTANDARD + + private NoContentResult TestPreferMinimal() + { + if (Request.Headers.ContainsKey("Prefer")) + { + KeyValuePair header = Request.Headers.FirstOrDefault(h => h.Key.ToLower() == "prefer"); + if (header.Value.ToString() != null && header.Value.ToString().ToLower().Contains("return=minimal")) + { + NoContentResult result = NoContent(); + Request.Headers.Add("Preference-Applied", "return=minimal"); + + return result; + } + } + + return null; + } +#endif + +#if NETFRAMEWORK + /// + /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. + /// + /// Возвращается EdmEntityObject преобразованный из JSON-строки. + private EdmEntityObject ReplaceOdataBindNull() + { + if (!Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) + { + if (Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; + throw new Exception("ReplaceOdataBindNull: edmEntity is null."); + } + + Stream stream; + + string requestContentKey = PostPatchHandler.RequestContent; + if (Request.Properties.ContainsKey(PostPatchHandler.PropertyKeyBatchRequest) && (bool)Request.Properties[PostPatchHandler.PropertyKeyBatchRequest] == true) + { + requestContentKey = PostPatchHandler.RequestContent + $"_{PostPatchHandler.PropertyKeyContentId}_{Request.Properties[PostPatchHandler.PropertyKeyContentId]}"; + } + + string json = (string)Request.Properties[requestContentKey]; + + Dictionary props = + JsonConvert.DeserializeObject>(json, new JsonSerializerSettings() { FloatParseHandling = FloatParseHandling.Decimal }); + var keys = props.Keys.ToArray(); + var odataBindNullList = new List(); + foreach (var key in keys) + { + var p = key.IndexOf("@odata.bind"); + if (p != -1 && props[key] == null) + { + props.Remove(key); + var newKey = key.Substring(0, p); + if (props.ContainsKey(newKey)) + { + props.Remove(newKey); + } + + var type = (EdmEntityTypeReference)Request.Properties[ExtendedODataEntityDeserializer.OdataBindNull]; + + var prop = type.FindNavigationProperty(newKey); + if (prop.Type.IsCollection()) + { + odataBindNullList.Add(newKey); + } + else + { + props.Add(newKey, null); + } + } + } + + json = JsonConvert.SerializeObject(props); + Request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + IContentNegotiator negotiator = (IContentNegotiator)Configuration.Services.GetService(typeof(IContentNegotiator)); + var resultNegotiate = negotiator.Negotiate(typeof(EdmEntityObject), Request, Configuration.Formatters); + + stream = Request.Content.ReadAsStreamAsync().Result; + var formatter = resultNegotiate.Formatter; + + /* + // Другой вариант получения форматтера. + var formatter = ((ODataMediaTypeFormatter)Configuration.Formatters[0]).GetPerRequestFormatterInstance( + typeof(EdmEntityObject), Request, Request.Content.Headers.ContentType); + + */ + + var edmEntity = (EdmEntityObject)formatter.ReadFromStreamAsync( + typeof(EdmEntityObject), + stream, + Request.Content, + new ModelStateFormatterLogger(ModelState, "edmEntity")).Result; + if (edmEntity == null && Request.Properties.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + { + throw (Exception)Request.Properties[ExtendedODataEntityDeserializer.ReadException]; + } + + foreach (var prop in odataBindNullList) + { + edmEntity.TrySetPropertyValue(prop, null); + } + + return edmEntity; + } +#elif NETSTANDARD + /// + /// Заменяет в теле запроса представление навигационных свойств с Имя_Связи@odata.bind:null на представление Имя_Связи:null. + /// + /// Возвращается EdmEntityObject преобразованный из JSON-строки. + private EdmEntityObject ReplaceOdataBindNull() + { + if (!Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.OdataBindNull)) + { + if (Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; + throw new Exception("ReplaceOdataBindNull: edmEntity is null."); + } + + string json = (string)Request.HttpContext.Items[RequestHeadersHookMiddleware.PropertyKeyRequestContent]; + + Dictionary props = JsonConvert.DeserializeObject>(json); + var keys = props.Keys.ToArray(); + var odataBindNullList = new List(); + foreach (var key in keys) + { + var p = key.IndexOf("@odata.bind"); + if (p != -1 && props[key] == null) + { + props.Remove(key); + var newKey = key.Substring(0, p); + if (props.ContainsKey(newKey)) + { + props.Remove(newKey); + } + + var type = (EdmEntityTypeReference)Request.HttpContext.Items[ExtendedODataEntityDeserializer.OdataBindNull]; + + var prop = type.FindNavigationProperty(newKey); + if (prop.Type.IsCollection()) + { + odataBindNullList.Add(newKey); + } + else + { + props.Add(newKey, null); + } + } + } + + json = JsonConvert.SerializeObject(props); + Request.Body = new StringContent(json, Encoding.UTF8, "application/json").ReadAsStreamAsync().Result; + + var ictx = new InputFormatterContext( + HttpContext, + string.Empty, + ModelState, + MetadataProvider.GetMetadataForType(typeof(EdmEntityObject)), + (x, y) => new StreamReader(x, y)); + + IList formatters = ODataInputFormatterFactory.Create(); + + // The JSON input formatter is the first formatter in the OData input formatters list. + InputFormatterResult formatterResult = formatters.First().ReadRequestBodyAsync(ictx, Encoding.UTF8).Result; + + var edmEntity = (EdmEntityObject)formatterResult.Model; + + if (edmEntity == null && Request.HttpContext.Items.ContainsKey(ExtendedODataEntityDeserializer.ReadException)) + { + throw (Exception)Request.HttpContext.Items[ExtendedODataEntityDeserializer.ReadException]; + } + + foreach (var prop in odataBindNullList) + { + edmEntity.TrySetPropertyValue(prop, null); + } + + return edmEntity; + } +#endif + + /// + /// Общая логика модификации данных: вставка и обновление в зависимости от статуса. + /// Используется в Post (вставка) и Patch (обновление). + /// + /// Модифицируемая сущность. + /// Ключ сущности. Использовать, если не задан в сущности, но специфичен (не д.б. сгенерирован). + /// Созданная сущность. + private DataObject UpdateObject(EdmEntityObject edmEntity, object key) + { + Init(); + + // Список объектов для обновления. + List objs = new List(); + + try + { + // Создадим объект данных по пришедшей сущности. + // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. + DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true); + + for (int i = 0; i < objs.Count; i++) + { + ObjectStatus status = objs[i].GetStatus(false); + if (status == ObjectStatus.Created) + { + if (!ExecuteCallbackBeforeCreate(objs[i])) + { + objs.RemoveAt(i); + i++; + } + } + else + { + if (!ExecuteCallbackBeforeUpdate(objs[i])) + { + objs.RemoveAt(i); + i++; + } + } + } + + if (!OfflineManager.UnlockObjects(QueryOptions, objs)) + throw new OperationCanceledException(); // TODO + + // Обработка объектов данных в хранилище средствами сервиса данных. + // Статусы объектов должны автоматически получиться верными, т.к. в GetDataObjectByEdmEntity объект создаем + // только при неудачной попытке вычитки и лишь затем инициализируем свойства пришедшими значениями. + var objsArr = objs.ToArray(); + + // Список объектов для обновления без UnAltered. + var objsArrSmall = objsArr.Where(t => t.GetStatus() != ObjectStatus.UnAltered).ToArray(); + if (IsBatchChangeSetRequest) + { +#if NETFRAMEWORK + List dataObjectsToUpdate = (List)Request.Properties[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)Request.Properties[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; +#elif NETSTANDARD + List dataObjectsToUpdate = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.DataObjectsToUpdatePropertyKey]; + List allProcessedObjects = (List)Request.HttpContext.Items[DataObjectODataBatchHandler.AllProcessedObjectsPropertyKey]; +#endif + dataObjectsToUpdate.AddRange(objsArrSmall); + allProcessedObjects.Add(obj); + } + else + { + _dataService.UpdateObjects(ref objsArrSmall); + } + + // При успешном обновлении вычищаем из файловой системы, файлы подлежащие удалению. + _dataObjectFileAccessor.RemoveFileUploadDirectories(_removingFileDescriptions); + + return obj; + } + catch (Exception) + { + _removingFileDescriptions.Clear(); + throw; + } + } + + /// + /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый. + /// + /// Тип объекта, не может быть null. + /// Значение ключа. + /// Представление для загрузки объекта. + /// Объект данных. + private DataObject ReturnDataObject(Type objType, object keyValue, View view) + { + if (objType == null) + { + throw new ArgumentNullException(nameof(objType)); + } + + if (keyValue != null) + { + DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); + + if (dataObjectFromCache != 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; + } + } + + // Значение ключа автоматически создаётся. + DataObject obj; + + if (keyValue != null) + { + obj = DataObjectCache.CreateDataObject(objType, keyValue); + } + else + { + obj = (DataObject)Activator.CreateInstance(objType); + DataObjectCache.AddDataObject(obj); + } + + return obj; + } + + /// + /// Добавляет объект данных в список на обновление, если его там ещё нет. + /// + /// Список на обновление. + /// Объект данных, который добавляем. + /// Добавлять в конец списка. + private static void AddObjectToUpdate(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. + /// + /// Сущность OData. + /// Значение ключевого поля сущности. + /// Список объектов для обновления. + /// Признак, что объект добавляется в конец списка обновления. + /// Использовать представление для обновления (вместо представления по умолчанию). + /// Объект данных. + private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false) + { + if (edmEntity == null) + { + 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); + } + + // Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST. + // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. + 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, value, view); + + // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. + AddObjectToUpdate(dObjs, obj, endObject); + + // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). + string agregatorPropertyName = Information.GetAgregatePropertyName(objType); + IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); + + // Обрабатываем агрегатор первым. + List changedProps = entityProps + .Where(ep => changedPropNames.Contains(ep.Name)) + .OrderBy(ep => ep.Name != agregatorPropertyName) + .ToList(); + foreach (var prop in changedProps) + { + string dataObjectPropName; + try + { + dataObjectPropName = _model.GetDataObjectProperty(entityType.FullTypeName(), prop.Name).Name; + } + catch (KeyNotFoundException) + { + // Check if prop value is the link from master to pseudodetail (pseudoproperty). + if (HasPseudoproperty(entityType, prop.Name)) + { + continue; + } + + throw; + } + + // Обработка мастеров и детейлов. + if (prop is EdmNavigationProperty navProp) + { + edmEntity.TryGetPropertyValue(prop.Name, out value); + + EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity(); + + // Обработка мастеров. + if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne) + { + if (value is EdmEntityObject edmMaster) + { + // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. + bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); + DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); + + Information.SetPropValueByName(obj, dataObjectPropName, master); + + if (dataObjectPropName == agregatorPropertyName) + { + master.AddDetail(obj); + + // Нужно обязательно обозначить детейловое свойство загруженным, поскольку мы вносим в него изменения. + string detailPropName = Information.GetDetailArrayPropertyName(master.GetType(), obj.GetType()); + if (!string.IsNullOrEmpty(detailPropName) && !master.CheckLoadedProperty(detailPropName)) + { + master.AddLoadedProperties(detailPropName); + } + } + } + else + { + Information.SetPropValueByName(obj, dataObjectPropName, null); + } + } + + // Обработка детейлов. + if (edmMultiplicity == EdmMultiplicity.Many) + { + DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName); + + if (value is EdmEntityObjectCollection coll) + { + if (coll != null && coll.Count > 0) + { + foreach (var edmEnt in coll) + { + DataObject det = GetDataObjectByEdmEntity( + (EdmEntityObject)edmEnt, + null, + dObjs, + true, + useUpdateView); + + if (det.__PrimaryKey == null) + { + detarr.AddObject(det); + } + else + { + detarr.SetByKey(det.__PrimaryKey, det); + } + } + } + } + else + { + detarr.Clear(); + } + } + } + else + { + // Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj). + if (prop.Name != keyProperty.Name) + { + Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName); + edmEntity.TryGetPropertyValue(prop.Name, out value); + + // Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств, + // значит свойство файловое, и его нужно обработать особым образом. + if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType)) + { + IDataObjectFileProvider dataObjectFileProvider = _dataObjectFileAccessor.GetDataObjectFileProvider(dataObjectPropertyType); + + // Обработка файловых свойств объектов данных. + string serializedFileDescription = value as string; + if (serializedFileDescription == null) + { + // Файловое свойство было сброшено на клиенте. + // Ассоциированный файл должен быть удален, после успешного сохранения изменений. + // Для этого запоминаем метаданные ассоциированного файла, до того как свойство будет сброшено + // (для получения метаданных свойство будет дочитано в объект данных). + // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, + // соответственно из файловой системы просто нечего удалять, + // поэтому обходим его стороной, чтобы избежать лишных вычиток файлов из БД. + if (dataObjectPropertyType != typeof(File)) + { + var fileDescription = dataObjectFileProvider.GetFileDescription(_dataService, obj, dataObjectPropName); + _removingFileDescriptions.Add(fileDescription); + } + + // Сбрасываем файловое свойство в изменяемом объекте данных. + Information.SetPropValueByName(obj, dataObjectPropName, null); + } + else + { + // Файловое свойство было изменено, но не сброшено. + // Если в метаданных файла присутствует FileUploadKey значит файл был загружен на сервер, + // но еще не был ассоциирован с объектом данных, и это нужно сделать. + FileDescription fileDescription = FileDescription.FromJson(serializedFileDescription); + fileDescription.FilePropertyType = dataObjectPropertyType; + if (!(string.IsNullOrEmpty(fileDescription.FileUploadKey) || string.IsNullOrEmpty(fileDescription.FileName))) + { + var fileProperty = dataObjectFileProvider.GetFileProperty(_dataService, fileDescription); + Information.SetPropValueByName(obj, dataObjectPropName, fileProperty); + + // Файловое свойство типа File хранит данные ассоциированного файла прямо в БД, + // поэтому после успешного сохранения объекта данных, оссоциированный с ним файл должен быть удален из файловой системы. + // Для этого запоминаем описание загруженного файла. + if (dataObjectPropertyType == typeof(File)) + { + _removingFileDescriptions.Add(fileDescription); + } + } + } + } + else + { + // Преобразование типов для примитивных свойств. + if (value is DateTimeOffset) + value = ((DateTimeOffset)value).UtcDateTime; + if (value is EdmEnumObject) + value = ((EdmEnumObject)value).Value; + + Information.SetPropValueByName(obj, dataObjectPropName, value); + } + } + } + } + + if (!string.IsNullOrEmpty(agregatorPropertyName)) + { + DataObject agregator = (DataObject)Information.GetPropValueByName(obj, agregatorPropertyName); + + if (agregator != null) + { + AddObjectToUpdate(dObjs, agregator, endObject); + } + } + + return obj; + } + } +} From c9805d9b573a98641e7762187edc1cda16602406 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 11 Mar 2024 10:42:54 +0500 Subject: [PATCH 17/18] =?UTF-8?q?=D0=9C=D0=B5=D0=B4=D0=B2=D0=B5=D0=B4?= =?UTF-8?q?=D1=8C=20LF=20->=20CRLF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...20\264\320\262\320\265\320\264\321\214.cs" | 1700 ++++++++--------- 1 file changed, 850 insertions(+), 850 deletions(-) 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 f16467de..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" @@ -1,850 +1,850 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NewPlatform.Flexberry.ORM.ODataService.Tests -{ - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business.Audit; - using ICSSoft.STORMNET.Business.Audit.Objects; - - - // *** Start programmer edit section *** (Using statements) - - // *** End programmer edit section *** (Using statements) - - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь CustomAttributes) - - // *** End programmer edit section *** (Медведь CustomAttributes) - [BusinessServer("NewPlatform.Flexberry.ORM.ODataService.Tests.BearBS, NewPlatform.Flexberry.ORM.OD" + - "ataService.Tests.BusinessServers", ICSSoft.STORMNET.Business.DataServiceObjectEvents.OnAllEvents)] - [AutoAltered()] - [AccessType(ICSSoft.STORMNET.AccessType.none)] - [View("LoadTestView", new string[] { - "ЛесОбитания", - "ЛесОбитания.Заповедник", - "Мама", - "Мама.ЦветГлаз", - "Вес"})] - [AssociatedDetailViewAttribute("LoadTestView", "Берлога", "LoadTestView", true, "", "", true, new string[] { - ""})] - [View("OrderNumberTest", new string[] { - "ПорядковыйНомер", - "ЛесОбитания"})] - [View("МедведьE", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "Мама as \'Мама\'", - "Мама.ЦветГлаз as \'Цвет глаз\'", - "Мама.Вес", - "Папа as \'Папа\'", - "Папа.ЦветГлаз as \'Цвет глаз\'", - "Папа.Вес", - "ЛесОбитания as \'Лес обитания\'", - "ЛесОбитания.Название as \'Название\'", - "ПолеБС", - "СтранаРождения", - "СтранаРождения.Название"})] - [AssociatedDetailViewAttribute("МедведьE", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { - ""})] - [View("МедведьL", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "Мама.ЦветГлаз as \'Цвет глаз\'", - "Папа.ЦветГлаз as \'Цвет глаз\'", - "ЛесОбитания.Название as \'Название\'"})] - [View("МедведьShort", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'"})] - [View("МедведьUpdateView", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "ПолеБС"})] - [View("МедведьСДелейломИВычислимымСвойством", new string[] { - "ПорядковыйНомер as \'Порядковый номер\'", - "Вес as \'Вес\'", - "ЦветГлаз as \'Цвет глаз\'", - "Пол as \'Пол\'", - "ДатаРождения as \'Дата рождения\'", - "Мама as \'Мама\'", - "Мама.ЦветГлаз as \'Цвет глаз\'", - "Папа as \'Папа\'", - "Папа.ЦветГлаз as \'Цвет глаз\'", - "ЛесОбитания as \'Лес обитания\'", - "ЛесОбитания.Название as \'Название\'", - "МедведьСтрокой"})] - [AssociatedDetailViewAttribute("МедведьСДелейломИВычислимымСвойством", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { - ""})] - public class Медведь : ICSSoft.STORMNET.DataObject, IDataObjectWithAuditFields - { - - private int fВес; - - private ICSSoft.STORMNET.UserDataTypes.NullableDateTime fДатаРождения; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.tПол fПол; - - private string fПолеБС; - - private int fПорядковыйНомер; - - private string fЦветГлаз; - - private System.Nullable fCreateTime; - - private string fCreator; - - private string fEditor; - - private System.Nullable fEditTime; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Лес fЛесОбитания; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМама; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fПапа; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.Страна fСтранаРождения; - - private NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога fБерлога; - - // *** Start programmer edit section *** (Медведь CustomMembers) - - // *** End programmer edit section *** (Медведь CustomMembers) - - - /// - /// Вес. - /// - // *** Start programmer edit section *** (Медведь.Вес CustomAttributes) - - // *** End programmer edit section *** (Медведь.Вес CustomAttributes) - public virtual int Вес - { - get - { - // *** Start programmer edit section *** (Медведь.Вес Get start) - - // *** End programmer edit section *** (Медведь.Вес Get start) - int result = this.fВес; - // *** Start programmer edit section *** (Медведь.Вес Get end) - - // *** End programmer edit section *** (Медведь.Вес Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Вес Set start) - - // *** End programmer edit section *** (Медведь.Вес Set start) - this.fВес = value; - // *** Start programmer edit section *** (Медведь.Вес Set end) - - // *** End programmer edit section *** (Медведь.Вес Set end) - } - } - - /// - /// ВычислимоеПоле. - /// - // *** Start programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) - - // *** End programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) - [ICSSoft.STORMNET.NotStored()] - [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "@ПорядковыйНомер@ + @Вес@")] - public virtual int ВычислимоеПоле - { - get - { - // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Get) - return 0; - // *** End programmer edit section *** (Медведь.ВычислимоеПоле Get) - } - set - { - // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Set) - - // *** End programmer edit section *** (Медведь.ВычислимоеПоле Set) - } - } - - /// - /// ДатаРождения. - /// - // *** Start programmer edit section *** (Медведь.ДатаРождения CustomAttributes) - - // *** End programmer edit section *** (Медведь.ДатаРождения CustomAttributes) - public virtual ICSSoft.STORMNET.UserDataTypes.NullableDateTime ДатаРождения - { - get - { - // *** Start programmer edit section *** (Медведь.ДатаРождения Get start) - - // *** End programmer edit section *** (Медведь.ДатаРождения Get start) - ICSSoft.STORMNET.UserDataTypes.NullableDateTime result = this.fДатаРождения; - // *** Start programmer edit section *** (Медведь.ДатаРождения Get end) - - // *** End programmer edit section *** (Медведь.ДатаРождения Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ДатаРождения Set start) - - // *** End programmer edit section *** (Медведь.ДатаРождения Set start) - this.fДатаРождения = value; - // *** Start programmer edit section *** (Медведь.ДатаРождения Set end) - - // *** End programmer edit section *** (Медведь.ДатаРождения Set end) - } - } - - /// - /// МедведьСтрокой. - /// - // *** Start programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) - - // *** End programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) - [ICSSoft.STORMNET.NotStored()] - [StrLen(255)] - [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "\'ПорядковыйНомер:\' + @ПорядковыйНомер@ + \", Цвет глаз мамы:\" + isnull(@Мама.ЦветГ" + - "лаз@,\'\')")] - [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.PostgresDataService), "\'ПорядковыйНомер:\' || @ПорядковыйНомер@ || \", Цвет глаз мамы:\" || coalesce(@Мама.ЦветГ" + - "лаз@,\'\')")] - public virtual string МедведьСтрокой - { - get - { - // *** Start programmer edit section *** (Медведь.МедведьСтрокой Get) - return null; - // *** End programmer edit section *** (Медведь.МедведьСтрокой Get) - } - set - { - // *** Start programmer edit section *** (Медведь.МедведьСтрокой Set) - - // *** End programmer edit section *** (Медведь.МедведьСтрокой Set) - } - } - - /// - /// Пол. - /// - // *** Start programmer edit section *** (Медведь.Пол CustomAttributes) - - // *** End programmer edit section *** (Медведь.Пол CustomAttributes) - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.tПол Пол - { - get - { - // *** Start programmer edit section *** (Медведь.Пол Get start) - - // *** End programmer edit section *** (Медведь.Пол Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.tПол result = this.fПол; - // *** Start programmer edit section *** (Медведь.Пол Get end) - - // *** End programmer edit section *** (Медведь.Пол Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Пол Set start) - - // *** End programmer edit section *** (Медведь.Пол Set start) - this.fПол = value; - // *** Start programmer edit section *** (Медведь.Пол Set end) - - // *** End programmer edit section *** (Медведь.Пол Set end) - } - } - - /// - /// ПолеБС. - /// - // *** Start programmer edit section *** (Медведь.ПолеБС CustomAttributes) - - // *** End programmer edit section *** (Медведь.ПолеБС CustomAttributes) - [StrLen(255)] - public virtual string ПолеБС - { - get - { - // *** Start programmer edit section *** (Медведь.ПолеБС Get start) - - // *** End programmer edit section *** (Медведь.ПолеБС Get start) - string result = this.fПолеБС; - // *** Start programmer edit section *** (Медведь.ПолеБС Get end) - - // *** End programmer edit section *** (Медведь.ПолеБС Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ПолеБС Set start) - - // *** End programmer edit section *** (Медведь.ПолеБС Set start) - this.fПолеБС = value; - // *** Start programmer edit section *** (Медведь.ПолеБС Set end) - - // *** End programmer edit section *** (Медведь.ПолеБС Set end) - } - } - - /// - /// ПорядковыйНомер. - /// - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) - public virtual int ПорядковыйНомер - { - get - { - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get start) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get start) - int result = this.fПорядковыйНомер; - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get end) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set start) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set start) - this.fПорядковыйНомер = value; - // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set end) - - // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set end) - } - } - - /// - /// ЦветГлаз. - /// - // *** Start programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) - - // *** End programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) - [StrLen(255)] - public virtual string ЦветГлаз - { - get - { - // *** Start programmer edit section *** (Медведь.ЦветГлаз Get start) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Get start) - string result = this.fЦветГлаз; - // *** Start programmer edit section *** (Медведь.ЦветГлаз Get end) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ЦветГлаз Set start) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Set start) - this.fЦветГлаз = value; - // *** Start programmer edit section *** (Медведь.ЦветГлаз Set end) - - // *** End programmer edit section *** (Медведь.ЦветГлаз Set end) - } - } - - /// - /// Время создания объекта. - /// - // *** Start programmer edit section *** (Медведь.CreateTime CustomAttributes) - - // *** End programmer edit section *** (Медведь.CreateTime CustomAttributes) - public virtual System.Nullable CreateTime - { - get - { - // *** Start programmer edit section *** (Медведь.CreateTime Get start) - - // *** End programmer edit section *** (Медведь.CreateTime Get start) - System.Nullable result = this.fCreateTime; - // *** Start programmer edit section *** (Медведь.CreateTime Get end) - - // *** End programmer edit section *** (Медведь.CreateTime Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.CreateTime Set start) - - // *** End programmer edit section *** (Медведь.CreateTime Set start) - this.fCreateTime = value; - // *** Start programmer edit section *** (Медведь.CreateTime Set end) - - // *** End programmer edit section *** (Медведь.CreateTime Set end) - } - } - - /// - /// Создатель объекта. - /// - // *** Start programmer edit section *** (Медведь.Creator CustomAttributes) - - // *** End programmer edit section *** (Медведь.Creator CustomAttributes) - [StrLen(255)] - public virtual string Creator - { - get - { - // *** Start programmer edit section *** (Медведь.Creator Get start) - - // *** End programmer edit section *** (Медведь.Creator Get start) - string result = this.fCreator; - // *** Start programmer edit section *** (Медведь.Creator Get end) - - // *** End programmer edit section *** (Медведь.Creator Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Creator Set start) - - // *** End programmer edit section *** (Медведь.Creator Set start) - this.fCreator = value; - // *** Start programmer edit section *** (Медведь.Creator Set end) - - // *** End programmer edit section *** (Медведь.Creator Set end) - } - } - - /// - /// Последний редактор объекта. - /// - // *** Start programmer edit section *** (Медведь.Editor CustomAttributes) - - // *** End programmer edit section *** (Медведь.Editor CustomAttributes) - [StrLen(255)] - public virtual string Editor - { - get - { - // *** Start programmer edit section *** (Медведь.Editor Get start) - - // *** End programmer edit section *** (Медведь.Editor Get start) - string result = this.fEditor; - // *** Start programmer edit section *** (Медведь.Editor Get end) - - // *** End programmer edit section *** (Медведь.Editor Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Editor Set start) - - // *** End programmer edit section *** (Медведь.Editor Set start) - this.fEditor = value; - // *** Start programmer edit section *** (Медведь.Editor Set end) - - // *** End programmer edit section *** (Медведь.Editor Set end) - } - } - - /// - /// Время последнего редактирования объекта. - /// - // *** Start programmer edit section *** (Медведь.EditTime CustomAttributes) - - // *** End programmer edit section *** (Медведь.EditTime CustomAttributes) - public virtual System.Nullable EditTime - { - get - { - // *** Start programmer edit section *** (Медведь.EditTime Get start) - - // *** End programmer edit section *** (Медведь.EditTime Get start) - System.Nullable result = this.fEditTime; - // *** Start programmer edit section *** (Медведь.EditTime Get end) - - // *** End programmer edit section *** (Медведь.EditTime Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.EditTime Set start) - - // *** End programmer edit section *** (Медведь.EditTime Set start) - this.fEditTime = value; - // *** Start programmer edit section *** (Медведь.EditTime Set end) - - // *** End programmer edit section *** (Медведь.EditTime Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) - - // *** End programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) - [PropertyStorage(new string[] { - "ЛесОбитания"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Лес ЛесОбитания - { - get - { - // *** Start programmer edit section *** (Медведь.ЛесОбитания Get start) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Лес result = this.fЛесОбитания; - // *** Start programmer edit section *** (Медведь.ЛесОбитания Get end) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.ЛесОбитания Set start) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Set start) - this.fЛесОбитания = value; - // *** Start programmer edit section *** (Медведь.ЛесОбитания Set end) - - // *** End programmer edit section *** (Медведь.ЛесОбитания Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.Мама CustomAttributes) - - // *** End programmer edit section *** (Медведь.Мама CustomAttributes) - [PropertyStorage(new string[] { - "Мама"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Мама - { - get - { - // *** Start programmer edit section *** (Медведь.Мама Get start) - - // *** End programmer edit section *** (Медведь.Мама Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fМама; - // *** Start programmer edit section *** (Медведь.Мама Get end) - - // *** End programmer edit section *** (Медведь.Мама Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Мама Set start) - - // *** End programmer edit section *** (Медведь.Мама Set start) - this.fМама = value; - // *** Start programmer edit section *** (Медведь.Мама Set end) - - // *** End programmer edit section *** (Медведь.Мама Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.Папа CustomAttributes) - - // *** End programmer edit section *** (Медведь.Папа CustomAttributes) - [PropertyStorage(new string[] { - "Папа"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Папа - { - get - { - // *** Start programmer edit section *** (Медведь.Папа Get start) - - // *** End programmer edit section *** (Медведь.Папа Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fПапа; - // *** Start programmer edit section *** (Медведь.Папа Get end) - - // *** End programmer edit section *** (Медведь.Папа Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Папа Set start) - - // *** End programmer edit section *** (Медведь.Папа Set start) - this.fПапа = value; - // *** Start programmer edit section *** (Медведь.Папа Set end) - - // *** End programmer edit section *** (Медведь.Папа Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.СтранаРождения CustomAttributes) - - // *** End programmer edit section *** (Медведь.СтранаРождения CustomAttributes) - [PropertyStorage(new string[] { - "Страна"})] - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Страна СтранаРождения - { - get - { - // *** Start programmer edit section *** (Медведь.СтранаРождения Get start) - - // *** End programmer edit section *** (Медведь.СтранаРождения Get start) - NewPlatform.Flexberry.ORM.ODataService.Tests.Страна result = this.fСтранаРождения; - // *** Start programmer edit section *** (Медведь.СтранаРождения Get end) - - // *** End programmer edit section *** (Медведь.СтранаРождения Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.СтранаРождения Set start) - - // *** End programmer edit section *** (Медведь.СтранаРождения Set start) - this.fСтранаРождения = value; - // *** Start programmer edit section *** (Медведь.СтранаРождения Set end) - - // *** End programmer edit section *** (Медведь.СтранаРождения Set end) - } - } - - /// - /// Медведь - ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. - /// - // *** Start programmer edit section *** (Медведь.Берлога CustomAttributes) - - // *** End programmer edit section *** (Медведь.Берлога CustomAttributes) - public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога Берлога - { - get - { - // *** Start programmer edit section *** (Медведь.Берлога Get start) - - // *** End programmer edit section *** (Медведь.Берлога Get start) - if ((this.fБерлога == null)) - { - this.fБерлога = new NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога(this); - } - NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога result = this.fБерлога; - // *** Start programmer edit section *** (Медведь.Берлога Get end) - - // *** End programmer edit section *** (Медведь.Берлога Get end) - return result; - } - set - { - // *** Start programmer edit section *** (Медведь.Берлога Set start) - - // *** End programmer edit section *** (Медведь.Берлога Set start) - this.fБерлога = value; - // *** Start programmer edit section *** (Медведь.Берлога Set end) - - // *** End programmer edit section *** (Медведь.Берлога Set end) - } - } - - /// - /// Class views container. - /// - public class Views - { - - /// - /// Представление для работы тестов на загрузку объектов. - /// - public static ICSSoft.STORMNET.View LoadTestView - { - get - { - return ICSSoft.STORMNET.Information.GetView("LoadTestView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// Представление для работы теста по использованию порядкового номера. - /// - public static ICSSoft.STORMNET.View OrderNumberTest - { - get - { - return ICSSoft.STORMNET.Information.GetView("OrderNumberTest", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьE" view. - /// - public static ICSSoft.STORMNET.View МедведьE - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьE", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьL" view. - /// - public static ICSSoft.STORMNET.View МедведьL - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьL", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - - /// - /// "МедведьShort" view. - /// - public static ICSSoft.STORMNET.View МедведьShort - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьShort", 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.Медведь)); - } - } - - /// - /// "МедведьСДелейломИВычислимымСвойством" view. - /// - public static ICSSoft.STORMNET.View МедведьСДелейломИВычислимымСвойством - { - get - { - return ICSSoft.STORMNET.Information.GetView("МедведьСДелейломИВычислимымСвойством", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); - } - } - } - - /// - /// Audit class settings. - /// - public class AuditSettings - { - - /// - /// Включён ли аудит для класса. - /// - public static bool AuditEnabled = true; - - /// - /// Использовать имя представления для аудита по умолчанию. - /// - public static bool UseDefaultView = true; - - /// - /// Включён ли аудит операции чтения. - /// - public static bool SelectAudit = true; - - /// - /// Имя представления для аудирования операции чтения. - /// - public static string SelectAuditViewName = "AuditView"; - - /// - /// Включён ли аудит операции создания. - /// - public static bool InsertAudit = true; - - /// - /// Имя представления для аудирования операции создания. - /// - public static string InsertAuditViewName = "AuditView"; - - /// - /// Включён ли аудит операции изменения. - /// - public static bool UpdateAudit = true; - - /// - /// Имя представления для аудирования операции изменения. - /// - public static string UpdateAuditViewName = "AuditView"; - - /// - /// Включён ли аудит операции удаления. - /// - public static bool DeleteAudit = true; - - /// - /// Имя представления для аудирования операции удаления. - /// - public static string DeleteAuditViewName = "AuditView"; - - /// - /// Путь к форме просмотра результатов аудита. - /// - public static string FormUrl = ""; - - /// - /// Режим записи данных аудита (синхронный или асинхронный). - /// - public static ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode WriteMode = ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode.Synchronous; - - /// - /// Максимальная длина сохраняемого значения поля (если 0, то строка обрезаться не будет). - /// - public static int PrunningLength = 0; - - /// - /// Показывать ли пользователям в изменениях первичные ключи. - /// - public static bool ShowPrimaryKey = false; - - /// - /// Сохранять ли старое значение. - /// - public static bool KeepOldValue = true; - - /// - /// Сжимать ли сохраняемые значения. - /// - public static bool Compress = false; - - /// - /// Сохранять ли все значения атрибутов, а не только изменяемые. - /// - public static bool KeepAllValues = false; - } - } -} - +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NewPlatform.Flexberry.ORM.ODataService.Tests +{ + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business.Audit; + using ICSSoft.STORMNET.Business.Audit.Objects; + + + // *** Start programmer edit section *** (Using statements) + + // *** End programmer edit section *** (Using statements) + + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь CustomAttributes) + + // *** End programmer edit section *** (Медведь CustomAttributes) + [BusinessServer("NewPlatform.Flexberry.ORM.ODataService.Tests.BearBS, NewPlatform.Flexberry.ORM.OD" + + "ataService.Tests.BusinessServers", ICSSoft.STORMNET.Business.DataServiceObjectEvents.OnAllEvents)] + [AutoAltered()] + [AccessType(ICSSoft.STORMNET.AccessType.none)] + [View("LoadTestView", new string[] { + "ЛесОбитания", + "ЛесОбитания.Заповедник", + "Мама", + "Мама.ЦветГлаз", + "Вес"})] + [AssociatedDetailViewAttribute("LoadTestView", "Берлога", "LoadTestView", true, "", "", true, new string[] { + ""})] + [View("OrderNumberTest", new string[] { + "ПорядковыйНомер", + "ЛесОбитания"})] + [View("МедведьE", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "Мама as \'Мама\'", + "Мама.ЦветГлаз as \'Цвет глаз\'", + "Мама.Вес", + "Папа as \'Папа\'", + "Папа.ЦветГлаз as \'Цвет глаз\'", + "Папа.Вес", + "ЛесОбитания as \'Лес обитания\'", + "ЛесОбитания.Название as \'Название\'", + "ПолеБС", + "СтранаРождения", + "СтранаРождения.Название"})] + [AssociatedDetailViewAttribute("МедведьE", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { + ""})] + [View("МедведьL", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "Мама.ЦветГлаз as \'Цвет глаз\'", + "Папа.ЦветГлаз as \'Цвет глаз\'", + "ЛесОбитания.Название as \'Название\'"})] + [View("МедведьShort", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'"})] + [View("МедведьUpdateView", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "ПолеБС"})] + [View("МедведьСДелейломИВычислимымСвойством", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "Мама as \'Мама\'", + "Мама.ЦветГлаз as \'Цвет глаз\'", + "Папа as \'Папа\'", + "Папа.ЦветГлаз as \'Цвет глаз\'", + "ЛесОбитания as \'Лес обитания\'", + "ЛесОбитания.Название as \'Название\'", + "МедведьСтрокой"})] + [AssociatedDetailViewAttribute("МедведьСДелейломИВычислимымСвойством", "Берлога", "БерлогаE", true, "", "Берлога", true, new string[] { + ""})] + public class Медведь : ICSSoft.STORMNET.DataObject, IDataObjectWithAuditFields + { + + private int fВес; + + private ICSSoft.STORMNET.UserDataTypes.NullableDateTime fДатаРождения; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.tПол fПол; + + private string fПолеБС; + + private int fПорядковыйНомер; + + private string fЦветГлаз; + + private System.Nullable fCreateTime; + + private string fCreator; + + private string fEditor; + + private System.Nullable fEditTime; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Лес fЛесОбитания; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМама; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fПапа; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.Страна fСтранаРождения; + + private NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога fБерлога; + + // *** Start programmer edit section *** (Медведь CustomMembers) + + // *** End programmer edit section *** (Медведь CustomMembers) + + + /// + /// Вес. + /// + // *** Start programmer edit section *** (Медведь.Вес CustomAttributes) + + // *** End programmer edit section *** (Медведь.Вес CustomAttributes) + public virtual int Вес + { + get + { + // *** Start programmer edit section *** (Медведь.Вес Get start) + + // *** End programmer edit section *** (Медведь.Вес Get start) + int result = this.fВес; + // *** Start programmer edit section *** (Медведь.Вес Get end) + + // *** End programmer edit section *** (Медведь.Вес Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Вес Set start) + + // *** End programmer edit section *** (Медведь.Вес Set start) + this.fВес = value; + // *** Start programmer edit section *** (Медведь.Вес Set end) + + // *** End programmer edit section *** (Медведь.Вес Set end) + } + } + + /// + /// ВычислимоеПоле. + /// + // *** Start programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) + + // *** End programmer edit section *** (Медведь.ВычислимоеПоле CustomAttributes) + [ICSSoft.STORMNET.NotStored()] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "@ПорядковыйНомер@ + @Вес@")] + public virtual int ВычислимоеПоле + { + get + { + // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Get) + return 0; + // *** End programmer edit section *** (Медведь.ВычислимоеПоле Get) + } + set + { + // *** Start programmer edit section *** (Медведь.ВычислимоеПоле Set) + + // *** End programmer edit section *** (Медведь.ВычислимоеПоле Set) + } + } + + /// + /// ДатаРождения. + /// + // *** Start programmer edit section *** (Медведь.ДатаРождения CustomAttributes) + + // *** End programmer edit section *** (Медведь.ДатаРождения CustomAttributes) + public virtual ICSSoft.STORMNET.UserDataTypes.NullableDateTime ДатаРождения + { + get + { + // *** Start programmer edit section *** (Медведь.ДатаРождения Get start) + + // *** End programmer edit section *** (Медведь.ДатаРождения Get start) + ICSSoft.STORMNET.UserDataTypes.NullableDateTime result = this.fДатаРождения; + // *** Start programmer edit section *** (Медведь.ДатаРождения Get end) + + // *** End programmer edit section *** (Медведь.ДатаРождения Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ДатаРождения Set start) + + // *** End programmer edit section *** (Медведь.ДатаРождения Set start) + this.fДатаРождения = value; + // *** Start programmer edit section *** (Медведь.ДатаРождения Set end) + + // *** End programmer edit section *** (Медведь.ДатаРождения Set end) + } + } + + /// + /// МедведьСтрокой. + /// + // *** Start programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) + + // *** End programmer edit section *** (Медведь.МедведьСтрокой CustomAttributes) + [ICSSoft.STORMNET.NotStored()] + [StrLen(255)] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "\'ПорядковыйНомер:\' + @ПорядковыйНомер@ + \", Цвет глаз мамы:\" + isnull(@Мама.ЦветГ" + + "лаз@,\'\')")] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.PostgresDataService), "\'ПорядковыйНомер:\' || @ПорядковыйНомер@ || \", Цвет глаз мамы:\" || coalesce(@Мама.ЦветГ" + + "лаз@,\'\')")] + public virtual string МедведьСтрокой + { + get + { + // *** Start programmer edit section *** (Медведь.МедведьСтрокой Get) + return null; + // *** End programmer edit section *** (Медведь.МедведьСтрокой Get) + } + set + { + // *** Start programmer edit section *** (Медведь.МедведьСтрокой Set) + + // *** End programmer edit section *** (Медведь.МедведьСтрокой Set) + } + } + + /// + /// Пол. + /// + // *** Start programmer edit section *** (Медведь.Пол CustomAttributes) + + // *** End programmer edit section *** (Медведь.Пол CustomAttributes) + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.tПол Пол + { + get + { + // *** Start programmer edit section *** (Медведь.Пол Get start) + + // *** End programmer edit section *** (Медведь.Пол Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.tПол result = this.fПол; + // *** Start programmer edit section *** (Медведь.Пол Get end) + + // *** End programmer edit section *** (Медведь.Пол Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Пол Set start) + + // *** End programmer edit section *** (Медведь.Пол Set start) + this.fПол = value; + // *** Start programmer edit section *** (Медведь.Пол Set end) + + // *** End programmer edit section *** (Медведь.Пол Set end) + } + } + + /// + /// ПолеБС. + /// + // *** Start programmer edit section *** (Медведь.ПолеБС CustomAttributes) + + // *** End programmer edit section *** (Медведь.ПолеБС CustomAttributes) + [StrLen(255)] + public virtual string ПолеБС + { + get + { + // *** Start programmer edit section *** (Медведь.ПолеБС Get start) + + // *** End programmer edit section *** (Медведь.ПолеБС Get start) + string result = this.fПолеБС; + // *** Start programmer edit section *** (Медведь.ПолеБС Get end) + + // *** End programmer edit section *** (Медведь.ПолеБС Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ПолеБС Set start) + + // *** End programmer edit section *** (Медведь.ПолеБС Set start) + this.fПолеБС = value; + // *** Start programmer edit section *** (Медведь.ПолеБС Set end) + + // *** End programmer edit section *** (Медведь.ПолеБС Set end) + } + } + + /// + /// ПорядковыйНомер. + /// + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер CustomAttributes) + public virtual int ПорядковыйНомер + { + get + { + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get start) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get start) + int result = this.fПорядковыйНомер; + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Get end) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set start) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set start) + this.fПорядковыйНомер = value; + // *** Start programmer edit section *** (Медведь.ПорядковыйНомер Set end) + + // *** End programmer edit section *** (Медведь.ПорядковыйНомер Set end) + } + } + + /// + /// ЦветГлаз. + /// + // *** Start programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) + + // *** End programmer edit section *** (Медведь.ЦветГлаз CustomAttributes) + [StrLen(255)] + public virtual string ЦветГлаз + { + get + { + // *** Start programmer edit section *** (Медведь.ЦветГлаз Get start) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Get start) + string result = this.fЦветГлаз; + // *** Start programmer edit section *** (Медведь.ЦветГлаз Get end) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ЦветГлаз Set start) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Set start) + this.fЦветГлаз = value; + // *** Start programmer edit section *** (Медведь.ЦветГлаз Set end) + + // *** End programmer edit section *** (Медведь.ЦветГлаз Set end) + } + } + + /// + /// Время создания объекта. + /// + // *** Start programmer edit section *** (Медведь.CreateTime CustomAttributes) + + // *** End programmer edit section *** (Медведь.CreateTime CustomAttributes) + public virtual System.Nullable CreateTime + { + get + { + // *** Start programmer edit section *** (Медведь.CreateTime Get start) + + // *** End programmer edit section *** (Медведь.CreateTime Get start) + System.Nullable result = this.fCreateTime; + // *** Start programmer edit section *** (Медведь.CreateTime Get end) + + // *** End programmer edit section *** (Медведь.CreateTime Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.CreateTime Set start) + + // *** End programmer edit section *** (Медведь.CreateTime Set start) + this.fCreateTime = value; + // *** Start programmer edit section *** (Медведь.CreateTime Set end) + + // *** End programmer edit section *** (Медведь.CreateTime Set end) + } + } + + /// + /// Создатель объекта. + /// + // *** Start programmer edit section *** (Медведь.Creator CustomAttributes) + + // *** End programmer edit section *** (Медведь.Creator CustomAttributes) + [StrLen(255)] + public virtual string Creator + { + get + { + // *** Start programmer edit section *** (Медведь.Creator Get start) + + // *** End programmer edit section *** (Медведь.Creator Get start) + string result = this.fCreator; + // *** Start programmer edit section *** (Медведь.Creator Get end) + + // *** End programmer edit section *** (Медведь.Creator Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Creator Set start) + + // *** End programmer edit section *** (Медведь.Creator Set start) + this.fCreator = value; + // *** Start programmer edit section *** (Медведь.Creator Set end) + + // *** End programmer edit section *** (Медведь.Creator Set end) + } + } + + /// + /// Последний редактор объекта. + /// + // *** Start programmer edit section *** (Медведь.Editor CustomAttributes) + + // *** End programmer edit section *** (Медведь.Editor CustomAttributes) + [StrLen(255)] + public virtual string Editor + { + get + { + // *** Start programmer edit section *** (Медведь.Editor Get start) + + // *** End programmer edit section *** (Медведь.Editor Get start) + string result = this.fEditor; + // *** Start programmer edit section *** (Медведь.Editor Get end) + + // *** End programmer edit section *** (Медведь.Editor Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Editor Set start) + + // *** End programmer edit section *** (Медведь.Editor Set start) + this.fEditor = value; + // *** Start programmer edit section *** (Медведь.Editor Set end) + + // *** End programmer edit section *** (Медведь.Editor Set end) + } + } + + /// + /// Время последнего редактирования объекта. + /// + // *** Start programmer edit section *** (Медведь.EditTime CustomAttributes) + + // *** End programmer edit section *** (Медведь.EditTime CustomAttributes) + public virtual System.Nullable EditTime + { + get + { + // *** Start programmer edit section *** (Медведь.EditTime Get start) + + // *** End programmer edit section *** (Медведь.EditTime Get start) + System.Nullable result = this.fEditTime; + // *** Start programmer edit section *** (Медведь.EditTime Get end) + + // *** End programmer edit section *** (Медведь.EditTime Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.EditTime Set start) + + // *** End programmer edit section *** (Медведь.EditTime Set start) + this.fEditTime = value; + // *** Start programmer edit section *** (Медведь.EditTime Set end) + + // *** End programmer edit section *** (Медведь.EditTime Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) + + // *** End programmer edit section *** (Медведь.ЛесОбитания CustomAttributes) + [PropertyStorage(new string[] { + "ЛесОбитания"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Лес ЛесОбитания + { + get + { + // *** Start programmer edit section *** (Медведь.ЛесОбитания Get start) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Лес result = this.fЛесОбитания; + // *** Start programmer edit section *** (Медведь.ЛесОбитания Get end) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.ЛесОбитания Set start) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Set start) + this.fЛесОбитания = value; + // *** Start programmer edit section *** (Медведь.ЛесОбитания Set end) + + // *** End programmer edit section *** (Медведь.ЛесОбитания Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.Мама CustomAttributes) + + // *** End programmer edit section *** (Медведь.Мама CustomAttributes) + [PropertyStorage(new string[] { + "Мама"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Мама + { + get + { + // *** Start programmer edit section *** (Медведь.Мама Get start) + + // *** End programmer edit section *** (Медведь.Мама Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fМама; + // *** Start programmer edit section *** (Медведь.Мама Get end) + + // *** End programmer edit section *** (Медведь.Мама Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Мама Set start) + + // *** End programmer edit section *** (Медведь.Мама Set start) + this.fМама = value; + // *** Start programmer edit section *** (Медведь.Мама Set end) + + // *** End programmer edit section *** (Медведь.Мама Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.Папа CustomAttributes) + + // *** End programmer edit section *** (Медведь.Папа CustomAttributes) + [PropertyStorage(new string[] { + "Папа"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь Папа + { + get + { + // *** Start programmer edit section *** (Медведь.Папа Get start) + + // *** End programmer edit section *** (Медведь.Папа Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь result = this.fПапа; + // *** Start programmer edit section *** (Медведь.Папа Get end) + + // *** End programmer edit section *** (Медведь.Папа Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Папа Set start) + + // *** End programmer edit section *** (Медведь.Папа Set start) + this.fПапа = value; + // *** Start programmer edit section *** (Медведь.Папа Set end) + + // *** End programmer edit section *** (Медведь.Папа Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.СтранаРождения CustomAttributes) + + // *** End programmer edit section *** (Медведь.СтранаРождения CustomAttributes) + [PropertyStorage(new string[] { + "Страна"})] + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.Страна СтранаРождения + { + get + { + // *** Start programmer edit section *** (Медведь.СтранаРождения Get start) + + // *** End programmer edit section *** (Медведь.СтранаРождения Get start) + NewPlatform.Flexberry.ORM.ODataService.Tests.Страна result = this.fСтранаРождения; + // *** Start programmer edit section *** (Медведь.СтранаРождения Get end) + + // *** End programmer edit section *** (Медведь.СтранаРождения Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.СтранаРождения Set start) + + // *** End programmer edit section *** (Медведь.СтранаРождения Set start) + this.fСтранаРождения = value; + // *** Start programmer edit section *** (Медведь.СтранаРождения Set end) + + // *** End programmer edit section *** (Медведь.СтранаРождения Set end) + } + } + + /// + /// Медведь + ///Аудит включен для тестирования оффлайн-сервиса аудита в OData. + /// + // *** Start programmer edit section *** (Медведь.Берлога CustomAttributes) + + // *** End programmer edit section *** (Медведь.Берлога CustomAttributes) + public virtual NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога Берлога + { + get + { + // *** Start programmer edit section *** (Медведь.Берлога Get start) + + // *** End programmer edit section *** (Медведь.Берлога Get start) + if ((this.fБерлога == null)) + { + this.fБерлога = new NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога(this); + } + NewPlatform.Flexberry.ORM.ODataService.Tests.DetailArrayOfБерлога result = this.fБерлога; + // *** Start programmer edit section *** (Медведь.Берлога Get end) + + // *** End programmer edit section *** (Медведь.Берлога Get end) + return result; + } + set + { + // *** Start programmer edit section *** (Медведь.Берлога Set start) + + // *** End programmer edit section *** (Медведь.Берлога Set start) + this.fБерлога = value; + // *** Start programmer edit section *** (Медведь.Берлога Set end) + + // *** End programmer edit section *** (Медведь.Берлога Set end) + } + } + + /// + /// Class views container. + /// + public class Views + { + + /// + /// Представление для работы тестов на загрузку объектов. + /// + public static ICSSoft.STORMNET.View LoadTestView + { + get + { + return ICSSoft.STORMNET.Information.GetView("LoadTestView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// Представление для работы теста по использованию порядкового номера. + /// + public static ICSSoft.STORMNET.View OrderNumberTest + { + get + { + return ICSSoft.STORMNET.Information.GetView("OrderNumberTest", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьE" view. + /// + public static ICSSoft.STORMNET.View МедведьE + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьE", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьL" view. + /// + public static ICSSoft.STORMNET.View МедведьL + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьL", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + + /// + /// "МедведьShort" view. + /// + public static ICSSoft.STORMNET.View МедведьShort + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьShort", 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.Медведь)); + } + } + + /// + /// "МедведьСДелейломИВычислимымСвойством" view. + /// + public static ICSSoft.STORMNET.View МедведьСДелейломИВычислимымСвойством + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьСДелейломИВычислимымСвойством", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + } + + /// + /// Audit class settings. + /// + public class AuditSettings + { + + /// + /// Включён ли аудит для класса. + /// + public static bool AuditEnabled = true; + + /// + /// Использовать имя представления для аудита по умолчанию. + /// + public static bool UseDefaultView = true; + + /// + /// Включён ли аудит операции чтения. + /// + public static bool SelectAudit = true; + + /// + /// Имя представления для аудирования операции чтения. + /// + public static string SelectAuditViewName = "AuditView"; + + /// + /// Включён ли аудит операции создания. + /// + public static bool InsertAudit = true; + + /// + /// Имя представления для аудирования операции создания. + /// + public static string InsertAuditViewName = "AuditView"; + + /// + /// Включён ли аудит операции изменения. + /// + public static bool UpdateAudit = true; + + /// + /// Имя представления для аудирования операции изменения. + /// + public static string UpdateAuditViewName = "AuditView"; + + /// + /// Включён ли аудит операции удаления. + /// + public static bool DeleteAudit = true; + + /// + /// Имя представления для аудирования операции удаления. + /// + public static string DeleteAuditViewName = "AuditView"; + + /// + /// Путь к форме просмотра результатов аудита. + /// + public static string FormUrl = ""; + + /// + /// Режим записи данных аудита (синхронный или асинхронный). + /// + public static ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode WriteMode = ICSSoft.STORMNET.Business.Audit.Objects.tWriteMode.Synchronous; + + /// + /// Максимальная длина сохраняемого значения поля (если 0, то строка обрезаться не будет). + /// + public static int PrunningLength = 0; + + /// + /// Показывать ли пользователям в изменениях первичные ключи. + /// + public static bool ShowPrimaryKey = false; + + /// + /// Сохранять ли старое значение. + /// + public static bool KeepOldValue = true; + + /// + /// Сжимать ли сохраняемые значения. + /// + public static bool Compress = false; + + /// + /// Сохранять ли все значения атрибутов, а не только изменяемые. + /// + public static bool KeepAllValues = false; + } + } +} + From d9e430d065ebcce5632834da155c223f21672936 Mon Sep 17 00:00:00 2001 From: Ilya Naidanov Date: Mon, 11 Mar 2024 12:21:49 +0500 Subject: [PATCH 18/18] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D1=91=D0=BD=20crp=20(UpdateViews)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tform.Flexberry.ORM.ODataService.Tests.crp | 1196 +++++++++-------- 1 file changed, 599 insertions(+), 597 deletions(-) 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 3bbb0984..b149d758 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,1316 +1,1318 @@ - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - + + + + + + + + - + - + - + - - + + - + - + - + - - + + - + - + - + - + - - - + + + - + - + - + - + - - + + + - + - + + - + + + + + + + + + + + + + + + - + - + - + - - - - - + - + - - - + + - + - - + - + + - + - + + - - - - + - + - + - + - + - + + + + - + - + - - - + - + - - - - + - + + + - + - + + - - - - - - - - - - + + + + - + + + + - + - + + + + + + - + + + - + + - - + + - + + - + + + + + + + + + + + - - + - + - - + - + - + + - + - + + + - + - - - + - + - - - + - + - - + + + - + - - + - + - + - + - + - + + + + - + - + + + + + + + + + + + + - + - - + - + - + - + - + - + - + - - - - - - - - + + + + - + - + + + + - + - + - - + - + - + - + + + + + + + + - + + - + - + - + - + - + + + - + - - - + - + - - + - + - + - - + - + - - - + - - - + - + - - + - + - + + - - - + - + - + - + + - + - + - + - + - + + + + - + - + + + - + + + - + - - - - + - + - + - + - - - - - + - + - + + + + - + - - + - + - + + + - - - + - + - + - - - - - - - - - - - - - + + - - - - - - - - + - + - - - - - - - + - - + - + - - - - - - + - - - + - + - - - + - + - - - - - - - - - - - - + - - - + - + - - - - - - - - - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + + - + + + + + + + + - + + + - + + - + + + + + + + + + + + + + + + + + - + + - + + + + + + + + + + + + - + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - - + + @@ -1330,4 +1332,4 @@ - + \ No newline at end of file