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

Возможность задать представление для обновления объекта (UpdateView) #292

Merged
merged 18 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ 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.

### 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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
226 changes: 115 additions & 111 deletions NewPlatform.Flexberry.ORM.ODataService.nuspec

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,37 @@ public static void SafeLoadDetails(this IDataService dataService, View view, ILi
}
}

/// <summary>
/// Догрузка объекта по указанному представлению, с загрузкой детейлов с сохранением состояния изменения.
/// </summary>
/// <param name="dataService">Экземпляр сервиса данных.</param>
/// <param name="dataObject">Объект данных, который нужно догрузить.</param>
/// <param name="view">Представление, которое используется для догрузки.</param>
/// <param name="dataObjectCache">Кеш объектов данных.</param>
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);
}
}

/// <summary>
/// Add detail object to agregator according detail type.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,21 @@ public View GetDataObjectDefaultView(Type dataObjectType)
}

return _metadata[dataObjectType].DefaultView.Clone();
}

/// <summary>
/// Осуществляет получение представления для обновления объекта, соответствующего заданному типу объекта данных.
/// </summary>
/// <param name="dataObjectType">Тип объекта данных, для которого требуется получить представление для обновления.</param>
/// <returns>Представление для обновления объекта, соответствующее заданному типу объекта данных.</returns>
public View GetDataObjectUpdateView(Type dataObjectType)
{
if (dataObjectType == null)
{
throw new ArgumentNullException(nameof(dataObjectType), "Contract assertion not met: dataObjectType != null");
}

return _metadata[dataObjectType].UpdateView?.Clone();
}

/// <summary>
Expand All @@ -534,7 +549,7 @@ public List<Type> GetTypes(List<string> strTypes)
/// <summary>
/// Осуществляет получение типа объекта данных, соответствующего заданному имени набора сущностей в EDM-модели.
/// </summary>
/// <param name="edmEntitySetName">Имя набора сущностей в EDM-модели, для которого требуется получить представление по умолчанию.</param>
/// <param name="edmEntitySetName">Имя набора сущностей в EDM-модели, для которого требуется получить тип.</param>
/// <returns>Типа объекта данных, соответствующий заданному имени набора сущностей в EDM-модели.</returns>
public Type GetDataObjectType(string edmEntitySetName)
{
Expand All @@ -554,6 +569,22 @@ public Type GetDataObjectType(string edmEntitySetName)
return dataObjectType;
}

/// <summary>
/// Осуществляет получение типа объекта данных, соответствующего заданной сущности в EDM-модели.
/// </summary>
/// <param name="edmEntity">Сущность в EDM-модели, для которой требуется получить тип.</param>
/// <returns>Типа объекта данных, соответствующий заданной сущности в EDM-модели.</returns>
public Type GetDataObjectType(EdmEntityObject edmEntity)
{
if (edmEntity == null)
{
throw new ArgumentNullException(nameof(edmEntity));
}

IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType;
return GetDataObjectType(GetEdmEntitySet(entityType).Name);
}

/// <summary>
/// Получает список зарегистрированных в модели типов, которые являются дочерними к данному родительскому типу.
/// В список добавляется также сам родительский тип.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public sealed class DataObjectEdmTypeSettings
/// </summary>
public View DefaultView { get; set; }

/// <summary>
/// View to be used instead of DefaultView on updates (Patch/Batch).
/// </summary>
public View UpdateView { get; set; }

/// <summary>
/// The list of exposed details.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public class DefaultDataObjectEdmModelBuilder : IDataObjectEdmModelBuilder
/// </summary>
public Func<PropertyInfo, string> EntityPropertyNameBuilder { get; set; }

/// <summary>
/// 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).
/// </summary>
private Dictionary<Type, View> UpdateViews { get; set; }

private readonly PropertyInfo _keyProperty = Information.ExtractPropertyInfo<DataObject>(n => n.__PrimaryKey);

/// <summary>
Expand All @@ -82,7 +87,8 @@ public DefaultDataObjectEdmModelBuilder(
IEnumerable<Assembly> searchAssemblies,
bool useNamespaceInEntitySetName = true,
PseudoDetailDefinitions pseudoDetailDefinitions = null,
Dictionary<Type, IEdmPrimitiveType> additionalMapping = null)
Dictionary<Type, IEdmPrimitiveType> additionalMapping = null,
IEnumerable<KeyValuePair<Type, View>> updateViews = null)
{
_searchAssemblies = searchAssemblies ?? throw new ArgumentNullException(nameof(searchAssemblies), "Contract assertion not met: searchAssemblies != null");
_useNamespaceInEntitySetName = useNamespaceInEntitySetName;
Expand All @@ -94,6 +100,11 @@ public DefaultDataObjectEdmModelBuilder(
EntityPropertyNameBuilder = BuildEntityPropertyName;
EntityTypeNameBuilder = BuildEntityTypeName;
EntityTypeNamespaceBuilder = BuildEntityTypeNamespace;

if (updateViews != null)
{
SetUpdateView(updateViews);
}
}

/// <summary>
Expand Down Expand Up @@ -173,6 +184,54 @@ public IPseudoDetailDefinition GetPseudoDetailDefinition(object pseudoDetail)
.FirstOrDefault();
}

