From d5e711a633f05413fdca6670092715e9b494b2f9 Mon Sep 17 00:00:00 2001 From: Einar Ingebrigtsen Date: Tue, 20 Aug 2024 15:18:10 +0200 Subject: [PATCH] Limiting Joins on children to be oriented around the IdentifiedBy property - supporting arbitrary On() will be in conflict with how we identify the children today --- .../GroupProjection.cs | 1 - .../GroupProjectionWithMultipleJoins.cs | 2 -- .../UserProjection.cs | 1 - .../DotNET/Projections/ChildrenBuilder.cs | 7 ++++ .../DotNET/Projections/IChildrenBuilder.cs | 19 ++++++++++- .../Clients/DotNET/Projections/JoinBuilder.cs | 33 +++++++++++++++---- ...yPropertyExpressionWhenJoiningWithEvent.cs | 12 +++++++ ...nPropertyExpressionWhenJoiningWithEvent.cs | 3 -- ...ropertyShouldNotBeSpecifiedForChildJoin.cs | 12 +++++++ 9 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 Source/Clients/DotNET/Projections/MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent.cs create mode 100644 Source/Clients/DotNET/Projections/OnPropertyShouldNotBeSpecifiedForChildJoin.cs diff --git a/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjection.cs b/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjection.cs index 2c75a8bd8..df69e740a 100644 --- a/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjection.cs +++ b/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjection.cs @@ -14,6 +14,5 @@ public void Define(IProjectionBuilderFor builder) => builder .From(b => b .UsingKey(e => e.UserId)) .Join(j => j - .On(u => u.UserId) .Set(m => m.Name).To(e => e.Name))); } diff --git a/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjectionWithMultipleJoins.cs b/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjectionWithMultipleJoins.cs index d515ba1ab..0a7c553fe 100644 --- a/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjectionWithMultipleJoins.cs +++ b/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/GroupProjectionWithMultipleJoins.cs @@ -14,9 +14,7 @@ public void Define(IProjectionBuilderFor builder) => builder .From(b => b .UsingKey(e => e.UserId)) .Join(j => j - .On(u => u.UserId) .Set(m => m.Name).To(e => e.Name)) .Join(j => j - .On(u => u.UserId) .Set(m => m.Name).To(e => e.Name))); } diff --git a/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/UserProjection.cs b/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/UserProjection.cs index a48b89e5f..ab8fe39a7 100644 --- a/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/UserProjection.cs +++ b/Integration/Orleans.InProcess/Projections/Scenarios/when_projecting_with_join_for_children/UserProjection.cs @@ -15,6 +15,5 @@ public void Define(IProjectionBuilderFor builder) => builder .From(b => b .UsingParentKey(e => e.UserId)) .Join(j => j - .On(g => g.GroupId) .Set(m => m.GroupName).To(e => e.Name))); } diff --git a/Source/Clients/DotNET/Projections/ChildrenBuilder.cs b/Source/Clients/DotNET/Projections/ChildrenBuilder.cs index 1b2e20668..24a3c8626 100644 --- a/Source/Clients/DotNET/Projections/ChildrenBuilder.cs +++ b/Source/Clients/DotNET/Projections/ChildrenBuilder.cs @@ -37,8 +37,15 @@ public class ChildrenBuilder( // TODO: This is not used, but it should be - figure out what the purpose was. The FromEventProperty method is called from ModelPropertiesBuilder EventType? _fromEventPropertyEventType; IEventValueExpression? _fromEventPropertyExpression; + #pragma warning restore IDE0052 // Remove unread private members + /// + public bool HasIdentifiedBy => _identifiedBy.IsSet; + + /// + public PropertyPath GetIdentifiedBy() => _identifiedBy; + /// public IChildrenBuilder IdentifiedBy(PropertyPath propertyPath) { diff --git a/Source/Clients/DotNET/Projections/IChildrenBuilder.cs b/Source/Clients/DotNET/Projections/IChildrenBuilder.cs index c9000e3d8..5a11040c5 100644 --- a/Source/Clients/DotNET/Projections/IChildrenBuilder.cs +++ b/Source/Clients/DotNET/Projections/IChildrenBuilder.cs @@ -7,12 +7,29 @@ namespace Cratis.Chronicle.Projections; +/// +/// Defines the base interface for children builders. +/// +public interface IChildrenBuilder +{ + /// + /// Gets whether or not the builder has identified by. + /// + bool HasIdentifiedBy { get; } + + /// + /// Gets the property path that identifies the child model in the collection within the parent. + /// + /// for the identified by. + PropertyPath GetIdentifiedBy(); +} + /// /// Defines the builder for building out a child relationship on a model. /// /// Parent model type. /// Child model type. -public interface IChildrenBuilder : IProjectionBuilder> +public interface IChildrenBuilder : IProjectionBuilder>, IChildrenBuilder { /// /// Sets the property that identifies the child model in the collection within the parent. diff --git a/Source/Clients/DotNET/Projections/JoinBuilder.cs b/Source/Clients/DotNET/Projections/JoinBuilder.cs index 2c6d8412e..73d6f8d58 100644 --- a/Source/Clients/DotNET/Projections/JoinBuilder.cs +++ b/Source/Clients/DotNET/Projections/JoinBuilder.cs @@ -13,14 +13,12 @@ namespace Cratis.Chronicle.Projections; /// Model to build for. /// Event to build for. /// Type of parent builder. -/// -/// Initializes a new instance of the . -/// /// The parent . public class JoinBuilder(IProjectionBuilder projectionBuilder) : ModelPropertiesBuilder, TParentBuilder>(projectionBuilder), IJoinBuilder where TParentBuilder : class { + readonly IProjectionBuilder _projectionBuilder = projectionBuilder; PropertyPath? _on; /// @@ -33,7 +31,14 @@ public IJoinBuilder On(Expression public JoinDefinition Build() { - ThrowIfMissingOn(); + ThrowIfMissingOnForRootProjection(); + ThrowIfOnSpecifiedForChildProjection(); + ThrowIfMissingIdentifiedByForChildProjection(); + + if (_on is null && _projectionBuilder is IChildrenBuilder childrenBuilder) + { + _on = childrenBuilder.GetIdentifiedBy(); + } return new() { @@ -43,11 +48,27 @@ public JoinDefinition Build() }; } - void ThrowIfMissingOn() + void ThrowIfMissingOnForRootProjection() { - if (_on is null) + if (_on is null && _projectionBuilder is not IChildrenBuilder) { throw new MissingOnPropertyExpressionWhenJoiningWithEvent(typeof(TModel), typeof(TEvent)); } } + + void ThrowIfOnSpecifiedForChildProjection() + { + if (_on is not null && _projectionBuilder is IChildrenBuilder) + { + throw new OnPropertyShouldNotBeSpecifiedForChildJoin(typeof(TModel), typeof(TEvent)); + } + } + + void ThrowIfMissingIdentifiedByForChildProjection() + { + if (_on is null && _projectionBuilder is IChildrenBuilder childrenBuilder && !childrenBuilder.HasIdentifiedBy) + { + throw new MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent(typeof(TModel), typeof(TEvent)); + } + } } diff --git a/Source/Clients/DotNET/Projections/MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent.cs b/Source/Clients/DotNET/Projections/MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent.cs new file mode 100644 index 000000000..165e90840 --- /dev/null +++ b/Source/Clients/DotNET/Projections/MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent.cs @@ -0,0 +1,12 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Chronicle.Projections; + +/// +/// Exception that is thrown when the on property expression is missing for the root projection. +/// +/// Type of model joining with event. +/// Type of event that is being joined. +public class MissingIdentifiedByPropertyExpressionWhenJoiningWithEvent(Type modelType, Type eventType) + : Exception($"Missing the identified by property expression for model of type '{modelType.FullName}' when joining with event of type '{eventType.FullName}'. Use the `.IdentifiedBy()` method on the child builder."); diff --git a/Source/Clients/DotNET/Projections/MissingOnPropertyExpressionWhenJoiningWithEvent.cs b/Source/Clients/DotNET/Projections/MissingOnPropertyExpressionWhenJoiningWithEvent.cs index 863cbf65e..7b2b15a91 100644 --- a/Source/Clients/DotNET/Projections/MissingOnPropertyExpressionWhenJoiningWithEvent.cs +++ b/Source/Clients/DotNET/Projections/MissingOnPropertyExpressionWhenJoiningWithEvent.cs @@ -6,9 +6,6 @@ namespace Cratis.Chronicle.Projections; /// /// Exception that gets thrown when missing the on property expression when joining with specific event type. /// -/// -/// Initializes a new instance of the class. -/// /// Type of model joining with event. /// Type of event that is being joined. public class MissingOnPropertyExpressionWhenJoiningWithEvent(Type modelType, Type eventType) diff --git a/Source/Clients/DotNET/Projections/OnPropertyShouldNotBeSpecifiedForChildJoin.cs b/Source/Clients/DotNET/Projections/OnPropertyShouldNotBeSpecifiedForChildJoin.cs new file mode 100644 index 000000000..cdd1142a3 --- /dev/null +++ b/Source/Clients/DotNET/Projections/OnPropertyShouldNotBeSpecifiedForChildJoin.cs @@ -0,0 +1,12 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Cratis.Chronicle.Projections; + +/// +/// Exception that gets thrown when on property should not be specified for child join. +/// +/// Type of model joining with event. +/// Type of event that is being joined. +public class OnPropertyShouldNotBeSpecifiedForChildJoin(Type modelType, Type eventType) + : Exception($"On property should not be specified for child join for model of type '{modelType.FullName}' when joining with event of type '{eventType.FullName}', it will implicitly use what is set using the `IdentifiedBy()");