Skip to content

Commit

Permalink
Merge pull request #294 from Flexberry/feature-26-fix-batch-safe
Browse files Browse the repository at this point in the history
Решение проблемы с батч-запросом
  • Loading branch information
Anisimova2020 authored Feb 14, 2024
2 parents ee49b2b + 3b3f366 commit adeb59b
Show file tree
Hide file tree
Showing 19 changed files with 3,353 additions and 1,223 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Changed

### Fixed
1. Fixed loading of object with crushing of already loaded masters.
2. Fixed loading of details.

## [7.1.1] - 2023.06.08

### Added

### Changed
1. Updated `NewPlatform.Flexberry.ORM` up to `7.1.1`.
2. Get properties from objects for send it to frontend always rethrow exception now.
Expand Down
11 changes: 3 additions & 8 deletions NewPlatform.Flexberry.ORM.ODataService.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>NewPlatform.Flexberry.ORM.ODataService</id>
<version>7.1.1</version>
<version>7.2.0-beta01</version>
<title>Flexberry ORM ODataService</title>
<authors>New Platform Ltd.</authors>
<owners>New Platform Ltd.</owners>
Expand All @@ -12,14 +12,9 @@
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<description>Flexberry ORM OData Service Package.</description>
<releaseNotes>
Changed
1. Updated `NewPlatform.Flexberry.ORM` up to `7.1.1`.
2. Get properties from objects for send it to frontend always rethrow exception now.

Fixed
1. Fixed problem with metadata when inheritance and PublishName is used.
2. Safe load details with complex type usage hierarchy.

