diff --git a/Remotion/Data/DomainObjects.UnitTests/DataManagement/DataManagerTest.cs b/Remotion/Data/DomainObjects.UnitTests/DataManagement/DataManagerTest.cs index 12a883f872..52b627fa5f 100644 --- a/Remotion/Data/DomainObjects.UnitTests/DataManagement/DataManagerTest.cs +++ b/Remotion/Data/DomainObjects.UnitTests/DataManagement/DataManagerTest.cs @@ -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 domainObjectFilter = obj => true; + var command = _dataManagerWithMocks.CreateUnloadFilteredDomainObjectsCommand(domainObjectFilter); + + Assert.That(command, Is.TypeOf()); + 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 () { diff --git a/Remotion/Data/DomainObjects.UnitTests/DataManagement/DelegatingDataManagerTest.cs b/Remotion/Data/DomainObjects.UnitTests/DataManagement/DelegatingDataManagerTest.cs index 20f6877eb7..701f8691a5 100644 --- a/Remotion/Data/DomainObjects.UnitTests/DataManagement/DelegatingDataManagerTest.cs +++ b/Remotion/Data/DomainObjects.UnitTests/DataManagement/DelegatingDataManagerTest.cs @@ -45,6 +45,8 @@ public void DelegatingMembers () new IRelationEndPoint[0]); var dataManagementCommand = new Mock(); var randomBoolean = BooleanObjectMother.GetRandomBoolean(); + Predicate expectedPredicate = state => state.IsUnchanged; + Predicate domainObjectFilter = obj => true; CheckDelegation(dm => dm.GetOrCreateVirtualEndPoint(relationEndPointID), virtualEndPoint.Object); CheckDelegation(dm => dm.GetRelationEndPointWithLazyLoad(relationEndPointID), virtualEndPoint.Object); @@ -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 expectedPredicate = state => state.IsUnchanged; CheckDelegation(dm => dm.GetLoadedDataByObjectState(expectedPredicate), new[] { persistableData }); CheckDelegation(dm => dm.MarkInvalid(domainObject)); CheckDelegation(dm => dm.MarkNotInvalid(objectID)); @@ -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); diff --git a/Remotion/Data/DomainObjects.UnitTests/DataManagement/SerializableFakes/SerializableDataManagerFake.cs b/Remotion/Data/DomainObjects.UnitTests/DataManagement/SerializableFakes/SerializableDataManagerFake.cs index 5428d7c0d6..6dfbe6232c 100644 --- a/Remotion/Data/DomainObjects.UnitTests/DataManagement/SerializableFakes/SerializableDataManagerFake.cs +++ b/Remotion/Data/DomainObjects.UnitTests/DataManagement/SerializableFakes/SerializableDataManagerFake.cs @@ -139,5 +139,10 @@ public IDataManagementCommand CreateUnloadAllCommand () { throw new NotImplementedException(); } + + public IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate domainObjectFilter) + { + throw new NotImplementedException(); + } } } diff --git a/Remotion/Data/DomainObjects.UnitTests/IntegrationTests/Unload/UnloadFilteredDomainObjectsCommantTest.cs b/Remotion/Data/DomainObjects.UnitTests/IntegrationTests/Unload/UnloadFilteredDomainObjectsCommantTest.cs new file mode 100644 index 0000000000..ec1890190b --- /dev/null +++ b/Remotion/Data/DomainObjects.UnitTests/IntegrationTests/Unload/UnloadFilteredDomainObjectsCommantTest.cs @@ -0,0 +1,373 @@ +// // 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.Linq; +using NUnit.Framework; +using Remotion.Data.DomainObjects.DomainImplementation; +using Remotion.Data.DomainObjects.UnitTests.TestDomain; +using Remotion.TypePipe; + +namespace Remotion.Data.DomainObjects.UnitTests.IntegrationTests.Unload +{ + [TestFixture] + public class UnloadFilteredTest: ClientTransactionBaseTest + { + [Test] + public void UnloadFiltered_WithAllObjects_SingleDomainObject_WithoutChanges () + { + var order = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(order.State.IsNotLoadedYet, Is.True); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithOneToOneRelation_WithoutChanges () + { + var order = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + order.OrderTicket.EnsureDataAvailable(); + + var orderTicket = order.OrderTicket; + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(order.State.IsNotLoadedYet, Is.True); + Assert.That(orderTicket.State.IsNotLoadedYet, Is.True); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithOneToOneRelation_WithChanges () + { + var order1 = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + var order2 = (Order)LifetimeService.NewObject(TestableClientTransaction,typeof(Order), ParamList.Empty); + order1.OrderTicket.EnsureDataAvailable(); + + var orderTicket = order1.OrderTicket; + orderTicket.Order = order2; + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(order1.State.IsNotLoadedYet, Is.True); + Assert.That(order1.State.IsRelationChanged, Is.False); + Assert.That(order2.State.IsInvalid, Is.True); + Assert.That(order2.State.IsRelationChanged, Is.False); + Assert.That(orderTicket.State.IsNotLoadedYet, Is.True); + Assert.That(orderTicket.State.IsRelationChanged, Is.False); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithOneToOneRelation_WithChanges_IncludeOnlyForeignKeySide_ThrowsInvalidOperationException () + { + var order1 = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + var order2 = (Order)LifetimeService.NewObject(TestableClientTransaction,typeof(Order), ParamList.Empty); + order1.OrderTicket.EnsureDataAvailable(); + + var orderTicket = order1.OrderTicket; + orderTicket.Order = order2; + + Assert.That(order1.State.IsChanged, Is.True); + Assert.That(order1.State.IsRelationChanged, Is.True); + Assert.That(order2.State.IsNew, Is.True); + Assert.That(order2.State.IsRelationChanged, Is.True); + Assert.That(orderTicket.State.IsChanged, Is.True); + Assert.That(orderTicket.State.IsDataChanged, Is.True); + Assert.That(orderTicket.State.IsRelationChanged, Is.True); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + Assert.That( + () => UnloadFiltered(obj => obj == orderTicket), + Throws.InvalidOperationException + .With.Message.EqualTo( + "Cannot unload the following relation endpoints because the associated domain objects have not been included in the data set.\r\n" + + orderTicket.ID + "/Remotion.Data.DomainObjects.UnitTests.TestDomain.OrderTicket.Order,\r\n" + + order2.ID + "/Remotion.Data.DomainObjects.UnitTests.TestDomain.Order.OrderTicket")); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithOneToOneRelation_WithChanges_IncludeVirtualSide () + { + var order1 = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + var order2 = (Order)LifetimeService.NewObject(TestableClientTransaction,typeof(Order), ParamList.Empty); + order1.OrderTicket.EnsureDataAvailable(); + + var orderTicket = order1.OrderTicket; + orderTicket.Order = order2; + + Assert.That(order1.State.IsChanged, Is.True); + Assert.That(order1.State.IsRelationChanged, Is.True); + Assert.That(order2.State.IsNew, Is.True); + Assert.That(order2.State.IsRelationChanged, Is.True); + Assert.That(orderTicket.State.IsChanged, Is.True); + Assert.That(orderTicket.State.IsDataChanged, Is.True); + Assert.That(orderTicket.State.IsRelationChanged, Is.True); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + var orderTicketEndPoints = TestableClientTransaction.DataManager.RelationEndPoints.Where(ep => ep.Definition.ClassDefinition.ClassType == typeof(OrderTicket)).ToArray(); + + UnloadFiltered(obj => obj == order1 || obj == order2); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.DataContainers.Select(dc => dc.DomainObject), Has.No.AnyOf(orderTicket)); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints.Select(ep => ep.Definition), Has.No.AnyOf(orderTicketEndPoints)); + + Assert.That(order1.State.IsUnchanged, Is.True); + Assert.That(order1.State.IsRelationChanged, Is.False); + Assert.That(order2.State.IsNew, Is.True); + Assert.That(order2.State.IsRelationChanged, Is.False); + Assert.That(orderTicket.State.IsNotLoadedYet, Is.True); + Assert.That(orderTicket.State.IsRelationChanged, Is.False); + + orderTicket.EnsureDataAvailable(); + Assert.That(orderTicket.Order, Is.SameAs(order1)); + Assert.That(order2.OrderTicket, Is.Null); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithDomainObjectCollectionRelation_WithoutChanges () + { + var order = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + order.OrderItems.EnsureDataComplete(); + + var orderItem = order.OrderItems.First(); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(order.State.IsNotLoadedYet, Is.True); + Assert.That(order.OrderItems.IsDataComplete, Is.False); + Assert.That(orderItem.State.IsNotLoadedYet, Is.True); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithDomainObjectCollectionRelation_WithChanges () + { + var order = (Order)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Order1, false); + order.OrderItems.EnsureDataComplete(); + + var orderItem = order.OrderItems.First(); + orderItem.Order = null; + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(order.State.IsNotLoadedYet, Is.True); + Assert.That(order.OrderItems.IsDataComplete, Is.False); + Assert.That(order.State.IsRelationChanged, Is.False); + Assert.That(orderItem.State.IsNotLoadedYet, Is.True); + Assert.That(orderItem.State.IsRelationChanged, Is.False); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithVirtualCollectionRelation_WithoutChanges () + { + var product = (Product)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Product1, false); + product.Reviews.EnsureDataComplete(); + + var productReview = product.Reviews.First(); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(product.State.IsNotLoadedYet, Is.True); + Assert.That(product.Reviews.IsDataComplete, Is.False); + Assert.That(productReview.State.IsNotLoadedYet, Is.True); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithVirtualCollectionRelation_WithChanges () + { + var product = (Product)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Product1, false); + product.Reviews.EnsureDataComplete(); + + var productReview = product.Reviews.First(); + productReview.Product = null; + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(product.State.IsNotLoadedYet, Is.True); + Assert.That(product.Reviews.IsDataComplete, Is.False); + Assert.That(product.State.IsRelationChanged, Is.False); + Assert.That(productReview.State.IsNotLoadedYet, Is.True); + Assert.That(productReview.State.IsRelationChanged, Is.False); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithAnonymousRelation_WithoutChanges () + { + var location = (Location)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Location1, false); + + var client = location.Client; + client.EnsureDataAvailable(); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(location.State.IsNotLoadedYet, Is.True); + Assert.That(client.State.IsNotLoadedYet, Is.True); + } + + [Test] + public void UnloadFiltered_WithAllObjects_WithAnonymousRelation_WithChanges () + { + var location = (Location)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Location1, false); + + var client = location.Client; + client.EnsureDataAvailable(); + location.Client = null; + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + + UnloadFiltered(obj => true); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Empty); + + Assert.That(location.State.IsNotLoadedYet, Is.True); + Assert.That(location.State.IsRelationChanged, Is.False); + Assert.That(client.State.IsNotLoadedYet, Is.True); + Assert.That(client.State.IsRelationChanged, Is.False); + } + + [Test] + public void UnloadFiltered_WithSomeObjects_WithAnonymousRelation_WithChanges_IncludeOnlyForeignKeySide () + { + var location = (Location)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Location1, false); + + var client = location.Client; + client.EnsureDataAvailable(); + location.Client = null; + + Assert.That(location.State.IsChanged, Is.True); + Assert.That(location.State.IsRelationChanged, Is.True); + Assert.That(client.State.IsUnchanged, Is.True); + Assert.That(client.State.IsRelationChanged, Is.False); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + var locationEndPoints = TestableClientTransaction.DataManager.RelationEndPoints.Where(ep => ep.Definition.ClassDefinition.ClassType == typeof(Location)).ToArray(); + + UnloadFiltered(obj => obj == location); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.DataContainers.Select(dc => dc.DomainObject), Has.No.AnyOf(location)); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints.Select(ep => ep.Definition), Has.No.AnyOf(locationEndPoints)); + + Assert.That(location.State.IsNotLoadedYet, Is.True); + Assert.That(location.State.IsRelationChanged, Is.False); + Assert.That(client.State.IsUnchanged, Is.True); + Assert.That(client.State.IsRelationChanged, Is.False); + + location.EnsureDataAvailable(); + Assert.That(location.Client, Is.SameAs(client)); + } + + [Test] + public void UnloadFiltered_WithSomeObjects_WithAnonymousRelation_WithChanges_IncludeOnlyAnonymousSide () + { + var location = (Location)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Location1, false); + + var client = location.Client; + client.EnsureDataAvailable(); + location.Client = null; + + Assert.That(location.State.IsChanged, Is.True); + Assert.That(location.State.IsDataChanged, Is.True); + Assert.That(location.State.IsRelationChanged, Is.True); + Assert.That(client.State.IsUnchanged, Is.True); + Assert.That(client.State.IsRelationChanged, Is.False); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + var clientEndPoints = TestableClientTransaction.DataManager.RelationEndPoints.Where(ep => ep.Definition.ClassDefinition.ClassType == typeof(Client)).ToArray(); + + UnloadFiltered(obj => obj == client); + + Assert.That(TestableClientTransaction.DataManager.DataContainers, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.DataContainers.Select(dc => dc.DomainObject), Has.No.AnyOf(client)); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints, Is.Not.Empty); + Assert.That(TestableClientTransaction.DataManager.RelationEndPoints.Select(ep => ep.Definition), Has.No.AnyOf(clientEndPoints)); + + Assert.That(location.State.IsChanged, Is.True); + Assert.That(location.State.IsDataChanged, Is.True); + Assert.That(location.State.IsRelationChanged, Is.True); + Assert.That(client.State.IsNotLoadedYet, Is.True); + Assert.That(client.State.IsRelationChanged, Is.False); + + var client2 = (Client)LifetimeService.GetObject(TestableClientTransaction, DomainObjectIDs.Client2, false); + Assert.That(client2.ID, Is.Not.EqualTo(client.ID)); + location.Client = client2; + Assert.That(location.Client, Is.EqualTo(client2)); + } + + + private void UnloadFiltered (Predicate domainObjectFilter) + { + UnloadService.UnloadFiltered(TestableClientTransaction, domainObjectFilter); + } + } +} diff --git a/Remotion/Data/DomainObjects.UnitTests/TestableClientTransaction.cs b/Remotion/Data/DomainObjects.UnitTests/TestableClientTransaction.cs index 7486e901ba..7a543bed45 100644 --- a/Remotion/Data/DomainObjects.UnitTests/TestableClientTransaction.cs +++ b/Remotion/Data/DomainObjects.UnitTests/TestableClientTransaction.cs @@ -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) diff --git a/Remotion/Data/DomainObjects/DataManagement/Commands/UnloadFilteredDomainObjectsCommand.cs b/Remotion/Data/DomainObjects/DataManagement/Commands/UnloadFilteredDomainObjectsCommand.cs new file mode 100644 index 0000000000..a6345e29fb --- /dev/null +++ b/Remotion/Data/DomainObjects/DataManagement/Commands/UnloadFilteredDomainObjectsCommand.cs @@ -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 DomainObjectFilter { get; } + private IReadOnlyCollection _dataContainersForUnloading = Array.Empty(); + private IReadOnlyCollection _virtualEndPointsForUnloading = Array.Empty(); + private IReadOnlyCollection _realObjectEndPointsForUnloading = Array.Empty(); + private List _exceptions = new List(); + + public UnloadFilteredDomainObjectsCommand ( + DataContainerMap dataContainerMap, + IInvalidDomainObjectManager invalidDomainObjectManager, + RelationEndPointMap relationEndPointMap, + IClientTransactionEventSink transactionEventSink, + Predicate 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 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 RealObjectEndPoints, + HashSet VirtualEndPoints, + HashSet RealObjectEndPointsNotPartOfUnloadSet, + HashSet VirtualEndPointsNotPartOfUnloadSet) + GetRelationEndPointsForUnloading (IReadOnlyCollection dataContainers) + { + var realObjectEndPoints = new HashSet(); + var virtualObjectEndPoints = new HashSet(); + var realObjectEndPointsNotPartOfUnloadSet = new HashSet(); + var virtualEndPointsNotPartOfUnloadSet = new HashSet(); + 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)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; + } + } +} diff --git a/Remotion/Data/DomainObjects/DataManagement/DataManager.cs b/Remotion/Data/DomainObjects/DataManagement/DataManager.cs index f562c587f9..80d8a49ca7 100644 --- a/Remotion/Data/DomainObjects/DataManagement/DataManager.cs +++ b/Remotion/Data/DomainObjects/DataManagement/DataManager.cs @@ -444,6 +444,19 @@ public IDataManagementCommand CreateUnloadAllCommand () return new UnloadAllCommand(_relationEndPointManager, _dataContainerMap, _invalidDomainObjectManager, _transactionEventSink); } + public IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate 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)); diff --git a/Remotion/Data/DomainObjects/DataManagement/DelegatingDataManager.cs b/Remotion/Data/DomainObjects/DataManagement/DelegatingDataManager.cs index 4de9fd561f..bed54411e8 100644 --- a/Remotion/Data/DomainObjects/DataManagement/DelegatingDataManager.cs +++ b/Remotion/Data/DomainObjects/DataManagement/DelegatingDataManager.cs @@ -136,6 +136,11 @@ public IDataManagementCommand CreateUnloadAllCommand () return SafeInnerDataManager.CreateUnloadAllCommand(); } + public IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate domainObjectFilter) + { + return SafeInnerDataManager.CreateUnloadFilteredDomainObjectsCommand(domainObjectFilter); + } + public void LoadLazyCollectionEndPoint (RelationEndPointID endPointID) { SafeInnerDataManager.LoadLazyCollectionEndPoint(endPointID); diff --git a/Remotion/Data/DomainObjects/DataManagement/IDataManager.cs b/Remotion/Data/DomainObjects/DataManagement/IDataManager.cs index 436fdca023..d385c50f59 100644 --- a/Remotion/Data/DomainObjects/DataManagement/IDataManager.cs +++ b/Remotion/Data/DomainObjects/DataManagement/IDataManager.cs @@ -49,5 +49,6 @@ public interface IDataManager : IRelationEndPointProvider, ILoadedDataContainerP IDataManagementCommand CreateUnloadCommand (params ObjectID[] objectIDs); IDataManagementCommand CreateUnloadVirtualEndPointsCommand (params RelationEndPointID[] endPointIDs); IDataManagementCommand CreateUnloadAllCommand (); + IDataManagementCommand CreateUnloadFilteredDomainObjectsCommand (Predicate domainObjectFilter); } } diff --git a/Remotion/Data/DomainObjects/DomainImplementation/UnloadService.cs b/Remotion/Data/DomainObjects/DomainImplementation/UnloadService.cs index f6e3f1130c..6420a23c7e 100644 --- a/Remotion/Data/DomainObjects/DomainImplementation/UnloadService.cs +++ b/Remotion/Data/DomainObjects/DomainImplementation/UnloadService.cs @@ -280,6 +280,16 @@ public static void UnloadAll (ClientTransaction clientTransaction) executor.ExecuteCommandForTransactionHierarchy(clientTransaction); } + public static void UnloadFiltered (ClientTransaction clientTransaction, Predicate domainObjectFilter) + { + ArgumentUtility.CheckNotNull("clientTransaction", clientTransaction); + ArgumentUtility.CheckNotNull("domainObjectFilter", domainObjectFilter); + + Func commandFactory = tx => tx.DataManager.CreateUnloadFilteredDomainObjectsCommand(domainObjectFilter); + var executor = new TransactionHierarchyCommandExecutor(commandFactory); + executor.ExecuteCommandForTransactionHierarchy(clientTransaction); + } + private static void CheckVirtualEndPointID (RelationEndPointID endPointID) { if (!endPointID.Definition.IsVirtual)