Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Нежадная загрузка мастеров при обновлении объекта #293

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix grammatical issue in the parameter description.

The phrase "allow to change" is grammatically incorrect. Consider rewording:

-They allow to change loading mode of masters during OData update requests.
+They allow changing the loading mode of masters during OData update requests.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
2. `masterLightLoadTypes` and `masterLightLoadAllTypes` parameters of `DefaultDataObjectEdmModelBuilder` class. They allow to change loading mode of masters during OData update requests.
2. `masterLightLoadTypes` and `masterLightLoadAllTypes` parameters of `DefaultDataObjectEdmModelBuilder` class. They allow changing the loading mode of masters during OData update requests.
🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Did you mean “changing”? Or maybe you should add a pronoun? In active voice, ‘allow’ + ‘to’ takes an object, usually a pronoun.
Context: ...bjectEdmModelBuilder` class. They allow to change loading mode of masters during OData up...

(ALLOW_TO)


### Changed
1. Updated Flexberry ORM up to 7.2.0-beta01.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace NewPlatform.Flexberry.ORM.ODataService.Controllers
namespace NewPlatform.Flexberry.ORM.ODataService.Controllers
{
using System;
using System.Collections.Generic;
Expand All @@ -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 Newtonsoft.Json.Linq;
using NewPlatform.Flexberry.ORM.ODataService.Events;
using NewPlatform.Flexberry.ORM.ODataService.Handlers;
#endif
#if NETSTANDARD
using System.Data;
using Microsoft.AspNet.OData.Formatter;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -739,13 +742,31 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key)
}

/// <summary>
/// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению <paramref name="view"/>, иначе - создаётся новый.
/// Получить объект данных на основании EdmEntity.
/// </summary>
/// <param name="edmEntity">EdmEntity, который будет использован для получения объекта данных.</param>
/// <returns>Объект данных.</returns>
private DataObject ReturnDataObject(EdmEntityObject edmEntity)
{
if (edmEntity == null)
{
throw new ArgumentNullException(nameof(edmEntity));
}

Type masterType = _model.GetDataObjectType(edmEntity);
object masterKey = GetKey(edmEntity);

return ReturnDataObject(masterType, masterKey);
}

/// <summary>
/// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению по умолчанию, иначе - создаётся новый.
/// </summary>
/// <param name="objType">Тип объекта, не может быть <c>null</c>.</param>
/// <param name="keyValue">Значение ключа.</param>
/// <param name="view">Представление для загрузки объекта.</param>
/// <param name="useUpdateView">Использовать UpdateView для загрузки объекта (при наличии).</param>
/// <returns>Объект данных.</returns>
private DataObject ReturnDataObject(Type objType, object keyValue, View view)
private DataObject ReturnDataObject(Type objType, object keyValue, bool useUpdateView = false)
{
if (objType == null)
{
Expand All @@ -754,6 +775,16 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view)

if (keyValue != null)
{
View view = null;
if (useUpdateView)
{
view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType);
}
else
{
view = _model.GetDataObjectDefaultView(objType);
}

DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue);

if (dataObjectFromCache != null)
Expand All @@ -774,7 +805,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view)
IEnumerable<PropertyInView> 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];
Expand All @@ -790,7 +821,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue, View view)
return dataObjectFromCache;
}

// Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказать отдельные операции с детейлами и перевычитка затрёт эти изменения.
// Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения.
View lightView = view.Clone();
DetailInView[] lightViewDetails = lightView.Details;
lightView.Details = new DetailInView[0];
Expand Down Expand Up @@ -848,8 +879,31 @@ private static void AddObjectToUpdate(List<DataObject> objsToUpdate, DataObject
objsToUpdate.Insert(0, dataObject); // Добавляем объект в начало списка.
}

}
}
}

/// <summary>
/// Получить значение ключа у указанной сущности.
/// </summary>
/// <param name="edmEntity">Сущность.</param>
/// <returns>Значение ключа.</returns>
private object GetKey(EdmEntityObject edmEntity)
{
if (edmEntity == null)
Anisimova2020 marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ArgumentNullException(nameof(edmEntity), $"{nameof(edmEntity)} can not be null.");
}

object key;

// Получим значение ключа.
IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
IEnumerable<IEdmProperty> entityProps = entityType.Properties();
var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName);
edmEntity.TryGetPropertyValue(keyProperty.Name, out key);
Anisimova2020 marked this conversation as resolved.
Show resolved Hide resolved

return key;
}

Comment on lines +899 to +915
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential NullReferenceException in GetKey method

In the GetKey method, if the key property is not found, keyProperty will be null, leading to a possible NullReferenceException when accessing keyProperty.Name at line 912. It's advisable to check if keyProperty is null and throw a meaningful exception if the key property is missing.

Apply this diff to handle the null case:

                 var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName);
+                if (keyProperty == null)
+                {
+                    throw new InvalidOperationException($"Key property '{_model.KeyPropertyName}' not found in the entity of type '{entityType.FullTypeName()}'.");
+                }
                 edmEntity.TryGetPropertyValue(keyProperty.Name, out key);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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<IEdmProperty> entityProps = entityType.Properties();
var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName);
edmEntity.TryGetPropertyValue(keyProperty.Name, out key);
return key;
}
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<IEdmProperty> entityProps = entityType.Properties();
var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName);
if (keyProperty == null)
{
throw new InvalidOperationException($"Key property '{_model.KeyPropertyName}' not found in the entity of type '{entityType.FullTypeName()}'.");
}
edmEntity.TryGetPropertyValue(keyProperty.Name, out key);
return key;
}

/// <summary>
/// Построение объекта данных по сущности OData.
Expand All @@ -867,36 +921,13 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
return null;
}

// Значение свойства.
object value;

// Получим значение ключа.
IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
IEnumerable<IEdmProperty> 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, useUpdateView);

// Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом.
AddObjectToUpdate(dObjs, obj, endObject);
Expand All @@ -906,6 +937,8 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
IEnumerable<string> changedPropNames = edmEntity.GetChangedPropertyNames();

// Обрабатываем агрегатор первым.
IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
IEnumerable<IEdmProperty> entityProps = entityType.Properties().ToList();
List<IEdmProperty> changedProps = entityProps
.Where(ep => changedPropNames.Contains(ep.Name))
.OrderBy(ep => ep.Name != agregatorPropertyName)
Expand All @@ -931,22 +964,37 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
// Обработка мастеров и детейлов.
if (prop is EdmNavigationProperty navProp)
{
edmEntity.TryGetPropertyValue(prop.Name, out value);

EdmMultiplicity edmMultiplicity = navProp.TargetMultiplicity();

// Обработка мастеров.
if (edmMultiplicity == EdmMultiplicity.One || edmMultiplicity == EdmMultiplicity.ZeroOrOne)
{
object value;
edmEntity.TryGetPropertyValue(prop.Name, out value);

if (value is EdmEntityObject edmMaster)
{
// Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом.
bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName);
DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView);
bool masterOwnPropsUpdated = edmMaster.GetChangedPropertyNames().Any(propName => propName != _model.KeyPropertyName);
bool isAggregator = dataObjectPropName == agregatorPropertyName;
DataObject master = null;

Type objectType = _model.GetDataObjectType(edmEntity);
bool masterLightLoad = _model.IsMasterLightLoad(objectType);

if (masterLightLoad && !masterOwnPropsUpdated && !isAggregator)
{
master = ReturnDataObject(edmMaster); // здесь мастер не добавляется в dObjs (объекты на обновление) т.к. мы точно знаем что он будет в состоянии UnAltered
}
else
{
master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView);
}

Information.SetPropValueByName(obj, dataObjectPropName, master);

if (dataObjectPropName == agregatorPropertyName)
if (isAggregator)
{
master.AddDetail(obj);

Expand All @@ -969,6 +1017,9 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
{
DetailArray detarr = (DetailArray)Information.GetPropValueByName(obj, dataObjectPropName);

object value;
edmEntity.TryGetPropertyValue(prop.Name, out value);

if (value is EdmEntityObjectCollection coll)
{
if (coll != null && coll.Count > 0)
Expand Down Expand Up @@ -1002,11 +1053,13 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke
else
{
// Обработка собственных свойств объекта (неключевых, т.к. ключ устанавливаем при начальной инициализации объекта obj).
if (prop.Name != keyProperty.Name)
if (prop.Name != _model.KeyPropertyName)
{
Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName);
object value;
edmEntity.TryGetPropertyValue(prop.Name, out value);

Type dataObjectPropertyType = Information.GetPropertyType(objType, dataObjectPropName);

// Если тип свойства относится к одному из зарегистрированных провайдеров файловых свойств,
// значит свойство файловое, и его нужно обработать особым образом.
if (_dataObjectFileAccessor.HasDataObjectFileProvider(dataObjectPropertyType))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,13 @@ public View GetDataObjectUpdateView(Type dataObjectType)
return _metadata[dataObjectType].UpdateView?.Clone();
}

/// <summary>
/// Возвращает информацию, должны ли мастера объекта загружаться в экономном режиме (только __PrimaryKey).
/// </summary>
/// <param name="dataObjectType">Тип объекта данных.</param>
/// <returns>Мастера должны загружаться экономно.</returns>
public bool IsMasterLightLoad(Type dataObjectType) => _metadata[dataObjectType].MasterLightLoad;
Anisimova2020 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Получает список зарегистрированных в модели типов по списку имён типов.
/// </summary>
Expand Down
Anisimova2020 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public sealed class DataObjectEdmTypeSettings
/// </summary>
public View UpdateView { get; set; }

/// <summary>
/// Whether to load object masters in LightLoaded state (load only primary key).
/// </summary>
public bool MasterLightLoad { get; set; }
Anisimova2020 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The list of exposed details.
/// </summary>
Expand All @@ -56,4 +61,4 @@ public sealed class DataObjectEdmTypeSettings
/// </summary>
public IDictionary<PropertyInfo, DataObjectEdmDetailSettings> PseudoDetailProperties { get; } = new Dictionary<PropertyInfo, DataObjectEdmDetailSettings>();
}
}
}
Loading
Loading