Skip to content

Commit

Permalink
Cherry pick: Ensures that before adding an item to a collection with …
Browse files Browse the repository at this point in the history
…identity management the next identity value will be unique within the collection. (#4335)

Fixes #3581
  • Loading branch information
StefanOssendorf authored Nov 22, 2024
1 parent 2f54bd5 commit a2ece22
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 0 deletions.
50 changes: 50 additions & 0 deletions Source/Csla.test/GraphMerge/BranchUniqueIdentities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//-----------------------------------------------------------------------
// <copyright file="GraphMergeTests.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>no summary</summary>
//-----------------------------------------------------------------------

namespace Csla.Test.GraphMerge
{
internal class BranchUniqueIdentities : BusinessBase<BranchUniqueIdentities>
{
public static readonly PropertyInfo<Guid> IdProperty = RegisterProperty<Guid>(nameof(Id));
public Guid Id
{
get => GetProperty(IdProperty);
private set => SetProperty(IdProperty, value);
}

public static readonly PropertyInfo<LeafsUniqueIdentities> LeafsProperty = RegisterProperty<LeafsUniqueIdentities>(nameof(Leafs));
public LeafsUniqueIdentities Leafs
{
get => GetProperty(LeafsProperty);
private set => SetProperty(LeafsProperty, value);
}

[FetchChild]
private async void Create([Inject] IChildDataPortal<LeafsUniqueIdentities> leafsPortal)
{
using (BypassPropertyChecks)
{
Id = Guid.NewGuid();

Leafs = await leafsPortal.FetchChildAsync();
}
}

[InsertChild]
private async Task Insert()
{
await FieldManager.UpdateChildrenAsync();
}

[UpdateChild]
private void Update()
{
FieldManager.UpdateChildren();
}
}
}
18 changes: 18 additions & 0 deletions Source/Csla.test/GraphMerge/GraphMergerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using Csla.Core;
using Csla.TestHelpers;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;


Expand All @@ -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)
Expand All @@ -28,6 +31,8 @@ public static void ClassInitialize(TestContext context)
public void Initialize()
{
TestResults.Reinitialise();
_applicationContext = _testDIContext.CreateTestApplicationContext();
_systemUnderTest = new GraphMerger(_applicationContext);
}

[TestMethod]
Expand Down Expand Up @@ -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<RootUniqueIdentities>().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);
}
}
}
12 changes: 12 additions & 0 deletions Source/Csla.test/GraphMerge/IdentityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using Csla.Core;
using Csla.TestHelpers;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;


Expand Down Expand Up @@ -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<RootUniqueIdentities>().FetchAsync();

var newItem = root.Branch.Leafs.AddNew();
newItem.LeafId = 1337;

root.Branch.Leafs.Should().OnlyHaveUniqueItems(l => ((IBusinessObject)l).Identity);
}
}
}
44 changes: 44 additions & 0 deletions Source/Csla.test/GraphMerge/LeafUniqueIdentities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//-----------------------------------------------------------------------
// <copyright file="GraphMergeTests.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>no summary</summary>
//-----------------------------------------------------------------------

namespace Csla.Test.GraphMerge
{
internal class LeafUniqueIdentities : BusinessBase<LeafUniqueIdentities>
{
public static readonly PropertyInfo<int> LeafIdProperty = RegisterProperty<int>(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;
}
}
}
}
25 changes: 25 additions & 0 deletions Source/Csla.test/GraphMerge/LeafsUniqueIdentities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//-----------------------------------------------------------------------
// <copyright file="GraphMergeTests.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>no summary</summary>
//-----------------------------------------------------------------------

namespace Csla.Test.GraphMerge
{
internal class LeafsUniqueIdentities : BusinessListBase<LeafsUniqueIdentities, LeafUniqueIdentities>
{
[FetchChild]
private async void Fetch([Inject] IChildDataPortal<LeafUniqueIdentities> childDataPortal)
{
using (LoadListMode)
{
foreach (var id in Enumerable.Range(1, 5))
{
Add(await childDataPortal.FetchChildAsync(id));
}
}
}
}
}
50 changes: 50 additions & 0 deletions Source/Csla.test/GraphMerge/RootUniqueIdentities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//-----------------------------------------------------------------------
// <copyright file="GraphMergeTests.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>no summary</summary>
//-----------------------------------------------------------------------

namespace Csla.Test.GraphMerge
{
internal class RootUniqueIdentities : BusinessBase<RootUniqueIdentities>
{
public static readonly PropertyInfo<Guid> IdProperty = RegisterProperty<Guid>(nameof(Id));
public Guid Id
{
get => GetProperty(IdProperty);
private set => SetProperty(IdProperty, value);
}

public static readonly PropertyInfo<BranchUniqueIdentities> BranchProperty = RegisterProperty<BranchUniqueIdentities>(nameof(Branch));
public BranchUniqueIdentities Branch
{
get => GetProperty(BranchProperty);
private set => SetProperty(BranchProperty, value);
}

[Fetch]
private async void Create([Inject] IChildDataPortal<BranchUniqueIdentities> 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();
}
}
}
2 changes: 2 additions & 0 deletions Source/Csla/BusinessBindingListBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Source/Csla/BusinessListBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions Source/Csla/Core/IdentityManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,23 @@ public int GetNextIdentity(int? current = null)
}
return result;
}

/// <summary>
/// Ensures that the internal value of <see cref="_nextIdentity"/> is greater than the greatest <see cref="IBusinessObject.Identity"/> within the given collection.
/// That ensures that new object get a unique identity within the collection.
/// </summary>
/// <typeparam name="T">Item type of the list</typeparam>
/// <param name="parent"></param>
/// <param name="items"></param>
internal static void EnsureNextIdentityValueIsUnique<T>(IParent parent, IReadOnlyCollection<T> 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));
}
}
}
1 change: 1 addition & 0 deletions Source/Csla/DynamicBindingListBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ protected override object AddNewCore()
/// <param name="item">Item to insert.</param>
protected override void InsertItem(int index, T item)
{
IdentityManager.EnsureNextIdentityValueIsUnique(this, this);
item.SetParent(this);
base.InsertItem(index, item);
}
Expand Down
1 change: 1 addition & 0 deletions Source/Csla/DynamicListBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ protected override T AddNewCore()
/// <param name="item">Item to insert.</param>
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)
Expand Down

0 comments on commit a2ece22

Please sign in to comment.