/// <summary>
/// Change default views that would be used to load objects on updates.
/// </summary>
/// <remarks><i>Should be called before MapDataObjectRoute.</i></remarks>
/// <param name="updateViews">Key - DataObject type, value - view to be used for objects of that type on updates.</param>
private void SetUpdateView(IEnumerable<KeyValuePair<Type, View>> updateViews)
{
if (this.UpdateViews is null)
{
this.UpdateViews = new Dictionary<Type, View>();
}

if (updateViews != null)
{
foreach (KeyValuePair<Type, View> kvp in updateViews)
{
SetUpdateView(kvp.Key, kvp.Value);
}
}
}

/// <summary>
/// Change <paramref name="updateView"/> for a specific <paramref name="dataObjectType"/>. Update view would be used to load these objects on updates.
/// </summary>
/// <remarks><i>Should be called before MapDataObjectRoute.</i></remarks>
/// <param name="dataObjectType">DataObject type for which update view would be set.</param>
/// <param name="updateView">Update view to be used for objects of type <paramref name="dataObjectType" />. <i>Setting <see langword="null" /> removes update view for the type.</i></param>
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));
}

if (updateView is null)
{
UpdateViews.Remove(dataObjectType);
return;
}

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));
}

UpdateViews[dataObjectType] = updateView;
}

/// <summary>
/// Adds the property for exposing.
/// </summary>
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ public abstract class BaseIntegratedTest : IDisposable
/// <summary>
/// Base class for integration tests.
/// </summary>
public abstract class BaseIntegratedTest : IClassFixture<CustomWebApplicationFactory<Startup>>, IDisposable
/// <typeparam name="TStartup">Startup class used for booting the application.</typeparam>
public abstract class BaseIntegratedTest<TStartup> : IClassFixture<CustomWebApplicationFactory<TStartup>>, IDisposable
where TStartup : class
{
protected readonly WebApplicationFactory<Startup> _factory;
protected readonly WebApplicationFactory<TStartup> _factory;
turbcool marked this conversation as resolved.
Show resolved Hide resolved
#endif
protected ITestOutputHelper _output;

Expand Down Expand Up @@ -139,7 +141,7 @@ protected BaseIntegratedTest(string tempDbNamePrefix, bool useGisDataService = f
/// <param name="output">Unit tests debug output.</param>
/// <param name="tempDbNamePrefix">Prefix for temp database name.</param>
/// <param name="useGisDataService">Use DataService with Gis support.</param>
protected BaseIntegratedTest(CustomWebApplicationFactory<Startup> factory, ITestOutputHelper output, string tempDbNamePrefix, bool useGisDataService = false)
protected BaseIntegratedTest(CustomWebApplicationFactory<TStartup> factory, ITestOutputHelper output, string tempDbNamePrefix, bool useGisDataService = false)
{
_factory = factory;
_output = output;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@
/// <summary>
/// Базовый класс для тестирования работы с данными через ODataService.
/// </summary>
#if NETFRAMEWORK
public class BaseODataServiceIntegratedTest : BaseIntegratedTest
#endif
#if NETCOREAPP
public class BaseODataServiceIntegratedTest<TStartup> : BaseIntegratedTest<TStartup>
where TStartup : class
#endif
{
protected IDataObjectEdmModelBuilder _builder;

Expand Down Expand Up @@ -72,7 +78,7 @@ public BaseODataServiceIntegratedTest(
}
#endif
#if NETCOREAPP
public BaseODataServiceIntegratedTest(CustomWebApplicationFactory<Startup> factory, ITestOutputHelper output = null, bool useNamespaceInEntitySetName = false, bool useGisDataService = false, PseudoDetailDefinitions pseudoDetailDefinitions = null)
public BaseODataServiceIntegratedTest(CustomWebApplicationFactory<TStartup> factory, ITestOutputHelper output = null, bool useNamespaceInEntitySetName = false, bool useGisDataService = false, PseudoDetailDefinitions pseudoDetailDefinitions = null)
: base(factory, output, "ODataDB", useGisDataService)
{
Init(useNamespaceInEntitySetName, pseudoDetailDefinitions);
Expand Down
Loading
Loading