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

Cherry pick: Fix for #3581, Ensure unique identity values within identity managed collections #4335

Merged
merged 1 commit into from
Nov 22, 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
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
Loading