diff --git a/Source/Csla.test/GraphMerge/BranchUniqueIdentities.cs b/Source/Csla.test/GraphMerge/BranchUniqueIdentities.cs
new file mode 100644
index 0000000000..85a52040ec
--- /dev/null
+++ b/Source/Csla.test/GraphMerge/BranchUniqueIdentities.cs
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// no summary
+//-----------------------------------------------------------------------
+
+namespace Csla.Test.GraphMerge
+{
+ internal class BranchUniqueIdentities : BusinessBase
+ {
+ public static readonly PropertyInfo IdProperty = RegisterProperty(nameof(Id));
+ public Guid Id
+ {
+ get => GetProperty(IdProperty);
+ private set => SetProperty(IdProperty, value);
+ }
+
+ public static readonly PropertyInfo LeafsProperty = RegisterProperty(nameof(Leafs));
+ public LeafsUniqueIdentities Leafs
+ {
+ get => GetProperty(LeafsProperty);
+ private set => SetProperty(LeafsProperty, value);
+ }
+
+ [FetchChild]
+ private async void Create([Inject] IChildDataPortal leafsPortal)
+ {
+ using (BypassPropertyChecks)
+ {
+ Id = Guid.NewGuid();
+
+ Leafs = await leafsPortal.FetchChildAsync();
+ }
+ }
+
+ [InsertChild]
+ private async Task Insert()
+ {
+ await FieldManager.UpdateChildrenAsync();
+ }
+
+ [UpdateChild]
+ private void Update()
+ {
+ FieldManager.UpdateChildren();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/Csla.test/GraphMerge/GraphMergerTests.cs b/Source/Csla.test/GraphMerge/GraphMergerTests.cs
index 3132abc192..01c65c731a 100644
--- a/Source/Csla.test/GraphMerge/GraphMergerTests.cs
+++ b/Source/Csla.test/GraphMerge/GraphMergerTests.cs
@@ -8,6 +8,7 @@
using Csla.Core;
using Csla.TestHelpers;
+using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -17,6 +18,8 @@ namespace Csla.Test.GraphMerge
public class GraphMergerTests
{
private static TestDIContext _testDIContext;
+ private ApplicationContext _applicationContext;
+ private GraphMerger _systemUnderTest;
[ClassInitialize]
public static void ClassInitialize(TestContext context)
@@ -28,6 +31,8 @@ public static void ClassInitialize(TestContext context)
public void Initialize()
{
TestResults.Reinitialise();
+ _applicationContext = _testDIContext.CreateTestApplicationContext();
+ _systemUnderTest = new GraphMerger(_applicationContext);
}
[TestMethod]
@@ -363,5 +368,18 @@ public void MergeChildList()
Assert.IsTrue(ReferenceEquals(target.ChildList, target.ChildList[1].Parent), "parent ref");
}
+ [TestMethod]
+ public async Task MergeChildsAtDepth2Correctly()
+ {
+ var root = await _testDIContext.CreateDataPortal().FetchAsync();
+
+ root.Branch.Leafs.AddNew().LeafId = 1337;
+
+ var allLeafIds = root.Branch.Leafs.Select(l => l.LeafId).ToList();
+
+ await root.SaveAndMergeAsync();
+
+ root.Branch.Leafs.Select(l => l.LeafId).Should().ContainInOrder(allLeafIds);
+ }
}
}
diff --git a/Source/Csla.test/GraphMerge/IdentityTests.cs b/Source/Csla.test/GraphMerge/IdentityTests.cs
index 6da2a1a25a..4e13d82421 100644
--- a/Source/Csla.test/GraphMerge/IdentityTests.cs
+++ b/Source/Csla.test/GraphMerge/IdentityTests.cs
@@ -8,6 +8,7 @@
using Csla.Core;
using Csla.TestHelpers;
+using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -137,5 +138,16 @@ public void IdentityInitializedDynamicBindingListBase()
var obj = dataPortal.Create();
Assert.IsTrue(((IBusinessObject)obj).Identity >= 0);
}
+
+ [TestMethod]
+ public async Task Identity_WhenAddingANewListItemAfterFetchTheIdentityWithinTheListMustBeUnique()
+ {
+ var root = await _testDIContext.CreateDataPortal().FetchAsync();
+
+ var newItem = root.Branch.Leafs.AddNew();
+ newItem.LeafId = 1337;
+
+ root.Branch.Leafs.Should().OnlyHaveUniqueItems(l => ((IBusinessObject)l).Identity);
+ }
}
}
diff --git a/Source/Csla.test/GraphMerge/LeafUniqueIdentities.cs b/Source/Csla.test/GraphMerge/LeafUniqueIdentities.cs
new file mode 100644
index 0000000000..bd2241654c
--- /dev/null
+++ b/Source/Csla.test/GraphMerge/LeafUniqueIdentities.cs
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// no summary
+//-----------------------------------------------------------------------
+
+namespace Csla.Test.GraphMerge
+{
+ internal class LeafUniqueIdentities : BusinessBase
+ {
+ public static readonly PropertyInfo LeafIdProperty = RegisterProperty(nameof(LeafId));
+ public int LeafId
+ {
+ get => GetProperty(LeafIdProperty);
+ set => SetProperty(LeafIdProperty, value);
+ }
+
+ [Create]
+ [CreateChild]
+ private async Task Create(int leafId)
+ {
+ using (BypassPropertyChecks)
+ {
+ LeafId = leafId;
+ }
+
+ await BusinessRules.CheckRulesAsync();
+ }
+
+ [InsertChild]
+ private void Insert() { }
+
+ [FetchChild]
+ private void Fetch(int id)
+ {
+ using (BypassPropertyChecks)
+ {
+ LeafId = id;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/Csla.test/GraphMerge/LeafsUniqueIdentities.cs b/Source/Csla.test/GraphMerge/LeafsUniqueIdentities.cs
new file mode 100644
index 0000000000..a9682e4a86
--- /dev/null
+++ b/Source/Csla.test/GraphMerge/LeafsUniqueIdentities.cs
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// no summary
+//-----------------------------------------------------------------------
+
+namespace Csla.Test.GraphMerge
+{
+ internal class LeafsUniqueIdentities : BusinessListBase
+ {
+ [FetchChild]
+ private async void Fetch([Inject] IChildDataPortal childDataPortal)
+ {
+ using (LoadListMode)
+ {
+ foreach (var id in Enumerable.Range(1, 5))
+ {
+ Add(await childDataPortal.FetchChildAsync(id));
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Csla.test/GraphMerge/RootUniqueIdentities.cs b/Source/Csla.test/GraphMerge/RootUniqueIdentities.cs
new file mode 100644
index 0000000000..6a46e164bb
--- /dev/null
+++ b/Source/Csla.test/GraphMerge/RootUniqueIdentities.cs
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Marimer LLC. All rights reserved.
+// Website: https://cslanet.com
+//
+// no summary
+//-----------------------------------------------------------------------
+
+namespace Csla.Test.GraphMerge
+{
+ internal class RootUniqueIdentities : BusinessBase
+ {
+ public static readonly PropertyInfo IdProperty = RegisterProperty(nameof(Id));
+ public Guid Id
+ {
+ get => GetProperty(IdProperty);
+ private set => SetProperty(IdProperty, value);
+ }
+
+ public static readonly PropertyInfo BranchProperty = RegisterProperty(nameof(Branch));
+ public BranchUniqueIdentities Branch
+ {
+ get => GetProperty(BranchProperty);
+ private set => SetProperty(BranchProperty, value);
+ }
+
+ [Fetch]
+ private async void Create([Inject] IChildDataPortal portalBranch)
+ {
+ using (BypassPropertyChecks)
+ {
+ Id = Guid.NewGuid();
+
+ Branch = await portalBranch.FetchChildAsync();
+ }
+ }
+
+ [Insert]
+ private async Task Insert()
+ {
+ await FieldManager.UpdateChildrenAsync();
+ }
+
+ [Update]
+ private async Task Update()
+ {
+ await FieldManager.UpdateChildrenAsync();
+ }
+ }
+}
diff --git a/Source/Csla/BusinessBindingListBase.cs b/Source/Csla/BusinessBindingListBase.cs
index c58b96ee59..bd599e10fb 100644
--- a/Source/Csla/BusinessBindingListBase.cs
+++ b/Source/Csla/BusinessBindingListBase.cs
@@ -338,6 +338,8 @@ protected override void InsertItem(int index, C item)
{
if (item.IsChild)
{
+ IdentityManager.EnsureNextIdentityValueIsUnique(this, this);
+
// set parent reference
item.SetParent(this);
// ensure child uses same context as parent
diff --git a/Source/Csla/BusinessListBase.cs b/Source/Csla/BusinessListBase.cs
index 87021e2e49..8b5a815609 100644
--- a/Source/Csla/BusinessListBase.cs
+++ b/Source/Csla/BusinessListBase.cs
@@ -342,6 +342,8 @@ protected override void InsertItem(int index, C item)
{
if (item.IsChild)
{
+ IdentityManager.EnsureNextIdentityValueIsUnique(this, this);
+
// set parent reference
item.SetParent(this);
// ensure child uses same context as parent
diff --git a/Source/Csla/Core/IdentityManager.cs b/Source/Csla/Core/IdentityManager.cs
index 0b9df2085e..b63f49226d 100644
--- a/Source/Csla/Core/IdentityManager.cs
+++ b/Source/Csla/Core/IdentityManager.cs
@@ -45,5 +45,23 @@ public int GetNextIdentity(int? current = null)
}
return result;
}
+
+ ///
+ /// Ensures that the internal value of is greater than the greatest within the given collection.
+ /// That ensures that new object get a unique identity within the collection.
+ ///
+ /// Item type of the list
+ ///
+ ///
+ internal static void EnsureNextIdentityValueIsUnique(IParent parent, IReadOnlyCollection items) where T : IBusinessObject
+ {
+ // No items means we do not have to worry about any identity duplicates
+ if (items.Count == 0)
+ {
+ return;
+ }
+
+ _ = parent.GetNextIdentity(items.Max(c => c.Identity));
+ }
}
}
\ No newline at end of file
diff --git a/Source/Csla/DynamicBindingListBase.cs b/Source/Csla/DynamicBindingListBase.cs
index 080a573b61..3f4c155b1f 100644
--- a/Source/Csla/DynamicBindingListBase.cs
+++ b/Source/Csla/DynamicBindingListBase.cs
@@ -283,6 +283,7 @@ protected override object AddNewCore()
/// Item to insert.
protected override void InsertItem(int index, T item)
{
+ IdentityManager.EnsureNextIdentityValueIsUnique(this, this);
item.SetParent(this);
base.InsertItem(index, item);
}
diff --git a/Source/Csla/DynamicListBase.cs b/Source/Csla/DynamicListBase.cs
index b2ec4e1dc6..d2ae926a1c 100644
--- a/Source/Csla/DynamicListBase.cs
+++ b/Source/Csla/DynamicListBase.cs
@@ -316,6 +316,7 @@ protected override T AddNewCore()
/// Item to insert.
protected override void InsertItem(int index, T item)
{
+ IdentityManager.EnsureNextIdentityValueIsUnique(this, this);
item.SetParent(this);
// ensure child uses same context as parent
if (item is IUseApplicationContext iuac)