Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,22 @@ public void CreateUnloadAllCommand ()
Assert.That(unloadAllCommand.TransactionEventSink, Is.SameAs(_transactionEventSinkStub.Object));
}

[Test]
[Ignore("TODO RM-8240: enable after hard cast has been removed")]
public void CreateUnloadFilteredDomainObjectsCommand ()
{
Predicate<DomainObject> domainObjectFilter = obj => true;
var command = _dataManagerWithMocks.CreateUnloadFilteredDomainObjectsCommand(domainObjectFilter);

Assert.That(command, Is.TypeOf<UnloadFilteredDomainObjectsCommand>());
var unloadFilteredDomainObjectsCommand = (UnloadFilteredDomainObjectsCommand)command;
Assert.That(unloadFilteredDomainObjectsCommand.DataContainerMap, Is.SameAs(DataManagerTestHelper.GetDataContainerMap(_dataManagerWithMocks)));
Assert.That(unloadFilteredDomainObjectsCommand.RelationEndPointMap, Is.SameAs(DataManagerTestHelper.GetRelationEndPointManager(_dataManagerWithMocks)));
Assert.That(unloadFilteredDomainObjectsCommand.InvalidDomainObjectManager, Is.SameAs(DataManagerTestHelper.GetInvalidDomainObjectManager(_dataManagerWithMocks)));
Assert.That(unloadFilteredDomainObjectsCommand.TransactionEventSink, Is.SameAs(_transactionEventSinkStub.Object));
Assert.That(unloadFilteredDomainObjectsCommand.DomainObjectFilter, Is.SameAs(domainObjectFilter));
}

[Test]
public void GetDataContainerWithoutLoading_NotLoaded ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public void DelegatingMembers ()
new IRelationEndPoint[0]);
var dataManagementCommand = new Mock<IDataManagementCommand>();
var randomBoolean = BooleanObjectMother.GetRandomBoolean();
Predicate<DomainObjectState> expectedPredicate = state => state.IsUnchanged;
Predicate<DomainObject> domainObjectFilter = obj => true;

