diff --git a/CHANGELOG.md b/CHANGELOG.md
index a62c646e..714342e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests).
+2. `masterLightLoadTypes` and `masterLightLoadAllTypes` parameters of `DefaultDataObjectEdmModelBuilder` class. They allow to change loading mode of masters during OData update requests.
### Changed
1. Updated Flexberry ORM up to 7.2.0-beta01.
diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs
index e5b682bd..1fd72656 100644
--- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs
+++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs
@@ -1,4 +1,4 @@
-namespace NewPlatform.Flexberry.ORM.ODataService.Controllers
+namespace NewPlatform.Flexberry.ORM.ODataService.Controllers
{
using System;
using System.Collections.Generic;
@@ -23,13 +23,16 @@
#if NETFRAMEWORK
using System.Net.Http.Formatting;
+ using System.Web;
using System.Web.Http;
using System.Web.Http.Results;
using System.Web.Http.Validation;
using NewPlatform.Flexberry.ORM.ODataService.Events;
using NewPlatform.Flexberry.ORM.ODataService.Handlers;
+ using Newtonsoft.Json.Linq;
#endif
#if NETSTANDARD
+ using System.Data;
using Microsoft.AspNet.OData.Formatter;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -676,7 +679,7 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key)
{
// Создадим объект данных по пришедшей сущности.
// В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней.
- DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true);
+ DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs);
for (int i = 0; i < objs.Count; i++)
{
@@ -739,76 +742,113 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key)
}
///
- /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый.
+ /// Загрузить существующий объект данных в облегчённом варианте (только __PrimaryKey), используя информацию из EdmEntity.
///
- /// Тип объекта, не может быть null.
- /// Значение ключа.
- /// Представление для загрузки объекта.
+ /// EdmEntity, который будет использован для получения объекта данных.
/// Объект данных.
- private DataObject ReturnDataObject(Type objType, object keyValue, View view)
+ private DataObject LightLoadDataObject(EdmEntityObject edmEntity)
{
- if (objType == null)
+ if (edmEntity == null)
{
- throw new ArgumentNullException(nameof(objType));
+ throw new ArgumentNullException(nameof(edmEntity));
}
- if (keyValue != null)
- {
- DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue);
+ Type masterType = _model.GetDataObjectType(edmEntity);
+ object masterKey = GetKey(edmEntity);
+ View view = new View(new ViewAttribute("dynView", new string[] { Information.ExtractPropertyPath(x => x.__PrimaryKey) }), masterType);
+
+ return LoadDataObject(masterType, masterKey, view);
+ }
- if (dataObjectFromCache != null)
+ ///
+ /// Загрузить существующий объект данных.
+ ///
+ /// Тип загружаемого объекта.
+ /// Первичный ключ загружаемого объекта.
+ /// Представление, по которому будет загружен объект.
+ /// Объект данных.
+ private DataObject LoadDataObject(Type objType, object keyValue, View view)
+ {
+ DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue);
+
+ if (dataObjectFromCache != null)
+ {
+ // Если объект не новый и не загружен целиком (начиная с ORM@5.1.0-beta15).
+ if (dataObjectFromCache.GetStatus(false) == ObjectStatus.UnAltered
+ && dataObjectFromCache.GetLoadingState() != LoadingState.Loaded)
{
- // Если объект не новый и не загружен целиком (начиная с 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)))
{
- // Для обратной совместимости сравним перечень загруженных свойств и свойств в представлении.
- /* Данный код срабатывает, например, если в кэше был объект, который загрузился только на уровне первичного ключа.
- *
- * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В.
- * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В.
- * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние 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);
+ // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения.
+ 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);
- }
+ 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)
+ 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())
{
- DataObject dataObject = dobjs[0];
- if (lightViewDetails.Any())
- {
- // Дочитаем детейлы, чтобы в бизнес-серверах эти данные уже были. Детейлы с изменёнными состояниями будут пропущены из зачитки.
- _dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, DataObjectCache);
- }
+ // Дочитаем детейлы, чтобы в бизнес-серверах эти данные уже были. Детейлы с изменёнными состояниями будут пропущены из зачитки.
+ _dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, DataObjectCache);
+ }
+
+ return dataObject;
+ }
+
+ return null;
+ }
+ ///
+ /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению по умолчанию, иначе - создаётся новый.
+ ///
+ /// Тип объекта, не может быть null.
+ /// Значение ключа.
+ /// Объект данных.
+ private DataObject ReturnDataObject(Type objType, object keyValue)
+ {
+ if (objType == null)
+ {
+ throw new ArgumentNullException(nameof(objType));
+ }
+
+ if (keyValue != null)
+ {
+ View view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType);
+ DataObject dataObject = LoadDataObject(objType, keyValue, view);
+ if (dataObject != null)
+ {
return dataObject;
}
}
@@ -848,8 +888,31 @@ private static void AddObjectToUpdate(List objsToUpdate, DataObject
objsToUpdate.Insert(0, dataObject); // Добавляем объект в начало списка.
}
- }
}
+ }
+
+ ///
+ /// Получить значение ключа у указанной сущности.
+ ///
+ /// Сущность.
+ /// Значение ключа.
+ private object GetKey(EdmEntityObject edmEntity)
+ {
+ if (edmEntity == null)
+ {
+ throw new ArgumentNullException(nameof(edmEntity), $"{nameof(edmEntity)} can not be null.");
+ }
+
+ object key;
+
+ // Получим значение ключа.
+ IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
+ IEnumerable entityProps = entityType.Properties();
+ var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName);
+ edmEntity.TryGetPropertyValue(keyProperty.Name, out key);
+
+ return key;
+ }
///
/// Построение объекта данных по сущности OData.
@@ -858,45 +921,21 @@ private static void AddObjectToUpdate(List objsToUpdate, DataObject
/// Значение ключевого поля сущности.
/// Список объектов для обновления.
/// Признак, что объект добавляется в конец списка обновления.
- /// Использовать представление для обновления (вместо представления по умолчанию).
/// Объект данных.
- private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false)
+ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false)
{
if (edmEntity == null)
{
return null;
}
- // Значение свойства.
- object value;
-
- // Получим значение ключа.
- IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
- IEnumerable entityProps = entityType.Properties().ToList();
- var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName);
- if (key != null)
- {
- value = key;
- }
- else
- {
- edmEntity.TryGetPropertyValue(keyProperty.Name, out value);
- }
+ key = key ?? GetKey(edmEntity);
// Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST.
// Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса.
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);
+ DataObject obj = ReturnDataObject(objType, key);
// Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом.
AddObjectToUpdate(dObjs, obj, endObject);
@@ -906,6 +945,8 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames();
// Обрабатываем агрегатор первым.
+ IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
+ IEnumerable entityProps = entityType.Properties().ToList();
List changedProps = entityProps
.Where(ep => changedPropNames.Contains(ep.Name))
.OrderBy(ep => ep.Name != agregatorPropertyName)
@@ -931,22 +972,38 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
// Обработка мастеров и детейлов.
if (prop is EdmNavigationProperty navProp)
{
- edmEntity.TryGetPropertyValue(prop.Name, out value);
-
EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity();
// Обработка мастеров.
if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne)
{
+ object value;
+ edmEntity.TryGetPropertyValue(prop.Name, out value);
+
if (value is EdmEntityObject edmMaster)
{
// Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом.
bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName);
- DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView);
+ bool masterOwnPropsUpdated = edmMaster.GetChangedPropertyNames().Any(propName => propName != _model.KeyPropertyName);
+ bool isAggregator = dataObjectPropName == agregatorPropertyName;
+ DataObject master = null;
+
+ Type objectType = _model.GetDataObjectType(edmEntity);
+ bool masterLightLoad = _model.IsMasterLightLoad(objectType);
+
+ if (masterLightLoad && !masterOwnPropsUpdated && !isAggregator)
+ {
+ master = LightLoadDataObject(edmMaster);
+ //AddObjectToUpdate(dObjs, master, insertIntoEnd);
+ }
+ else
+ {
+ master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd);
+ }
Information.SetPropValueByName(obj, dataObjectPropName, master);
- if (dataObjectPropName == agregatorPropertyName)
+ if (isAggregator)
{
master.AddDetail(obj);
@@ -969,6 +1026,9 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
{
DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName);
+ object value;
+ edmEntity.TryGetPropertyValue(prop.Name, out value);
+
if (value is EdmEntityObjectCollection coll)
{
if (coll != null && coll.Count > 0)
@@ -979,8 +1039,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
(EdmEntityObject)edmEnt,
null,
dObjs,
- true,
- useUpdateView);
+ true);
if (det.__PrimaryKey == null)
{
@@ -1002,11 +1061,13 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
else
{
// Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj).
- if (prop.Name != keyProperty.Name)
+ if (prop.Name != _model.KeyPropertyName)
{
- Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName);
+ object value;
edmEntity.TryGetPropertyValue(prop.Name, out value);
+ Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName);
+
// Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств,
// значит свойство файловое, и его нужно обработать особым образом.
if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType))
diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs
index dcfae14f..95586d2a 100644
--- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs
+++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs
@@ -528,6 +528,13 @@ public View GetDataObjectUpdateView(Type dataObjectType)
return _metadata[dataObjectType].UpdateView?.Clone();
}
+ ///
+ /// Возвращает информацию, должны ли мастера объекта загружаться в экономном режиме (только __PrimaryKey).
+ ///
+ /// Тип объекта данных.
+ /// Мастера должны загружаться экономно.
+ public bool IsMasterLightLoad(Type dataObjectType) => _metadata == null ? false : _metadata[dataObjectType]?.MasterLightLoad ?? false;
+
///
/// Получает список зарегистрированных в модели типов по списку имён типов.
///
diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs
index 4e4219a1..e5c03947 100644
--- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs
+++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs
@@ -36,6 +36,11 @@ public sealed class DataObjectEdmTypeSettings
///
public View UpdateView { get; set; }
+ ///
+ /// Whether to load object masters in LightLoaded state (load only primary key).
+ ///
+ public bool MasterLightLoad { get; set; }
+
///
/// The list of exposed details.
///
@@ -56,4 +61,4 @@ public sealed class DataObjectEdmTypeSettings
///
public IDictionary PseudoDetailProperties { get; } = new Dictionary();
}
-}
\ No newline at end of file
+}
diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs
index 3e6ed225..fbe317d4 100644
--- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs
+++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs
@@ -74,6 +74,16 @@ public class DefaultDataObjectEdmModelBuilder : IDataObjectEdmModelBuilder
///
private Dictionary UpdateViews { get; set; }
+ ///
+ /// Types for which masters should be light-loaded on updates (load only __PrimaryKey).
+ ///
+ private IEnumerable MasterLightLoadTypes { get; set; }
+
+ ///
+ /// Whether to load all masters in light-loaded mode on updates (load only __PrimaryKey).
+ ///
+ private bool MasterLightLoadAllTypes { get; set; }
+
private readonly PropertyInfo _keyProperty = Information.ExtractPropertyInfo(n => n.__PrimaryKey);
///
@@ -88,7 +98,9 @@ public DefaultDataObjectEdmModelBuilder(
bool useNamespaceInEntitySetName = true,
PseudoDetailDefinitions pseudoDetailDefinitions = null,
Dictionary additionalMapping = null,
- IEnumerable> updateViews = null)
+ IEnumerable> updateViews = null,
+ IEnumerable masterLightLoadTypes = null,
+ bool masterLightLoadAllTypes = false)
{
_searchAssemblies = searchAssemblies ?? throw new ArgumentNullException(nameof(searchAssemblies), "Contract assertion not met: searchAssemblies != null");
_useNamespaceInEntitySetName = useNamespaceInEntitySetName;
@@ -105,6 +117,18 @@ public DefaultDataObjectEdmModelBuilder(
{
SetUpdateView(updateViews);
}
+
+ if (masterLightLoadTypes != null)
+ {
+ SetMasterLightLoadTypes(masterLightLoadTypes);
+
+ if (masterLightLoadAllTypes)
+ {
+ throw new ArgumentException("Parameters masterLightLoadAllTypes and masterLightLoadTypes can not be used together in DefaultDataObjectEdmModelBuilder.");
+ }
+ }
+
+ this.MasterLightLoadAllTypes = masterLightLoadAllTypes;
}
///
@@ -215,7 +239,12 @@ private void SetUpdateView(Type dataObjectType, View updateView)
{
if (!dataObjectType.IsSubclassOf(typeof(DataObject)))
{
- throw new ArgumentException("Update view can be set only for a DataObject.", nameof(dataObjectType));
+ throw new ArgumentException($"Update view can be set only for a DataObject. Current type is {dataObjectType}", nameof(dataObjectType));
+ }
+
+ if (dataObjectType is null)
+ {
+ throw new ArgumentException("dataObjectType can not be null.", nameof(dataObjectType));
}
if (updateView is null)
@@ -232,6 +261,26 @@ private void SetUpdateView(Type dataObjectType, View updateView)
UpdateViews[dataObjectType] = updateView;
}
+ ///
+ /// Sets DataObject types for which masters will be light-loaded on updates (load only __PrimaryKey).
+ ///
+ /// Types for which masters should be light-loaded.
+ private void SetMasterLightLoadTypes(IEnumerable masterLightLoadTypes)
+ {
+ if (masterLightLoadTypes != null)
+ {
+ foreach (Type type in masterLightLoadTypes)
+ {
+ if (!type.IsSubclassOf(typeof(DataObject)))
+ {
+ throw new ArgumentException("MasterLightLoad option can be set only for a DataObject.", nameof(masterLightLoadTypes));
+ }
+ }
+
+ MasterLightLoadTypes = masterLightLoadTypes;
+ }
+ }
+
///
/// Adds the property for exposing.
///
@@ -310,6 +359,7 @@ private void AddDataObjectWithHierarchy(DataObjectEdmMetadata meta, Type dataObj
CollectionName = EntitySetNameBuilder(dataObjectType),
DefaultView = DynamicView.Create(dataObjectType, null).View,
UpdateView = updateView,
+ MasterLightLoad = MasterLightLoadAllTypes || (MasterLightLoadTypes?.Contains(dataObjectType) ?? false),
};
AddProperties(dataObjectType, typeSettings);
@@ -458,4 +508,4 @@ private string BuildEntityPropertyName(PropertyInfo propertyDataObject)
return propertyDataObject.Name;
}
}
-}
\ No newline at end of file
+}
diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs
new file mode 100644
index 00000000..01f70568
--- /dev/null
+++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/MasterLightLoadTest.cs
@@ -0,0 +1,233 @@
+#if NETCOREAPP
+namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update
+{
+ using System;
+ using System.Data;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Http;
+ using ICSSoft.STORMNET;
+ using ICSSoft.STORMNET.Business.LINQProvider;
+ using ICSSoft.STORMNET.KeyGen;
+ using NewPlatform.Flexberry.ORM.ODataService.Batch;
+ using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions;
+ using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers;
+
+ using Xunit;
+ using Xunit.Abstractions;
+
+ ///
+ /// Тесты для проверки работы MasterLightLoad. Для запуска OData backend используется модифицированная версия Startup - ,
+ /// которая задаёт флаг экономной загрузки мастеров для .
+ ///
+ public class MasterLightLoadTest : BaseODataServiceIntegratedTest
+ {
+ ///
+ /// Конструктор по-умолчанию.
+ ///
+ /// Фабрика для приложения.
+ /// Вывод диагностической информации по тестам.
+ public MasterLightLoadTest(CustomWebApplicationFactory factory, ITestOutputHelper output)
+ : base(factory, output)
+ {
+ }
+
+ ///
+ /// Проверка экономной загрузки мастера при активной настройке MasterLightLoad при смене мастера.
+ ///
+ [Fact]
+ public void MasterChangedTest()
+ {
+ ActODataService(args =>
+ {
+ // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных.
+ Порода порода = new Порода { Название = "Сиамская" };
+ Кошка кошка1 = new Кошка { Кличка = "Болтушка", Агрессивная = true, Порода = порода };
+ Кошка кошка2 = new Кошка { Кличка = "Петрушка", Агрессивная = false, Порода = порода };
+ args.DataService.UpdateObject(порода);
+ args.DataService.UpdateObject(кошка1);
+ args.DataService.UpdateObject(кошка2);
+
+ Котенок котенок = new Котенок { Кошка = кошка1, КличкаКотенка = "Котенок Гав", Глупость = 10 };
+ args.DataService.UpdateObject(котенок);
+
+ // Обновляем ссылку на мастера
+ котенок.Кошка = кошка2;
+
+ // Представление, по которому будем обновлять.
+ string[] котенокPropertiesNames =
+ {
+ Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey),
+ };
+ var котенокDynamicView = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNames), typeof(Котенок));
+
+ // Преобразуем объект в JSON-строку.
+ string requestJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model);
+
+ // Добавляем в payload информацию о том, что поменяли ссылку на мастера
+ requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonData, котенокDynamicView, args.Token.Model, кошка2, nameof(Котенок.Кошка));
+
+ // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности).
+ var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, котенок);
+
+ using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result)
+ {
+ // Если приходит код 200, значит, настройка не ломает загрузку.
+ // Фактическую проверку того, что кошка загрузилась в LightLoaded надо делать через отладчик.
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+
+ // TODO: проверка на экономную загрузку мастера.
+ }
+ });
+ }
+
+ ///
+ /// Проверка экономной загрузки мастера при активной настройке MasterLightLoad при смене значения поля у мастера.
+ ///
+ [Fact]
+ public void MasterPropsChangedBatchTest()
+ {
+ ActODataService(async args =>
+ {
+ // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных.
+ Порода порода = new Порода { Название = "Сиамская" };
+ Кошка кошка = new Кошка { Кличка = "Болтушка", Агрессивная = true, Порода = порода };
+ args.DataService.UpdateObject(порода);
+ args.DataService.UpdateObject(кошка);
+
+ Котенок котенок = new Котенок { Кошка = кошка, КличкаКотенка = "Котенок Гав", Глупость = 10 };
+ args.DataService.UpdateObject(котенок);
+
+ // Обновляем атрибут объекта
+ котенок.Глупость = 1;
+
+ // Обновляем атрибут мастера
+ котенок.Кошка.Кличка = "Петрушка";
+
+ // Представление, по которому будем обновлять объект
+ string[] котенокPropertiesNames =
+ {
+ Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey),
+ Information.ExtractPropertyPath<Котенок>(x => x.Глупость),
+ };
+ var котенокDynamicView = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNames), typeof(Котенок));
+
+ // Представление, по которому будем обновлять мастер
+ string[] кошкаPropertiesNames =
+ {
+ Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey),
+ Information.ExtractPropertyPath<Кошка>(x => x.Кличка),
+ };
+ var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка));
+
+ // Преобразуем объект в JSON-строку.
+ string котенокJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model);
+
+ // Добавляем в payload информацию о ссылке на мастера
+ котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, котенок.Кошка, nameof(Котенок.Кошка));
+
+ const string baseUrl = "http://localhost/odata";
+ string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку.
+ {
+ CreateChangeset(
+ $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}",
+ кошка.ToJson(кошкаDynamicView, args.Token.Model),
+ кошка),
+ CreateChangeset(
+ $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Котенок)).Name}",
+ котенокJsonData,
+ котенок),
+ };
+
+ // Act.
+ HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets);
+ using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result)
+ {
+ // Assert.
+ CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK });
+ Котенок котенокLoaded = args.DataService.Query<Котенок>(котенокDynamicView).FirstOrDefault(x => x.__PrimaryKey == котенок.__PrimaryKey);
+ Кошка кошкаLoaded = args.DataService.Query<Кошка>(кошкаDynamicView).FirstOrDefault(x => x.__PrimaryKey == кошка.__PrimaryKey);
+ Assert.NotNull(котенокLoaded);
+ Assert.NotNull(кошкаLoaded);
+ Assert.Equal(1, котенокLoaded.Глупость);
+ Assert.Equal("Петрушка", кошкаLoaded.Кличка);
+
+ // TODO: проверка на экономную загрузку мастера.
+ }
+ });
+ }
+
+ ///
+ /// Проверка экономной загрузки мастера при активной настройке MasterLightLoad при смене значения поля у мастера.
+ ///
+ [Fact]
+ public void MasterChangedBatchTest()
+ {
+ ActODataService(async args =>
+ {
+ // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных.
+ Порода порода = new Порода { Название = "Сиамская" };
+ Кошка кошка1 = new Кошка { Кличка = "Болтушка", Агрессивная = true, Порода = порода };
+ Кошка кошка2 = new Кошка { Кличка = "Петрушка", Агрессивная = false, Порода = порода };
+ args.DataService.UpdateObject(порода);
+ args.DataService.UpdateObject(кошка1);
+ args.DataService.UpdateObject(кошка2);
+
+ Котенок котенок = new Котенок { Кошка = кошка1, КличкаКотенка = "Котенок Гав", Глупость = 10 };
+ args.DataService.UpdateObject(котенок);
+
+ // Обновляем атрибут объекта
+ котенок.Глупость = 1;
+
+ // Обновляем мастера
+ котенок.Кошка = кошка2;
+
+ // Представление, по которому будем обновлять объект
+ string[] котенокPropertiesNames =
+ {
+ Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey),
+ Information.ExtractPropertyPath<Котенок>(x => x.Глупость),
+ };
+ var котенокDynamicView = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNames), typeof(Котенок));
+
+ // Преобразуем объект в JSON-строку.
+ string котенокJsonData = котенок.ToJson(котенокDynamicView, args.Token.Model);
+
+ // Добавляем в payload информацию о ссылке на мастера
+ котенокJsonData = ODataTestHelper.AddEntryRelationship(котенокJsonData, котенокDynamicView, args.Token.Model, котенок.Кошка, nameof(Котенок.Кошка));
+
+ const string baseUrl = "http://localhost/odata";
+ string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку.
+ {
+ CreateChangeset(
+ $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Котенок)).Name}",
+ котенокJsonData,
+ котенок),
+ };
+
+ // Act.
+ HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets);
+ using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result)
+ {
+ // Assert.
+ CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK });
+
+ string[] котенокPropertiesNamesMaster =
+ {
+ Information.ExtractPropertyPath<Котенок>(x => x.__PrimaryKey),
+ Information.ExtractPropertyPath<Котенок>(x => x.Глупость),
+ Information.ExtractPropertyPath<Котенок>(x => x.Кошка),
+ };
+ var котенокDynamicViewMaster = new View(new ViewAttribute("котенокDynamicView", котенокPropertiesNamesMaster), typeof(Котенок));
+ Котенок котенокLoaded = args.DataService.Query<Котенок>(котенокDynamicViewMaster).FirstOrDefault(x => x.Кошка.__PrimaryKey == кошка2.__PrimaryKey);
+ Assert.NotNull(котенокLoaded);
+ Assert.Equal(кошка2.__PrimaryKey, котенокLoaded.Кошка.__PrimaryKey);
+ Assert.Equal(1, котенокLoaded.Глупость);
+
+ // TODO: проверка на экономную загрузку мастера.
+ }
+ });
+ }
+ }
+}
+#endif
diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp
index b149d758..b65594b7 100644
--- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp
+++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp
@@ -1332,4 +1332,4 @@
-
\ No newline at end of file
+
diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs
index 4f8bf49c..d4eba42e 100644
--- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs
+++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs
@@ -20,16 +20,16 @@ public static class ODataTestHelper
/// Новое тело запроса к OData.
public static string AddEntryRelationship(string requestJsonData, View view, DataObjectEdmModel model, DataObject dataObject, string relationName)
{
- DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonData, view, model);
+ DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonData, view, model);
- objJsonМедв.Add(
+ objJson.Add(
$"{relationName}@odata.bind",
string.Format(
"{0}({1})",
model.GetEdmEntitySet(dataObject.GetType()).Name,
((KeyGuid)dataObject.__PrimaryKey).Guid.ToString("D")));
- var result = objJsonМедв.Serialize();
+ var result = objJson.Serialize();
return result;
}
diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs
new file mode 100644
index 00000000..f79b7d5f
--- /dev/null
+++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/MasterLightLoadTestStartup.cs
@@ -0,0 +1,75 @@
+#if NETCOREAPP
+namespace NewPlatform.Flexberry.ORM.ODataService.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using ICSSoft.Services;
+ using ICSSoft.STORMNET;
+ using IIS.Caseberry.Logging.Objects;
+ using Microsoft.AspNetCore.Builder;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.AspNetCore.Routing;
+ using Microsoft.Extensions.Configuration;
+ using NewPlatform.Flexberry.ORM.ODataService;
+ using NewPlatform.Flexberry.ORM.ODataService.Extensions;
+ using NewPlatform.Flexberry.ORM.ODataService.Model;
+ using NewPlatform.Flexberry.ORM.ODataService.WebApi.Extensions;
+ using NewPlatform.Flexberry.Services;
+ using ODataServiceSample.AspNetCore;
+ using Unity;
+
+ ///
+ /// Startup for testing MasterLightLoad configuration.
+ /// Differs from TestStartup that it marks type as data object for which masters should be light-loaded.
+ ///
+ public class MasterLightLoadTestStartup : Startup
+ {
+ ///
+ /// Initialize new instance of TestStartup.
+ ///
+ /// Configuration for new instance.
+ public MasterLightLoadTestStartup(IConfiguration configuration)
+ : base(configuration)
+ {
+ }
+
+ ///
+ public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ IUnityContainer unityContainer = UnityFactory.GetContainer();
+ unityContainer.RegisterInstance(env);
+
+ app.UseMiddleware();
+
+ app.UseMvc(builder =>
+ {
+ builder.MapRoute("Lock", "api/lock/{action}/{dataObjectId}", new { controller = "Lock" });
+ builder.MapFileRoute();
+ });
+
+ app.UseODataService(builder =>
+ {
+ IUnityContainer container = UnityFactory.GetContainer();
+
+ var assemblies = new[]
+ {
+ typeof(Котенок).Assembly,
+ typeof(ApplicationLog).Assembly,
+ typeof(UserSetting).Assembly,
+ typeof(Lock).Assembly,
+ };
+
+ PseudoDetailDefinitions pseudoDetailDefinitions = (PseudoDetailDefinitions)container.Resolve(typeof(PseudoDetailDefinitions));
+
+ // Set MasterLightLoad property for this DataObject
+ var masterLightLoadTypes = new List { typeof(Котенок) };
+ var modelBuilder = new DefaultDataObjectEdmModelBuilder(assemblies, false, pseudoDetailDefinitions, masterLightLoadTypes: masterLightLoadTypes);
+
+ var token = builder.MapDataObjectRoute(modelBuilder);
+
+ container.RegisterInstance(typeof(ManagementToken), token);
+ });
+ }
+ }
+}
+#endif