1. Fixed loading of object with crushing of already loaded masters.
2. Fixed loading of details.
</releaseNotes>
<copyright>Copyright New Platform Ltd 2023</copyright>
<tags>Flexberry ORM OData ODataService</tags>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,13 @@ private DataObject ReturnDataObject(Type objType, object keyValue)
&& dataObjectFromCache.GetLoadingState() != LoadingState.Loaded)
{
// Для обратной совместимости сравним перечень загруженных свойств и свойств в представлении.
// TODO: удалить эту проверку после стабилизации версии 5.1.0.
/* Данный код срабатывает, например, если в кэше был объект, который загрузился только на уровне первичного ключа.
*
* Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В.
* При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В.
* Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded.
* Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены).
*/
string[] loadedProps = dataObjectFromCache.GetLoadedProperties();
IEnumerable<PropertyInView> ownProps = view.Properties.Where(p => !p.Name.Contains('.'));
if (!ownProps.All(p => loadedProps.Contains(p.Name)))
Expand All @@ -731,7 +737,7 @@ private DataObject ReturnDataObject(Type objType, object keyValue)
View miniView = view.Clone();
DetailInView[] miniViewDetails = miniView.Details;
miniView.Details = new DetailInView[0];
_dataService.LoadObject(miniView, dataObjectFromCache, false, true, DataObjectCache);
_dataService.SafeLoadWithMasters(miniView, dataObjectFromCache, DataObjectCache);

if (miniViewDetails.Length > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,53 @@ public static object Execute(this SQLDataService dataService, Type dataObjectTyp
return queryProvider.Execute(expression);
}

/// <summary>
/// Загрузка объекта с его мастерами (объект должен быть не изменнённый и не до конца загруженный).
/// С мастерами необходимо обращаться аккуратно: если в кэше уже есть мастер, то нужно эту ситуацию разрешить,
/// поскольку иначе стандартная загрузка перетрёт данные мастера в кэше (и если он там изменён, то все изменения будут потеряны).
/// </summary>
/// <param name="dataService">Экземпляр сервиса данных.</param>
/// <param name="view">Представление объекта с мастерами.</param>
/// <param name="dobjectFromCache">Объект данных, в который будет производиться загрузка.</param>
/// <param name="dataObjectCache">Текущий кэш объектов данных (в данном кэше ранее существующие там объекты не должны быть перетёрты).</param>
public static void SafeLoadWithMasters(
this IDataService dataService, View view, ICSSoft.STORMNET.DataObject dobjectFromCache, DataObjectCache dataObjectCache)
{
if (dataService == null)
{
throw new ArgumentNullException(nameof(dataService));
}

if (view == null)
{
throw new ArgumentNullException(nameof(view));
}

if (dobjectFromCache == null)
{
throw new ArgumentNullException(nameof(dobjectFromCache));
}

if (dataObjectCache == null)
{
throw new ArgumentNullException(nameof(dataObjectCache));
}

// Прогружается пустой объект, чтобы избежать риска перетирания основного.
DataObject createdObject = (DataObject)Activator.CreateInstance(dobjectFromCache.GetType());
createdObject.SetExistObjectPrimaryKey(dobjectFromCache.__PrimaryKey);

// Используется отдельный кэш, чтобы не перетереть данные в основном кэше.
DataObjectCache specialCache = new DataObjectCache();
specialCache.StartCaching(false);
specialCache.AddDataObject(createdObject);
dataService.LoadObject(view, createdObject, false, true, specialCache);
specialCache.StopCaching();

// Перенос данных из одного объекта в другой.
ProperUpdateOfObject(dobjectFromCache, createdObject, dataObjectCache, specialCache);
}

/// <summary>
/// Загрузка детейлов с сохранением состояния изменения.
/// </summary>
Expand Down Expand Up @@ -122,28 +169,28 @@ public static void SafeLoadDetails(this IDataService dataService, View view, ILi
lcs.LimitFunction = FunctionBuilder.BuildIn(agregatorPropertyName, SQLWhereLanguageDef.LanguageDef.GetObjectTypeForNetType(agregatorKeyType), keys);

// Нужно соблюсти единственность инстанций агрегаторов при вычитке, поэтому реализуем отдельный кеш. Смешивать с кешем dataObjectCache нельзя, поскольку в предстоящей выборке будут те же самые детейлы (значения в кеше затрутся).
// Агрегаторы в кэш не помещаем. От помещения агрегаторов в кэш возникают неконтролируемые сбои основного кэша.
DataObjectCache agregatorCache = new DataObjectCache();
agregatorCache.StartCaching(false);
foreach (DataObject agregator in agregators)
{
agregatorCache.AddDataObject(agregator);
}

// Вычитываются детейлы одного типа, но для нескольких инстанций агрегаторов (оптимизируем количество SQL-запросов).
DataObject[] loadedDetails = dataService.LoadObjects(lcs, agregatorCache);
agregatorCache.StopCaching();

Dictionary<object, DataObject> extraCacheForAgregators = new Dictionary<object, DataObject>();
foreach (DataObject agregator in agregators)
{
agregator.AddLoadedProperties(detailInView.Name);
extraCacheForAgregators.Add(agregator.__PrimaryKey, agregator);
}

// Ввиду того, что агрегаторы нам пришли готовые с пустыми коллекциями детейлов, заполняем детейлы по агрегаторам значениями из кеша или из базы.
// На загрузку детейлов второго уровня передаем только детейлы отсутствующие в кэше.
List<DataObject> toLoadSecondDetails = new List<DataObject>();
foreach (DataObject loadedDetail in loadedDetails)
{
DataObject agregator = (DataObject)Information.GetPropValueByName(loadedDetail, agregatorPropertyName);
DataObject agregatorTemp = (DataObject)Information.GetPropValueByName(loadedDetail, agregatorPropertyName);
DataObject agregator = extraCacheForAgregators[agregatorTemp.__PrimaryKey];
object detailPrimaryKey = loadedDetail.__PrimaryKey;

DataObject detailFromCache = dataObjectCache.GetLivingDataObject(loadedDetail.GetType(), detailPrimaryKey);
Expand Down Expand Up @@ -226,5 +273,116 @@ public static void AddDetail(this DataObject agregator, DataObject detail)
LogService.LogWarn($"Detail type {detailType.AssemblyQualifiedName} not found in agregator of type {agregatorType.AssemblyQualifiedName}.");
}
}


/// <summary>
/// Перенос означенных свойств из свежезагруженного объекта в основной, расположенный в основном кэше.
/// </summary>
/// <param name="currentObject">Основной объект, куда необходимо копировать значения свойств.</param>
/// <param name="loadedObjectLocal">Свежезагруженный объект.</param>
/// <param name="dataObjectCache">Основной кэш.</param>
/// <param name="dataObjectCacheLocal">Локальный кэш, куда была выполнена свежая прогрузка.</param>
private static void ProperUpdateOfObject(DataObject currentObject, DataObject loadedObjectLocal, DataObjectCache dataObjectCache, DataObjectCache dataObjectCacheLocal)
{
if (currentObject == null)
{
throw new ArgumentNullException(nameof(currentObject));
}

if (loadedObjectLocal == null)
{
throw new ArgumentNullException(nameof(loadedObjectLocal));
}

if (dataObjectCache == null)
{
throw new ArgumentNullException(nameof(dataObjectCache));
}

if (dataObjectCacheLocal == null)
{
throw new ArgumentNullException(nameof(dataObjectCacheLocal));
}

// Перенос значений свойств объекта (в том числе могут быть мастера). Если мастера означены, то перенос свойств мастера производится далее.
List<string> localObjectLoadedProps = loadedObjectLocal.GetLoadedPropertiesList();
List<string> currentObjectLoadedProps = currentObject.GetLoadedPropertiesList();
List<string> notLoadedForActual = localObjectLoadedProps.Except(currentObjectLoadedProps).ToList();
DataObject currentDataCopy = currentObject.GetDataCopy();
foreach (string notLoadedPropName in notLoadedForActual)
{
object propValue = Information.GetPropValueByName(loadedObjectLocal, notLoadedPropName);
Information.SetPropValueByName(currentObject, notLoadedPropName, propValue);
currentObject.AddLoadedProperties(notLoadedPropName);
Information.SetPropValueByName(currentDataCopy, notLoadedPropName, propValue);
}

// Ещё могут быть частично загруженные мастера.
ProperCacheUpdateForOneObject(dataObjectCache, dataObjectCacheLocal, loadedObjectLocal, true);
}

/// <summary>
/// Обновление кэша по свежезагруженному объекту.
/// </summary>
/// <param name="dataObjectCacheActual">Текущий основной кэш объектов.</param>
/// <param name="dataObjectCacheWithMasters">Вспомогательный кэш, куда загружался объект.</param>
/// <param name="loadedDataObject">Свежезагруженный объект, по которому обновляется основной кэш.</param>
/// <param name="loadedObjectsAdded">Флаг, определяющий, что в кэш уже добавлен свежезагруженный объект.</param>
private static void ProperCacheUpdateForOneObject(DataObjectCache dataObjectCacheActual, DataObjectCache dataObjectCacheWithMasters, DataObject loadedDataObject, bool loadedObjectsAdded)
{
if (dataObjectCacheActual == null)
{
throw new ArgumentNullException(nameof(dataObjectCacheActual));
}

if (dataObjectCacheWithMasters == null)
{
throw new ArgumentNullException(nameof(dataObjectCacheWithMasters));
}

if (loadedDataObject == null)
{
return;
}

if (!loadedObjectsAdded)
{
dataObjectCacheActual.AddDataObject(loadedDataObject);
}

Type dobjType = typeof(DataObject);
Type currentType = loadedDataObject.GetType();
List<string> loadedProperties = loadedDataObject.GetLoadedPropertiesList();
foreach (string currentPropertyName in loadedProperties)
{
Type currentPropertyType = Information.GetPropertyType(currentType, currentPropertyName);
if (currentPropertyType.IsSubclassOf(dobjType)) // Выбираем у текущего объекта ссылки на мастеров.
{
DataObject currentMaster = (DataObject)Information.GetPropValueByName(loadedDataObject, currentPropertyName);
if (currentMaster != null)
{
// Типы currentPropertyType и currentMaster.GetType() могут различаться из-за наследования.
DataObject masterFromActualCache = dataObjectCacheActual.GetLivingDataObject(currentMaster.GetType(), currentMaster.__PrimaryKey);

if (masterFromActualCache == null)
{
// Если мастера ранее не было в кэше, то просто его туда переносим.
dataObjectCacheActual.AddDataObject(currentMaster);

// Но в добавленном мастере могут быть мастера 2 и далее уровней.
ProperCacheUpdateForOneObject(dataObjectCacheActual, dataObjectCacheWithMasters, currentMaster, true);
}
else
{ // Если мастер был в кэше, то аккуратно нужно перенести только незагруженные ранее свойства.
if (masterFromActualCache.GetStatus(false) == ObjectStatus.UnAltered && masterFromActualCache.GetLoadingState() != LoadingState.Loaded)
{
ProperUpdateOfObject(masterFromActualCache, currentMaster, dataObjectCacheActual, dataObjectCacheWithMasters);
}
}
}
}
}
}

}
}
Loading

0 comments on commit adeb59b

Please sign in to comment.