Skip to content

Commit

Permalink
Replace obsolete AssociationAttribute with new EntityAssociationAttri…
Browse files Browse the repository at this point in the history
…bute on client (#509)

Replace obsolete `AssociationAttribute` with new `EntityAssociationAttribute` on client

* The client now uses `EntityAssociationAttribute` internally for all logic
* The old `AssociationAttribute` is still discovered and mapped to an `EntityAssociationAttribute` in case an old version of the code generation has been used.
* Code generation has been updated to generate  `EntityAssociationAttribute`  instead of `AssociationAttribute`
  • Loading branch information
Daniel-Svensson authored Jun 9, 2024
1 parent 34d62dd commit 6293d33
Show file tree
Hide file tree
Showing 74 changed files with 1,285 additions and 613 deletions.
6 changes: 3 additions & 3 deletions src/OpenRiaServices.Client/Framework/ChangeSetBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ protected override void VisitEntityCollection(IEntityCollection entityCollection
/// </summary>
/// <param name="association">The association to check.</param>
/// <returns>The resulting collection of entries.</returns>
private IEnumerable<ChangeSetEntry> FindOriginalChildren(AssociationAttribute association)
private IEnumerable<ChangeSetEntry> FindOriginalChildren(EntityAssociationAttribute association)
{
foreach (ChangeSetEntry entry in this._changeSetEntries.Where(p => p.Entity.EntityState == EntityState.Deleted))
{
Expand Down Expand Up @@ -467,7 +467,7 @@ protected override void VisitEntityCollection(IEntityCollection entityCollection
// look for any invalid updates made to composed children
if (entityCollection.HasValues && member.IsComposition)
{
AssociationAttribute assoc = member.AssociationAttribute;
EntityAssociationAttribute assoc = member.AssociationAttribute;
foreach (Entity childEntity in entityCollection.Entities)
{
CheckInvalidChildUpdates(childEntity, assoc);
Expand Down Expand Up @@ -498,7 +498,7 @@ protected override void VisitEntityRef(IEntityRef entityRef, Entity parent, Meta
/// </summary>
/// <param name="entity">The child entity to check.</param>
/// <param name="compositionAttribute">The composition attribute.</param>
private static void CheckInvalidChildUpdates(Entity entity, AssociationAttribute compositionAttribute)
private static void CheckInvalidChildUpdates(Entity entity, EntityAssociationAttribute compositionAttribute)
{
if (compositionAttribute == null)
{
Expand Down
6 changes: 3 additions & 3 deletions src/OpenRiaServices.Client/Framework/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public abstract partial class Entity : IEditableObject, INotifyPropertyChanged,

private bool _trackChanges;
private Entity _parent;
private AssociationAttribute _parentAssociation;
private EntityAssociationAttribute _parentAssociation;
private bool _hasChildChanges;
private Dictionary<string, ComplexObject> _trackedInstances;
private MetaType _metaType;
Expand Down Expand Up @@ -86,7 +86,7 @@ internal Entity Parent
/// <summary>
/// Gets the parent association for this entity.
/// </summary>
internal AssociationAttribute ParentAssociation
internal EntityAssociationAttribute ParentAssociation
{
get
{
Expand Down Expand Up @@ -121,7 +121,7 @@ internal MetaType MetaType
/// </remarks>
/// <param name="parent">The parent.</param>
/// <param name="association">The parent association.</param>
internal void SetParent(Entity parent, AssociationAttribute association)
internal void SetParent(Entity parent, EntityAssociationAttribute association)
{
if (this._parent != parent)
{
Expand Down
69 changes: 69 additions & 0 deletions src/OpenRiaServices.Client/Framework/EntityAssociationAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;

#pragma warning disable CS3015 // Type has no accessible constructors which use only CLS-compliant types

namespace OpenRiaServices
{
/// <summary>
/// Used to mark an Entity member as an association
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]

public sealed class EntityAssociationAttribute : Attribute
#pragma warning restore CS3015 // Type has no accessible constructors which use only CLS-compliant types
{
/// <summary>
/// Full form of constructor
/// </summary>
/// <param name="name">The name of the association. For bi-directional associations,
/// the name must be the same on both sides of the association</param>
/// <param name="thisKey">List of the property names of the key values on this side of the association</param>
/// <param name="otherKey">List of the property names of the key values on the other side of the association</param>
public EntityAssociationAttribute(string name, string[] thisKey, string[] otherKey)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
ThisKeyMembers = thisKey ?? throw new ArgumentNullException(nameof(thisKey));
OtherKeyMembers = otherKey ?? throw new ArgumentNullException(nameof(otherKey));

if (name .Length == 0)
throw new ArgumentException("Name cannot be empty", nameof(name));
if (thisKey.Length == 0)
throw new ArgumentException("ThisKey cannot be empty", nameof(thisKey));
if (otherKey.Length == 0)
throw new ArgumentException("OtherKey cannot be empty", nameof(otherKey));
}

/// <summary>
/// Gets the name of the association. For bi-directional associations, the name must
/// be the same on both sides of the association
/// </summary>
public string Name { get; }

/// <summary>
/// Gets or sets a value indicating whether this association member represents
/// the foreign key side of an association
/// </summary>
public bool IsForeignKey { get; set; }

/// <summary>
/// Gets the collection of individual key members specified in the ThisKey string.
/// </summary>
public IReadOnlyCollection<string> ThisKeyMembers { get; }

/// <summary>
/// Gets the collection of individual key members specified in the OtherKey string.
/// </summary>
public IReadOnlyCollection<string> OtherKeyMembers { get; }

/// <summary>
/// Gets or sets the key value on this side of the association
/// </summary>
public string ThisKey => string.Join(",", ThisKeyMembers);

/// <summary>
/// <see langword="string"/> representation of the key value on the other side of the association
/// </summary>
public string OtherKey => string.Join(",", OtherKeyMembers);
}
}
6 changes: 3 additions & 3 deletions src/OpenRiaServices.Client/Framework/EntityCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public sealed class EntityCollection<TEntity> : IEntityCollection, IEntityCollec
private bool _entitiesLoaded;
private bool _entitiesAdded;

private AssociationAttribute AssocAttribute => _metaMember.AssociationAttribute;
private EntityAssociationAttribute AssocAttribute => _metaMember.AssociationAttribute;
private bool IsComposition => _metaMember.IsComposition;

/// <summary>
Expand Down Expand Up @@ -74,7 +74,7 @@ public EntityCollection(Entity parent, string memberName, Func<TEntity, bool> en
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Property_Does_Not_Exist, parent.GetType(), memberName), nameof(memberName));
}
if (this._metaMember.AssociationAttribute == null)
if (!this._metaMember.IsAssociationMember)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.MemberMustBeAssociation, memberName), nameof(memberName));
}
Expand Down Expand Up @@ -770,7 +770,7 @@ private void ResetLoadedEntities()
#endregion

#region IEntityCollection Members
AssociationAttribute IEntityCollection.Association
EntityAssociationAttribute IEntityCollection.Association
{
get
{
Expand Down
8 changes: 4 additions & 4 deletions src/OpenRiaServices.Client/Framework/EntityRef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public sealed class EntityRef<TEntity> : IEntityRef where TEntity : Entity

private string MemberName => _metaMember.Name;
private bool IsComposition => _metaMember.IsComposition;
private AssociationAttribute AssocAttribute => _metaMember.AssociationAttribute;
private EntityAssociationAttribute AssocAttribute => _metaMember.AssociationAttribute;

/// <summary>
/// Initializes a new instance of the EntityRef class
Expand Down Expand Up @@ -58,7 +58,7 @@ public EntityRef(Entity parent, string memberName, Func<TEntity, bool> entityPre
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Property_Does_Not_Exist, parent.GetType(), memberName), nameof(memberName));
}
if (this._metaMember.AssociationAttribute == null)
if (!this._metaMember.IsAssociationMember)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.MemberMustBeAssociation, memberName), nameof(memberName));
}
Expand Down Expand Up @@ -393,7 +393,7 @@ private void ParentEntityPropertyChanged(object sender, PropertyChangedEventArgs
}

#region IEntityRef Members
AssociationAttribute IEntityRef.Association
EntityAssociationAttribute IEntityRef.Association
{
get
{
Expand Down Expand Up @@ -441,7 +441,7 @@ internal interface IEntityRef
/// <summary>
/// Gets the AssociationAttribute for this reference.
/// </summary>
AssociationAttribute Association
EntityAssociationAttribute Association
{
get;
}
Expand Down
6 changes: 3 additions & 3 deletions src/OpenRiaServices.Client/Framework/EntitySet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace OpenRiaServices.Client
/// </summary>
public abstract class EntitySet : IEnumerable, ICollection, INotifyCollectionChanged, IRevertibleChangeTracking, INotifyPropertyChanged
{
private readonly Dictionary<AssociationAttribute, Action<Entity>> _associationUpdateCallbackMap = new Dictionary<AssociationAttribute, Action<Entity>>();
private readonly Dictionary<EntityAssociationAttribute, Action<Entity>> _associationUpdateCallbackMap = new();
private readonly Type _entityType;
private EntityContainer _entityContainer;
private EntitySetOperations _supportedOperations;
Expand Down Expand Up @@ -305,7 +305,7 @@ internal void UpdateRelatedAssociations(Entity entity, string propertyName)
/// <param name="association">AssociationAttribute indicating the association to monitor</param>
/// <param name="callback">The callback to call</param>
/// <param name="register">True if the callback is being registered, false if it is being unregistered</param>
internal void RegisterAssociationCallback(AssociationAttribute association, Action<Entity> callback, bool register)
internal void RegisterAssociationCallback(EntityAssociationAttribute association, Action<Entity> callback, bool register)
{
this._associationUpdateCallbackMap.TryGetValue(association, out Action<Entity> del);
if (register)
Expand Down Expand Up @@ -1426,7 +1426,7 @@ public int Add(object value)
int countBefore = this.Source.Count;
this.Source.Add(entity);

return this.Source.Count == countBefore + 1
return this.Source.Count == countBefore + 1
? countBefore
: ((List<T>)this.Source.List).IndexOf(entity, countBefore);
}
Expand Down
2 changes: 1 addition & 1 deletion src/OpenRiaServices.Client/Framework/IEntityCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal interface IEntityCollection
/// <summary>
/// Gets the AssociationAttribute for this collection.
/// </summary>
AssociationAttribute Association
EntityAssociationAttribute Association
{
get;
}
Expand Down
18 changes: 16 additions & 2 deletions src/OpenRiaServices.Client/Framework/Internal/MetaMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,21 @@ internal MetaMember(MetaType metaType, PropertyInfo property, bool isRoundtripEn
IsKeyMember = TypeUtility.IsAttributeDefined(property, typeof(KeyAttribute), false);
IsComposition = TypeUtility.IsAttributeDefined(property, typeof(CompositionAttribute), false);

AssociationAttribute = (AssociationAttribute)property.GetCustomAttributes(typeof(AssociationAttribute), false).SingleOrDefault();
if (property.GetCustomAttribute<EntityAssociationAttribute>(false) is { } association)
{
this.AssociationAttribute = association;
}
#pragma warning disable CS0618 // Type or member is obsolete
// TODO: Remove fallback when code generation has used the never attribute for a little while
else if (property.GetCustomAttribute<AssociationAttribute>(false) is { } associationAttribute)
{
this.AssociationAttribute = new EntityAssociationAttribute(associationAttribute.Name, associationAttribute.ThisKeyMembers.ToArray(), associationAttribute.OtherKeyMembers.ToArray())
{
IsForeignKey = associationAttribute.IsForeignKey,
};
}
#pragma warning restore CS0618 // Type or member is obsolete

EditableAttribute = (EditableAttribute)property.GetCustomAttributes(typeof(EditableAttribute), false).SingleOrDefault();

IsRoundtripMember = CheckIfRoundtripMember(this, isRoundtripEntity);
Expand Down Expand Up @@ -88,7 +102,7 @@ internal MetaMember(MetaType metaType, PropertyInfo property, bool isRoundtripEn
/// Gets any <see cref="AssociationAttribute"/> applied to the property, or <c>null</c>
/// if no attribute is specified for the property
/// </summary>
public AssociationAttribute AssociationAttribute { get; }
public EntityAssociationAttribute AssociationAttribute { get; }

/// <summary>
/// Gets a value indicating whether this member is one of the supported values to send between
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
<PropertyGroup>
<TargetFrameworks>net472;netstandard2.0;net6.0-windows</TargetFrameworks>

<!-- Disable obsolete warning (primarily AssociationAttribute) -->
<NoWarn Condition=" '$(TargetFramework)' != 'net472' ">$(NoWarn);CS0618</NoWarn>
<DefineConstants Condition=" '$(TargetFramework)' == 'net472' or '$(TargetFramework)' == 'net6.0-windows' ">$(DefineConstants);HAS_COLLECTIONVIEW</DefineConstants>
<UseWPF Condition=" '$(TargetFramework)' == 'net6.0-windows' ">true</UseWPF>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,12 @@ public void TestDefaultDataAnnotationAttributeCtors()
{
Type[] _knownAttributeTypes = {
typeof(KeyAttribute),
typeof(AssociationAttribute),
typeof(ConcurrencyCheckAttribute),
typeof(TimestampAttribute)
};

foreach (Type t in _knownAttributeTypes)
{
if (t == typeof(AssociationAttribute)) {
// no default constructor defined
continue;
}

Attribute attr = null;
string message = string.Empty;
try
Expand All @@ -38,22 +32,5 @@ public void TestDefaultDataAnnotationAttributeCtors()
Assert.IsNotNull(attr, "Default ctor failed for attribute type " + t.GetType().Name + message);
}
}

[TestMethod]
public void TestAssociationAttribute()
{
AssociationAttribute attr = new AssociationAttribute("name", "thisKey", "otherKey");
attr.IsForeignKey = false;

Assert.AreEqual("name", attr.Name);
Assert.AreEqual("thisKey", attr.ThisKey);
Assert.AreEqual("otherKey", attr.OtherKey);
Assert.AreEqual(false, attr.IsForeignKey);

// Verify can reverse polarity of foreign key
attr.IsForeignKey = true;
Assert.AreEqual(true, attr.IsForeignKey);

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
<TargetFrameworks>net472;netstandard2.0;net6.0</TargetFrameworks>
<DefineConstants>$(DefineConstants);SERVERFX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' != 'net472'">
<!-- Disable obsolete warning (primarily AssociationAttribute) -->
<NoWarn>$(NoWarn);CS0618</NoWarn>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<Reference Include="mscorlib" />
Expand All @@ -30,6 +26,7 @@
<Compile Include="..\..\OpenRiaServices.Client\Framework\BinaryTypeUtility.cs" Link="Data\BinaryTypeUtility.cs" />
<Compile Include="..\..\OpenRiaServices.Client\Framework\DomainException.cs" Link="Data\DomainException.cs" />
<Compile Include="..\..\OpenRiaServices.Client\Framework\DomainIdentifierAttribute.cs" Link="Data\DomainIdentifierAttribute.cs" />
<Compile Include="..\..\OpenRiaServices.Client\Framework\EntityAssociationAttribute.cs" Link="Data\EntityAssociationAttribute.cs" />
<Compile Include="..\..\OpenRiaServices.Client\Framework\ExternalReferenceAttribute.cs" Link="Data\ExternalAttribute.cs" />
<Compile Include="..\..\OpenRiaServices.Client\Framework\Polyfills.cs" Link="Data\Polyfills.cs" />
<Compile Include="..\..\OpenRiaServices.Client\Framework\RoundtripOriginalAttribute.cs" Link="Data\RoundtripOriginalAttribute.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using OpenRiaServices.Server;

namespace OpenRiaServices.Tools.TextTemplate
Expand Down Expand Up @@ -57,6 +59,9 @@ internal class AttributeGeneratorHelper
{ typeof(EntityActionAttribute), null },
{ typeof(RequiresAuthenticationAttribute), null },
{ typeof(RequiresRoleAttribute), null },
// Translate all AssociationAttribute to EntityAssociationAttribute on the client
{ typeof(AssociationAttribute), new EntityAssociationAttributeBuilder() },
//{ typeof(EntityAssociationAttribute), new EntityAssociationAttributeBuilder() },
};

public static AttributeDeclaration GetAttributeDeclaration(Attribute attribute, ClientCodeGenerator textTemplateClientCodeGenerator, bool forcePropagation)
Expand Down Expand Up @@ -298,6 +303,25 @@ internal static string ConvertValueToCode(object value, bool isCSharp)
return CodeGenUtilities.GetTypeName(value.GetType()) + "." + value.ToString();
}

if (value is Array array)
{
Debug.Assert(isCSharp);

StringBuilder stringBuilder = new StringBuilder(200);

stringBuilder.Append($"new ");
stringBuilder.Append(CodeGenUtilities.GetTypeName(value.GetType().GetElementType()));
stringBuilder.Append("[] {");
for (int i=0; i < array.Length; i++)
{
if (i > 0)
stringBuilder.Append(", ");
stringBuilder.Append(ConvertValueToCode(array.GetValue(i), isCSharp));
}
stringBuilder.Append('}');
return stringBuilder.ToString();
}

return value.ToString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\DisplayCustomAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\DisplayCustomAttributeBuilder.cs" />
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\DomainIdentifierAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\DomainIdentifierAttributeBuilder.cs" />
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\EditableAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\EditableAttributeBuilder.cs" />
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\EntityAssociationAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\EntityAssociationAttributeBuilder.cs" />
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\ICustomAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\ICustomAttributeBuilder.cs" />
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\RangeCustomAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\RangeCustomAttributeBuilder.cs" />
<Compile Include="..\..\OpenRiaServices.Tools\Framework\MetadataPipeline\StandardCustomAttributeBuilder.cs" Link="CSharpGenerators\AttributeGenerationHelpers\StandardCustomAttributeBuilder.cs" />
Expand Down
Loading

0 comments on commit 6293d33

Please sign in to comment.