Skip to content

Commit

Permalink
EntityAssociationAttribute Step2 (#513)
Browse files Browse the repository at this point in the history
1. Change `EntityAssociationAttribute` namespace to `System.ComponentModel.DataAnnotations`
    * This will match other attributes such as `[Composition]` as well as the `AssociationAttribute` is should replace
2. Add  a constructor taking 2 strings for the case where the key is a single member
3. Update code generation to use new constructor when possible (`EntityAssociationAttributeBuilder`)
4. Update baselines
  • Loading branch information
Daniel-Svensson authored Jun 12, 2024
1 parent 6293d33 commit 6e0bcfc
Show file tree
Hide file tree
Showing 60 changed files with 525 additions and 997 deletions.
35 changes: 28 additions & 7 deletions src/OpenRiaServices.Client/Framework/EntityAssociationAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
using System;
using System.Collections.Generic;

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

namespace OpenRiaServices
namespace System.ComponentModel.DataAnnotations
{
/// <summary>
/// Used to mark an Entity member as an association
/// 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
/// Create an instance that defines an association between two entities, using any number of key members.
/// </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>
Expand All @@ -26,14 +26,35 @@ public EntityAssociationAttribute(string name, string[] thisKey, string[] otherK
ThisKeyMembers = thisKey ?? throw new ArgumentNullException(nameof(thisKey));
OtherKeyMembers = otherKey ?? throw new ArgumentNullException(nameof(otherKey));

if (name .Length == 0)
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>
/// Create an instance that defines an association using a single key member.
/// </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">A single property name of the key value on this side of the association</param>
/// <param name="otherKey">A single property name of the key value on the other side of the association</param>
public EntityAssociationAttribute(string name, string thisKey, string otherKey)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty", nameof(name));
if (string.IsNullOrEmpty(thisKey))
throw new ArgumentException("ThisKey cannot be empty", nameof(thisKey));
if (string.IsNullOrEmpty(otherKey))
throw new ArgumentException("OtherKey cannot be empty", nameof(otherKey));

Name = name;
ThisKeyMembers = [thisKey];
OtherKeyMembers = [otherKey];
}

/// <summary>
/// Gets the name of the association. For bi-directional associations, the name must
/// be the same on both sides of the association
Expand All @@ -49,12 +70,12 @@ public EntityAssociationAttribute(string name, string[] thisKey, string[] otherK
/// <summary>
/// Gets the collection of individual key members specified in the ThisKey string.
/// </summary>
public IReadOnlyCollection<string> ThisKeyMembers { get; }
public IReadOnlyList<string> ThisKeyMembers { get; }

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

/// <summary>
/// Gets or sets the key value on this side of the association
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoWarn>108</NoWarn>
<TargetFrameworks>net472;net6.0-windows;net8.0-windows</TargetFrameworks>
<Version>1.0.0.0</Version>
<UseWPF>true</UseWPF>
<DefineConstants>$(DefineConstants);HAS_COLLECTIONVIEW</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' != 'net472'">
<!-- Disable obsolete warning (primarily AssociationAttribute) -->
<NoWarn>$(NoWarn);CS0618</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFrameworks>net472</TargetFrameworks>
<DefineConstants>TRACE;DEBUG;VBTests</DefineConstants>
<NoWarn>108</NoWarn>
<RootNamespace>OpenRiaServices.Client.Test</RootNamespace>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoWarn>618</NoWarn>
<RootNamespace>CodeFirstModels</RootNamespace>
<AssemblyName>CodeFirstModels</AssemblyName>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,48 @@ internal class EntityAssociationAttributeBuilder : ICustomAttributeBuilder
/// </summary>
public AttributeDeclaration GetAttributeDeclaration(Attribute attribute)
{
AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(EntityAssociationAttribute));
string name;
string[] thisKey, otherKey;
bool isForeignKey;

if (attribute is EntityAssociationAttribute entityAssociation)
{
attributeDeclaration.ConstructorArguments.Add(entityAssociation.Name);
attributeDeclaration.ConstructorArguments.Add((string[])entityAssociation.ThisKeyMembers);
attributeDeclaration.ConstructorArguments.Add((string[])entityAssociation.OtherKeyMembers);

if (entityAssociation.IsForeignKey)
{
attributeDeclaration.NamedParameters.Add(nameof(EntityAssociationAttribute.IsForeignKey), true);
}
name = entityAssociation.Name;
thisKey = (string[])entityAssociation.ThisKeyMembers;
otherKey = (string[])entityAssociation.OtherKeyMembers;
isForeignKey = entityAssociation.IsForeignKey;
}
else if (attribute is AssociationAttribute associationAttribute)
{
// [EntityAssociation( {true|false} )]
attributeDeclaration.ConstructorArguments.Add(associationAttribute.Name);
attributeDeclaration.ConstructorArguments.Add(associationAttribute.ThisKeyMembers.ToArray());
attributeDeclaration.ConstructorArguments.Add(associationAttribute.OtherKeyMembers.ToArray());

if (associationAttribute.IsForeignKey)
{
attributeDeclaration.NamedParameters.Add(nameof(EntityAssociationAttribute.IsForeignKey), true);
}
name = associationAttribute.Name;
thisKey = associationAttribute.ThisKeyMembers.ToArray();
otherKey = associationAttribute.OtherKeyMembers.ToArray();
isForeignKey = associationAttribute.IsForeignKey;
}
else
{
return null;
}

// Generate the attribute declaration
// If there is only a single key member, we use string based constructor
AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(EntityAssociationAttribute));
attributeDeclaration.ConstructorArguments.Add(name);
if (thisKey.Length == 1 && otherKey.Length == 1)
{
attributeDeclaration.ConstructorArguments.Add(thisKey[0]);
attributeDeclaration.ConstructorArguments.Add(otherKey[0]);
}
else
{
attributeDeclaration.ConstructorArguments.Add(thisKey);
attributeDeclaration.ConstructorArguments.Add(otherKey);
}

if (isForeignKey)
{
attributeDeclaration.NamedParameters.Add(nameof(EntityAssociationAttribute.IsForeignKey), true);
}
return attributeDeclaration;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void CodeGen_External_Entity_EFtoPOCO()
TestHelper.AssertGeneratedCodeContains(
generatedCode,
"private EntityRef<global::DataTests.Scenarios.EF.Northwind.PersonalDetails> _personalDetails_MarkedAsExternal;",
"[EntityAssociation(\"Employee_PersonalDetails\", new string[] { \"EmployeeID\"}, new string[] { \"UniqueID\"}, IsForeignKey=true)] [ExternalReference()]",
"[EntityAssociation(\"Employee_PersonalDetails\", \"EmployeeID\", \"UniqueID\", IsForeignKey=true)] [ExternalReference()]",
"public global::DataTests.Scenarios.EF.Northwind.PersonalDetails PersonalDetails_MarkedAsExternal",
"private bool FilterPersonalDetails_MarkedAsExternal(global::DataTests.Scenarios.EF.Northwind.PersonalDetails entity)");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1750,9 +1750,7 @@ public string Name
/// <summary>
/// Gets or sets the associated <see cref="State"/> entity.
/// </summary>
[EntityAssociation("State_County", new string[] {
"StateName"}, new string[] {
"Name"}, IsForeignKey=true)]
[EntityAssociation("State_County", "StateName", "Name", IsForeignKey=true)]
public State State
{
get
Expand Down Expand Up @@ -1930,9 +1928,7 @@ public State()
/// </summary>
[CustomValidation(typeof(CountiesValidator), "AreCountiesValid")]
[Editable(false)]
[EntityAssociation("State_County", new string[] {
"Name"}, new string[] {
"StateName"})]
[EntityAssociation("State_County", "Name", "StateName")]
[ReadOnly(true)]
public EntityCollection<County> Counties
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1628,7 +1628,7 @@ Namespace Cities
''' <summary>
''' Gets or sets the associated <see cref="State"/> entity.
''' </summary>
<EntityAssociation("State_County", New String() {"StateName"}, New String() {"Name"}, IsForeignKey:=true)> _
<EntityAssociation("State_County", "StateName", "Name", IsForeignKey:=true)> _
Public Property State() As State
Get
If (Me._state Is Nothing) Then
Expand Down Expand Up @@ -1793,7 +1793,7 @@ Namespace Cities
''' </summary>
<CustomValidation(GetType(CountiesValidator), "AreCountiesValid"), _
Editable(false), _
EntityAssociation("State_County", New String() {"Name"}, New String() {"StateName"}), _
EntityAssociation("State_County", "Name", "StateName"), _
[ReadOnly](true)> _
Public ReadOnly Property Counties() As EntityCollection(Of County)
Get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,7 @@ public string LoginID
/// <summary>
/// Gets or sets the associated <see cref="Employee"/> entity.
/// </summary>
[EntityAssociation("Employee_Employee", new string[] {
"ManagerID"}, new string[] {
"EmployeeID"}, IsForeignKey=true)]
[EntityAssociation("Employee_Employee", "ManagerID", "EmployeeID", IsForeignKey=true)]
public Employee Manager
{
get
Expand Down Expand Up @@ -441,9 +439,7 @@ public string NationalIDNumber
/// <summary>
/// Gets the collection of associated <see cref="PurchaseOrder"/> entity instances.
/// </summary>
[EntityAssociation("Employee_PurchaseOrder", new string[] {
"EmployeeID"}, new string[] {
"EmployeeID"})]
[EntityAssociation("Employee_PurchaseOrder", "EmployeeID", "EmployeeID")]
public EntityCollection<PurchaseOrder> PurchaseOrders
{
get
Expand All @@ -459,9 +455,7 @@ public EntityCollection<PurchaseOrder> PurchaseOrders
/// <summary>
/// Gets the collection of associated <see cref="Employee"/> entity instances.
/// </summary>
[EntityAssociation("Employee_Employee", new string[] {
"EmployeeID"}, new string[] {
"ManagerID"})]
[EntityAssociation("Employee_Employee", "EmployeeID", "ManagerID")]
public EntityCollection<Employee> Reports
{
get
Expand Down Expand Up @@ -1265,9 +1259,7 @@ public Nullable<int> ProductSubcategoryID
/// <summary>
/// Gets the collection of associated <see cref="PurchaseOrderDetail"/> entity instances.
/// </summary>
[EntityAssociation("Product_PurchaseOrderDetail", new string[] {
"ProductID"}, new string[] {
"ProductID"})]
[EntityAssociation("Product_PurchaseOrderDetail", "ProductID", "ProductID")]
public EntityCollection<PurchaseOrderDetail> PurchaseOrderDetails
{
get
Expand Down Expand Up @@ -1637,9 +1629,7 @@ public PurchaseOrder()
/// <summary>
/// Gets or sets the associated <see cref="Employee"/> entity.
/// </summary>
[EntityAssociation("Employee_PurchaseOrder", new string[] {
"EmployeeID"}, new string[] {
"EmployeeID"}, IsForeignKey=true)]
[EntityAssociation("Employee_PurchaseOrder", "EmployeeID", "EmployeeID", IsForeignKey=true)]
public Employee Employee
{
get
Expand Down Expand Up @@ -1779,9 +1769,7 @@ public DateTime OrderDate
/// <summary>
/// Gets the collection of associated <see cref="PurchaseOrderDetail"/> entity instances.
/// </summary>
[EntityAssociation("PurchaseOrder_PurchaseOrderDetail", new string[] {
"PurchaseOrderID"}, new string[] {
"PurchaseOrderID"})]
[EntityAssociation("PurchaseOrder_PurchaseOrderDetail", "PurchaseOrderID", "PurchaseOrderID")]
public EntityCollection<PurchaseOrderDetail> PurchaseOrderDetails
{
get
Expand Down Expand Up @@ -2217,9 +2205,7 @@ public short OrderQty
/// <summary>
/// Gets or sets the associated <see cref="Product"/> entity.
/// </summary>
[EntityAssociation("Product_PurchaseOrderDetail", new string[] {
"ProductID"}, new string[] {
"ProductID"}, IsForeignKey=true)]
[EntityAssociation("Product_PurchaseOrderDetail", "ProductID", "ProductID", IsForeignKey=true)]
public Product Product
{
get
Expand Down Expand Up @@ -2287,9 +2273,7 @@ public int ProductID
/// <summary>
/// Gets or sets the associated <see cref="PurchaseOrder"/> entity.
/// </summary>
[EntityAssociation("PurchaseOrder_PurchaseOrderDetail", new string[] {
"PurchaseOrderID"}, new string[] {
"PurchaseOrderID"}, IsForeignKey=true)]
[EntityAssociation("PurchaseOrder_PurchaseOrderDetail", "PurchaseOrderID", "PurchaseOrderID", IsForeignKey=true)]
public PurchaseOrder PurchaseOrder
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets or sets the associated <see cref="Employee"/> entity.
''' </summary>
<EntityAssociation("Employee_Employee", New String() {"ManagerID"}, New String() {"EmployeeID"}, IsForeignKey:=true)> _
<EntityAssociation("Employee_Employee", "ManagerID", "EmployeeID", IsForeignKey:=true)> _
Public Property Manager() As Employee
Get
If (Me._manager Is Nothing) Then
Expand Down Expand Up @@ -430,7 +430,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets the collection of associated <see cref="PurchaseOrder"/> entity instances.
''' </summary>
<EntityAssociation("Employee_PurchaseOrder", New String() {"EmployeeID"}, New String() {"EmployeeID"})> _
<EntityAssociation("Employee_PurchaseOrder", "EmployeeID", "EmployeeID")> _
Public ReadOnly Property PurchaseOrders() As EntityCollection(Of PurchaseOrder)
Get
If (Me._purchaseOrders Is Nothing) Then
Expand All @@ -443,7 +443,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets the collection of associated <see cref="Employee"/> entity instances.
''' </summary>
<EntityAssociation("Employee_Employee", New String() {"EmployeeID"}, New String() {"ManagerID"})> _
<EntityAssociation("Employee_Employee", "EmployeeID", "ManagerID")> _
Public ReadOnly Property Reports() As EntityCollection(Of Employee)
Get
If (Me._reports Is Nothing) Then
Expand Down Expand Up @@ -1213,7 +1213,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets the collection of associated <see cref="PurchaseOrderDetail"/> entity instances.
''' </summary>
<EntityAssociation("Product_PurchaseOrderDetail", New String() {"ProductID"}, New String() {"ProductID"})> _
<EntityAssociation("Product_PurchaseOrderDetail", "ProductID", "ProductID")> _
Public ReadOnly Property PurchaseOrderDetails() As EntityCollection(Of PurchaseOrderDetail)
Get
If (Me._purchaseOrderDetails Is Nothing) Then
Expand Down Expand Up @@ -1567,7 +1567,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets or sets the associated <see cref="Employee"/> entity.
''' </summary>
<EntityAssociation("Employee_PurchaseOrder", New String() {"EmployeeID"}, New String() {"EmployeeID"}, IsForeignKey:=true)> _
<EntityAssociation("Employee_PurchaseOrder", "EmployeeID", "EmployeeID", IsForeignKey:=true)> _
Public Property Employee() As Employee
Get
If (Me._employee Is Nothing) Then
Expand Down Expand Up @@ -1685,7 +1685,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets the collection of associated <see cref="PurchaseOrderDetail"/> entity instances.
''' </summary>
<EntityAssociation("PurchaseOrder_PurchaseOrderDetail", New String() {"PurchaseOrderID"}, New String() {"PurchaseOrderID"})> _
<EntityAssociation("PurchaseOrder_PurchaseOrderDetail", "PurchaseOrderID", "PurchaseOrderID")> _
Public ReadOnly Property PurchaseOrderDetails() As EntityCollection(Of PurchaseOrderDetail)
Get
If (Me._purchaseOrderDetails Is Nothing) Then
Expand Down Expand Up @@ -2096,7 +2096,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets or sets the associated <see cref="Product"/> entity.
''' </summary>
<EntityAssociation("Product_PurchaseOrderDetail", New String() {"ProductID"}, New String() {"ProductID"}, IsForeignKey:=true)> _
<EntityAssociation("Product_PurchaseOrderDetail", "ProductID", "ProductID", IsForeignKey:=true)> _
Public Property Product() As Product
Get
If (Me._product Is Nothing) Then
Expand Down Expand Up @@ -2151,7 +2151,7 @@ Namespace AdventureWorksModel
''' <summary>
''' Gets or sets the associated <see cref="PurchaseOrder"/> entity.
''' </summary>
<EntityAssociation("PurchaseOrder_PurchaseOrderDetail", New String() {"PurchaseOrderID"}, New String() {"PurchaseOrderID"}, IsForeignKey:=true)> _
<EntityAssociation("PurchaseOrder_PurchaseOrderDetail", "PurchaseOrderID", "PurchaseOrderID", IsForeignKey:=true)> _
Public Property PurchaseOrder() As PurchaseOrder
Get
If (Me._purchaseOrder Is Nothing) Then
Expand Down
Loading

0 comments on commit 6e0bcfc

Please sign in to comment.