CheckDelegation(dm => dm.GetOrCreateVirtualEndPoint(relationEndPointID), virtualEndPoint.Object);
CheckDelegation(dm => dm.GetRelationEndPointWithLazyLoad(relationEndPointID), virtualEndPoint.Object);
Expand All @@ -57,7 +59,6 @@ public void DelegatingMembers ()
CheckDelegation(dm => dm.GetState(objectID), new DomainObjectState.Builder().SetDeleted().Value);
CheckDelegation(dm => dm.GetDataContainerWithLazyLoad(objectID, randomBoolean), dataContainer);
CheckDelegation(dm => dm.GetDataContainersWithLazyLoad(new[] { objectID }, true), new[] { dataContainer });
Predicate<DomainObjectState> expectedPredicate = state => state.IsUnchanged;
CheckDelegation(dm => dm.GetLoadedDataByObjectState(expectedPredicate), new[] { persistableData });
CheckDelegation(dm => dm.MarkInvalid(domainObject));
CheckDelegation(dm => dm.MarkNotInvalid(objectID));
Expand All @@ -67,6 +68,7 @@ public void DelegatingMembers ()
CheckDelegation(dm => dm.CreateUnloadCommand(new[] { objectID }), dataManagementCommand.Object);
CheckDelegation(dm => dm.CreateUnloadVirtualEndPointsCommand(new[] { relationEndPointID }), dataManagementCommand.Object);
CheckDelegation(dm => dm.CreateUnloadAllCommand(), dataManagementCommand.Object);
CheckDelegation(dm => dm.CreateUnloadFilteredDomainObjectsCommand(domainObjectFilter), dataManagementCommand.Object);
CheckDelegation(dm => dm.LoadLazyCollectionEndPoint(relationEndPointID));
CheckDelegation(dm => dm.LoadLazyVirtualObjectEndPoint(relationEndPointID));
CheckDelegation(dm => dm.LoadLazyDataContainer(objectID), dataContainer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,10 @@ public IDataManagementCommand CreateUnloadAllCommand ()
{
throw new NotImplementedException();
}

public IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate<DomainObject> domainObjectFilter)
{
throw new NotImplementedException();
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected TestableClientTransaction (IClientTransactionComponentFactory componen

public IClientTransactionEventBroker EventBroker
{
get { return (IClientTransactionEventBroker)PrivateInvoke.GetNonPublicProperty(this, typeof(ClientTransaction), "eventBroker"); }
get { return (IClientTransactionEventBroker)PrivateInvoke.GetNonPublicProperty(this, typeof(ClientTransaction), "_eventBroker"); }
}

public new DomainObject GetObject (ObjectID id, bool includeDeleted)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// This file is part of the re-motion Core Framework (www.re-motion.org)
// Copyright (c) rubicon IT GmbH, www.rubicon.eu
//
// The re-motion Core Framework is free software; you can redistribute it
// and/or modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// re-motion is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with re-motion; if not, see http://www.gnu.org/licenses.
//
using System;
using System.Collections.Generic;
using System.Linq;
using Remotion.Data.DomainObjects.DataManagement.RelationEndPoints;
using Remotion.Data.DomainObjects.Infrastructure;
using Remotion.Data.DomainObjects.Infrastructure.InvalidObjects;
using Remotion.Data.DomainObjects.Mapping;
using Remotion.FunctionalProgramming;
using Remotion.Utilities;

namespace Remotion.Data.DomainObjects.DataManagement.Commands
{
public class UnloadFilteredDomainObjectsCommand : IDataManagementCommand
{
public DataContainerMap DataContainerMap { get; }
public IInvalidDomainObjectManager InvalidDomainObjectManager { get; }
public RelationEndPointMap RelationEndPointMap { get; }
public IClientTransactionEventSink TransactionEventSink { get; }
public Predicate<DomainObject> DomainObjectFilter { get; }
private IReadOnlyCollection<DataContainer> _dataContainersForUnloading = Array.Empty<DataContainer>();
private IReadOnlyCollection<IVirtualEndPoint> _virtualEndPointsForUnloading = Array.Empty<IVirtualEndPoint>();
private IReadOnlyCollection<IRealObjectEndPoint> _realObjectEndPointsForUnloading = Array.Empty<IRealObjectEndPoint>();
private List<Exception> _exceptions = new List<Exception>();

public UnloadFilteredDomainObjectsCommand (
DataContainerMap dataContainerMap,
IInvalidDomainObjectManager invalidDomainObjectManager,
RelationEndPointMap relationEndPointMap,
IClientTransactionEventSink transactionEventSink,
Predicate<DomainObject> domainObjectFilter)
{
ArgumentUtility.CheckNotNull("dataContainerMap", dataContainerMap);
ArgumentUtility.CheckNotNull("invalidDomainObjectManager", invalidDomainObjectManager);
ArgumentUtility.CheckNotNull("relationEndPointMap", relationEndPointMap);
ArgumentUtility.CheckNotNull("transactionEventSink", transactionEventSink);
ArgumentUtility.CheckNotNull("domainObjectFilter", domainObjectFilter);

DataContainerMap = dataContainerMap;
InvalidDomainObjectManager = invalidDomainObjectManager;
RelationEndPointMap = relationEndPointMap;
TransactionEventSink = transactionEventSink;
DomainObjectFilter = domainObjectFilter;
}

public IEnumerable<Exception> GetAllExceptions ()
{
return _exceptions.AsReadOnly();
}

public void Begin ()
{
_dataContainersForUnloading = DataContainerMap
.Where(dc => DomainObjectFilter(dc.DomainObject))
.ToList()
.AsReadOnly();

var relationEndPoints = GetRelationEndPointsForUnloading(_dataContainersForUnloading);
if (relationEndPoints.RealObjectEndPointsNotPartOfUnloadSet.Any() || relationEndPoints.VirtualEndPointsNotPartOfUnloadSet.Any())
{
var problematicEndPoints = relationEndPoints.RealObjectEndPointsNotPartOfUnloadSet.Select(ep => ep.ID)
.Concat(relationEndPoints.VirtualEndPointsNotPartOfUnloadSet.Select(ep => ep.ID));
_exceptions.Add(
new InvalidOperationException(
"Cannot unload the following relation endpoints because the associated domain objects have not been included in the data set.\r\n"
+ string.Join(",\r\n", problematicEndPoints)));
throw _exceptions.First();
}
else
{
_realObjectEndPointsForUnloading = relationEndPoints.RealObjectEndPoints;
_virtualEndPointsForUnloading = relationEndPoints.VirtualEndPoints;
if (_dataContainersForUnloading.Count > 0)
TransactionEventSink.RaiseObjectsUnloadingEvent(_dataContainersForUnloading.Select(dc => dc.DomainObject).ToList());
}
}

public void Perform ()
{
if (_exceptions.Count > 0)
throw _exceptions.First();

// Unregistering a real-object-endpoint also unregisters the associated virtual endpoint.
// If is therefor best to first handle all real-object-endpoints in case both sides belong to the same object.

foreach (var relationEndPoint in _realObjectEndPointsForUnloading)
{
relationEndPoint.Rollback();
RelationEndPointMap.RemoveEndPoint(relationEndPoint.ID);
// RelationEndPointRegistrationAgent.UnregisterEndPoint(relationEndPoint, RelationEndPointMap);
}

foreach (var relationEndPoint in _virtualEndPointsForUnloading)
{
relationEndPoint.Rollback();
RelationEndPointMap.RemoveEndPoint(relationEndPoint.ID);
//relationEndPoint.MarkDataIncomplete();
// Assertion.IsTrue(relationEndPoint.CanBeCollected, "VirtualEndPoint '{0}' cannot be collected.", relationEndPoint.ID);
// RelationEndPointRegistrationAgent.UnregisterEndPoint(relationEndPoint, RelationEndPointMap);
}

foreach (var dataContainer in _dataContainersForUnloading)
{
DataContainerMap.Remove(dataContainer.ID);

var dataContainerState = dataContainer.State;
dataContainer.Discard();
if (dataContainerState.IsNew)
InvalidDomainObjectManager.MarkInvalid(dataContainer.DomainObject);
}
}

public void End ()
{
if (_exceptions.Count > 0)
throw _exceptions.First();

if (_dataContainersForUnloading.Count > 0)
TransactionEventSink.RaiseObjectsUnloadingEvent(_dataContainersForUnloading.Select(dc => dc.DomainObject).ToList());
}

public ExpandedCommand ExpandToAllRelatedObjects ()
{
return new ExpandedCommand(this);
}

private
(HashSet<IRealObjectEndPoint> RealObjectEndPoints,
HashSet<IVirtualEndPoint> VirtualEndPoints,
HashSet<IRealObjectEndPoint> RealObjectEndPointsNotPartOfUnloadSet,
HashSet<IVirtualEndPoint> VirtualEndPointsNotPartOfUnloadSet)
GetRelationEndPointsForUnloading (IReadOnlyCollection<DataContainer> dataContainers)
{
var realObjectEndPoints = new HashSet<IRealObjectEndPoint>();
var virtualObjectEndPoints = new HashSet<IVirtualEndPoint>();
var realObjectEndPointsNotPartOfUnloadSet = new HashSet<IRealObjectEndPoint>();
var virtualEndPointsNotPartOfUnloadSet = new HashSet<IVirtualEndPoint>();
var objectIDs = dataContainers.ToDictionary(dc => dc.ID);

var endPoints = RelationEndPointMap
.ApplySideEffect(ep => Assertion.IsFalse(ep.IsNull, "RelationEndPoint '{0}' is a null end-point.", ep.ID))
.Where(ep => ep.ObjectID != null);

foreach (var endPoint in endPoints)
{
Assertion.DebugAssert(endPoint.Definition.IsAnonymous == false, "endPoint.Definition.IsAnonymous == false");
Assertion.DebugIsNotNull(endPoint.ObjectID, "endPoint.ObjectID != null");
var isPartOfUnloadedSet = objectIDs.ContainsKey(endPoint.ObjectID);

if (endPoint is IRealObjectEndPoint realObjectEndPoint)
{
if (isPartOfUnloadedSet)
{
var virtualEndPointDefinition = realObjectEndPoint.ID.Definition.RelationDefinition.GetOppositeEndPointDefinition(realObjectEndPoint.ID.Definition);
Assertion.IsNotNull(virtualEndPointDefinition, "Inconsistent relation definition for endpoint '{0}'.", realObjectEndPoint.ID.Definition);

if (realObjectEndPoint.IsNull)
{
realObjectEndPoints.Add(realObjectEndPoint);
}
else if (virtualEndPointDefinition.IsAnonymous)
{
realObjectEndPoints.Add(realObjectEndPoint);
}
else
{
var virtualEndPointID = realObjectEndPoint.GetOppositeRelationEndPointID();
Assertion.IsNotNull(
virtualEndPointID,
"GetOppositeRelationEndPointID() for real endpoint '{0}' evaluated and returned null but virtual endpoint is not anonymous.",
realObjectEndPoint.ID);

if (IsPartOfUnloadedSet(realObjectEndPoint.OppositeObjectID) && IsPartOfUnloadedSet(realObjectEndPoint.OriginalOppositeObjectID))
{
realObjectEndPoints.Add(realObjectEndPoint);

var virtualEndPoint = (IVirtualEndPoint?)RelationEndPointMap[virtualEndPointID];
if (virtualEndPoint != null)
{
if (virtualEndPoint.HasChanged)
virtualEndPointsNotPartOfUnloadSet.Add(virtualEndPoint);
else
virtualObjectEndPoints.Add(virtualEndPoint);
}
}
else
{
realObjectEndPointsNotPartOfUnloadSet.Add(realObjectEndPoint);
var virtualEndPoint = (IVirtualEndPoint?)RelationEndPointMap[virtualEndPointID];
if (virtualEndPoint != null)
{
if (virtualEndPoint.HasChanged)
virtualEndPointsNotPartOfUnloadSet.Add(virtualEndPoint);
}
}
}
}
}
else
{
var virtualEndPoint = (IVirtualEndPoint)endPoint;
if (isPartOfUnloadedSet)
{
if (virtualEndPoint.IsNull)
{
virtualObjectEndPoints.Add(virtualEndPoint);
}
else if (virtualEndPoint.CanBeCollected)
{
virtualObjectEndPoints.Add(virtualEndPoint);
}
else if (virtualEndPoint.HasChanged == false)
{
virtualObjectEndPoints.Add(virtualEndPoint);
}
else if (virtualEndPoint.Definition.Cardinality == CardinalityType.One)
{
var virtualObjectEndPoint = (IVirtualObjectEndPoint)virtualEndPoint;
if (IsPartOfUnloadedSet(virtualObjectEndPoint.OppositeObjectID) && IsPartOfUnloadedSet(virtualObjectEndPoint.OriginalOppositeObjectID))
{
virtualObjectEndPoints.Add(virtualEndPoint);
}
else
{
virtualEndPointsNotPartOfUnloadSet.Add(virtualEndPoint);
var realObjectEndPointID = virtualObjectEndPoint.GetOppositeRelationEndPointID();
if (realObjectEndPointID != null)
{
var oppositeRealObjectEndPoint = (IRealObjectEndPoint?)RelationEndPointMap[realObjectEndPointID];
if (oppositeRealObjectEndPoint != null)
realObjectEndPointsNotPartOfUnloadSet.Add(oppositeRealObjectEndPoint);
}
}
}
else
{
var collectionEndPoint = (ICollectionEndPoint<ICollectionEndPointData>)virtualEndPoint;
if (collectionEndPoint.GetData().All(obj => objectIDs.ContainsKey(obj.ID))
&& collectionEndPoint.GetOriginalData().All(obj => objectIDs.ContainsKey(obj.ID)))
{
virtualObjectEndPoints.Add(virtualEndPoint);
}
else
{
virtualEndPointsNotPartOfUnloadSet.Add(virtualEndPoint);
var realObjectEndPointIDs = collectionEndPoint.GetOppositeRelationEndPointIDs().Where(epID => !IsPartOfUnloadedSet(epID.ObjectID));
foreach (var realObjectEndPointID in realObjectEndPointIDs)
{
var oppositeRealObjectEndPoint = (IRealObjectEndPoint?)RelationEndPointMap[realObjectEndPointID];
if (oppositeRealObjectEndPoint != null)
realObjectEndPointsNotPartOfUnloadSet.Add(oppositeRealObjectEndPoint);
}
}
}
}
}
}

realObjectEndPointsNotPartOfUnloadSet.RemoveWhere(realObjectEndPoints.Contains);
virtualEndPointsNotPartOfUnloadSet.RemoveWhere(virtualObjectEndPoints.Contains);

return (
RealObjectEndPoints: realObjectEndPoints,
VirtualEndPoints: virtualObjectEndPoints,
RealObjectEndPointsNotPartOfUnloadSet: realObjectEndPointsNotPartOfUnloadSet,
VirtualEndPointsNotPartOfUnloadSet: virtualEndPointsNotPartOfUnloadSet
);

bool IsPartOfUnloadedSet (ObjectID? objectID) => objectID == null || objectIDs.ContainsKey(objectID) || DataContainerMap[objectID] == null;
}
}
}
13 changes: 13 additions & 0 deletions Remotion/Data/DomainObjects/DataManagement/DataManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,19 @@ public IDataManagementCommand CreateUnloadAllCommand ()
return new UnloadAllCommand(_relationEndPointManager, _dataContainerMap, _invalidDomainObjectManager, _transactionEventSink);
}

public IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate<DomainObject> domainObjectFilter)
{
ArgumentUtility.CheckNotNull("domainObjectFilter", domainObjectFilter);

return new UnloadFilteredDomainObjectsCommand(
_dataContainerMap,
_invalidDomainObjectManager,
//TODO RM-8240: refactor UnloadFilteredDomainObjectsCommand to move relation endpoint unloading to separate command created by RelationEndPointManager
(RelationEndPointMap)_relationEndPointManager.RelationEndPoints,
_transactionEventSink,
domainObjectFilter);
}

private ClientTransactionsDifferException CreateClientTransactionsDifferException (string message, params object?[] args)
{
return new ClientTransactionsDifferException(String.Format(message, args));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ public IDataManagementCommand CreateUnloadAllCommand ()
return SafeInnerDataManager.CreateUnloadAllCommand();
}

public IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate<DomainObject> domainObjectFilter)
{
return SafeInnerDataManager.CreateUnloadFilteredDomainObjectsCommand(domainObjectFilter);
}

public void LoadLazyCollectionEndPoint (RelationEndPointID endPointID)
{
SafeInnerDataManager.LoadLazyCollectionEndPoint(endPointID);
Expand Down
1 change: 1 addition & 0 deletions Remotion/Data/DomainObjects/DataManagement/IDataManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ public interface IDataManager : IRelationEndPointProvider, ILoadedDataContainerP
IDataManagementCommand CreateUnloadCommand (params ObjectID[] objectIDs);
IDataManagementCommand CreateUnloadVirtualEndPointsCommand (params RelationEndPointID[] endPointIDs);
IDataManagementCommand CreateUnloadAllCommand ();
IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate<DomainObject> domainObjectFilter);
}
}
Loading