Skip to content

Commit a0c7b0f

Browse files
authored
Merge pull request #64 from delegateas/fix/handle-managed-and-guid-attributes
Managed properties and UniqueIdentifier properties
2 parents 6c0cb3b + 2de4ef8 commit a0c7b0f

8 files changed

Lines changed: 142 additions & 14 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace DataverseProxyGenerator.Core.Domain;
2+
3+
public record BooleanManagedColumnModel() : ManagedColumnModel("bool", IsNullable: false)
4+
{
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace DataverseProxyGenerator.Core.Domain;
2+
3+
public record ManagedColumnModel(string ReturnType, bool IsNullable) : ColumnModel
4+
{
5+
public string FullReturnType => IsNullable && ReturnType != "string" ? ReturnType + "?" : ReturnType;
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace DataverseProxyGenerator.Core.Domain;
2+
3+
public record UniqueIdentifierColumnModel : ColumnModel
4+
{
5+
}

src/DataverseProxyGenerator.Core/Metadata/DataverseMetadataFetcher.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private TableModel BuildTableModelFromMetadata(Dictionary<string, EntityMetadata
188188
};
189189

190190
var validAttributes = entityMetadata.Attributes
191-
.Where(x => x.AttributeOf == null)
191+
.Where(x => x.AttributeOf == null && x.LogicalName != entityMetadata.PrimaryIdAttribute)
192192
.ToList();
193193

194194
foreach (var attr in validAttributes)
@@ -225,6 +225,9 @@ private TableModel BuildTableModelFromMetadata(Dictionary<string, EntityMetadata
225225
LookupAttributeMetadata lookupAttr => BuildLookupColumn(lookupAttr),
226226
FileAttributeMetadata fileAttr => BuildFileColumn(fileAttr),
227227
ImageAttributeMetadata imageAttr => BuildImageColumn(imageAttr),
228+
ManagedPropertyAttributeMetadata managedAttr => BuildManagedPropertyColumn(managedAttr),
229+
UniqueIdentifierAttributeMetadata uniqueAttr => BuildUniqueIdentifierColumn(uniqueAttr),
230+
AttributeMetadata attrAttr when attrAttr.AttributeType == AttributeTypeCode.Uniqueidentifier => BuildUniqueIdentifierColumn(attrAttr),
228231
_ => null,
229232
};
230233

@@ -451,6 +454,49 @@ private static Dictionary<int, Dictionary<int, string>> BuildOptionLocalizations
451454
Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty),
452455
};
453456

457+
private ManagedColumnModel? BuildManagedPropertyColumn(ManagedPropertyAttributeMetadata attr)
458+
{
459+
return attr.ValueAttributeTypeCode switch
460+
{
461+
AttributeTypeCode.Boolean => BuildBooleanManagedColumnModel(attr),
462+
AttributeTypeCode.DateTime => BuildManagedColumnModel(attr, "DateTime", nullable: true),
463+
AttributeTypeCode.Decimal => BuildManagedColumnModel(attr, "decimal", nullable: true),
464+
AttributeTypeCode.Double => BuildManagedColumnModel(attr, "double", nullable: true),
465+
AttributeTypeCode.Integer => BuildManagedColumnModel(attr, "int", nullable: true),
466+
AttributeTypeCode.BigInt => BuildManagedColumnModel(attr, "long", nullable: true),
467+
AttributeTypeCode.Lookup => BuildManagedColumnModel(attr, "EntityReference", nullable: true),
468+
AttributeTypeCode.Money => BuildManagedColumnModel(attr, "decimal", nullable: true),
469+
AttributeTypeCode.Memo => BuildManagedColumnModel(attr, "string"),
470+
AttributeTypeCode.PartyList => BuildManagedColumnModel(attr, "IEnumerable<ActivityParty>"),
471+
AttributeTypeCode.String => BuildManagedColumnModel(attr, "string"),
472+
_ => null,
473+
};
474+
}
475+
476+
private ManagedColumnModel BuildManagedColumnModel(AttributeMetadata attr, string returnType, bool nullable = false) => new ManagedColumnModel(returnType, nullable)
477+
{
478+
LogicalName = attr.LogicalName,
479+
SchemaName = attr.SchemaName,
480+
DisplayName = ApplyLabelMapping(attr.DisplayName?.UserLocalizedLabel?.Label ?? attr.LogicalName),
481+
Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty),
482+
};
483+
484+
private BooleanManagedColumnModel BuildBooleanManagedColumnModel(AttributeMetadata attr) => new BooleanManagedColumnModel
485+
{
486+
LogicalName = attr.LogicalName,
487+
SchemaName = attr.SchemaName,
488+
DisplayName = ApplyLabelMapping(attr.DisplayName?.UserLocalizedLabel?.Label ?? attr.LogicalName),
489+
Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty),
490+
};
491+
492+
private UniqueIdentifierColumnModel BuildUniqueIdentifierColumn(AttributeMetadata attr) => new UniqueIdentifierColumnModel
493+
{
494+
LogicalName = attr.LogicalName,
495+
SchemaName = attr.SchemaName,
496+
DisplayName = ApplyLabelMapping(attr.DisplayName?.UserLocalizedLabel?.Label ?? attr.LogicalName),
497+
Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty),
498+
};
499+
454500
private static void MapRelationships(Dictionary<string, EntityMetadata> logicalNameToMetadata, EntityMetadata entityMetadata, TableModel table)
455501
{
456502
MapManyToOne(logicalNameToMetadata, entityMetadata, table);

src/DataverseProxyGenerator.Core/Templates/Body/EntityClass.scriban-cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,24 @@ public partial class {{table.SchemaName}} : ExtendedEntity{{ if table.Interfaces
130130
get => GetAttributeValue<byte[]>("{{column.LogicalName}}");
131131
set => SetAttributeValue("{{column.LogicalName}}", value);
132132
}
133+
{{~ else if column.TypeName == "UniqueIdentifierColumnModel" ~}}
134+
public Guid? {{column.SchemaName}}
135+
{
136+
get => GetAttributeValue<Guid?>("{{column.LogicalName}}");
137+
set => SetAttributeValue("{{column.LogicalName}}", value);
138+
}
139+
{{~ else if column.TypeName == "BooleanManagedColumnModel" ~}}
140+
public BooleanManagedProperty {{column.SchemaName}}
141+
{
142+
get => GetAttributeValue<BooleanManagedProperty>("{{column.LogicalName}}");
143+
set => SetAttributeValue("{{column.LogicalName}}", value);
144+
}
145+
{{~ else if column.TypeName == "ManagedColumnModel" ~}}
146+
public ManagedProperty<{{column.FullReturnType}}> {{column.SchemaName}}
147+
{
148+
get => GetAttributeValue<ManagedProperty<{{column.FullReturnType}}>>("{{column.LogicalName}}");
149+
set => SetAttributeValue("{{column.LogicalName}}", value);
150+
}
133151
{{~ else if column.TypeName == "PrimaryIdColumnModel" ~}}
134152
public Guid {{column.SchemaName}}
135153
{

tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.Generates_Correct_Code_For_All_AttributeTypes.verified.txt

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,28 @@ public partial class TestEntity : ExtendedEntity
115115
set => SetAttributeValue("isactive", value);
116116
}
117117

118+
/// <summary>
119+
/// <para>Display Name: A Managed Boolean Attribute</para>
120+
/// </summary>
121+
[AttributeLogicalName("managedbooleanattribute")]
122+
[DisplayName("A Managed Boolean Attribute")]
123+
public BooleanManagedProperty ManagedBooleanAttribute
124+
{
125+
get => GetAttributeValue<BooleanManagedProperty>("managedbooleanattribute");
126+
set => SetAttributeValue("managedbooleanattribute", value);
127+
}
128+
129+
/// <summary>
130+
/// <para>Display Name: A Managed DateTime Attribute</para>
131+
/// </summary>
132+
[AttributeLogicalName("manageddatetimeattribute")]
133+
[DisplayName("A Managed DateTime Attribute")]
134+
public ManagedProperty<DateTime?> ManagedDateTimeAttribute
135+
{
136+
get => GetAttributeValue<ManagedProperty<DateTime?>>("manageddatetimeattribute");
137+
set => SetAttributeValue("manageddatetimeattribute", value);
138+
}
139+
118140
/// <summary>
119141
/// <para>Display Name: Name</para>
120142
/// </summary>
@@ -197,6 +219,17 @@ public partial class TestEntity : ExtendedEntity
197219
set => SetAttributeValue("ratio", value);
198220
}
199221

222+
/// <summary>
223+
/// <para>Display Name: A ReadOnly Attribute</para>
224+
/// </summary>
225+
[AttributeLogicalName("readonlyattribute")]
226+
[DisplayName("A ReadOnly Attribute")]
227+
public ManagedProperty<string> ReadOnlyAttribute
228+
{
229+
get => GetAttributeValue<ManagedProperty<string>>("readonlyattribute");
230+
set => SetAttributeValue("readonlyattribute", value);
231+
}
232+
200233
/// <summary>
201234
/// <para>Display Name: Revenue</para>
202235
/// </summary>
@@ -231,28 +264,39 @@ public partial class TestEntity : ExtendedEntity
231264
set => this.SetOptionSetValue("status", value);
232265
}
233266

267+
/// <summary>
268+
/// <para>Display Name: Unique Identifier</para>
269+
/// </summary>
270+
[AttributeLogicalName("uniqueid")]
271+
[DisplayName("Unique Identifier")]
272+
public Guid? UniqueId
273+
{
274+
get => GetAttributeValue<Guid?>("uniqueid");
275+
set => SetAttributeValue("uniqueid", value);
276+
}
277+
234278

235279
/// <summary>
236280
/// Gets the logical column name for a property on the TestEntity entity, using the AttributeLogicalNameAttribute if present.
237281
/// </summary>
238-
/// <param name="lambda">Expression to pick the column</param>
282+
/// <param name="column">Expression to pick the column</param>
239283
/// <returns>Name of column</returns>
240284
/// <exception cref="ArgumentNullException">If no expression is provided</exception>
241285
/// <exception cref="ArgumentException">If the expression is not x => x.column</exception>
242-
public static string GetColumnName(Expression<Func<TestEntity, object>> lambda)
286+
public static string GetColumnName(Expression<Func<TestEntity, object>> column)
243287
{
244-
return TableAttributeHelpers.GetColumnName(lambda);
288+
return TableAttributeHelpers.GetColumnName(column);
245289
}
246290

247291
/// <summary>
248-
/// Retrieves a TestEntity with the specified attributes.
292+
/// Retrieves the TestEntity with the specified columns.
249293
/// </summary>
250294
/// <param name="service">Organization service</param>
251295
/// <param name="id">Id of TestEntity to retrieve</param>
252-
/// <param name="attrs">Expressions that specify attributes to retrieve</param>
296+
/// <param name="columns">Expressions that specify columns to retrieve</param>
253297
/// <returns>The retrieved TestEntity</returns>
254-
public static TestEntity Retrieve(IOrganizationService service, Guid id, params Expression<Func<TestEntity, object>>[] attrs)
298+
public static TestEntity Retrieve(IOrganizationService service, Guid id, params Expression<Func<TestEntity, object>>[] columns)
255299
{
256-
return service.Retrieve(id, attrs);
300+
return service.Retrieve(id, columns);
257301
}
258302
}

tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public async Task Generates_Correct_Code_For_All_AttributeTypes()
1515
DisplayName = "Test Entity",
1616
Columns = new List<ColumnModel>
1717
{
18+
new ManagedColumnModel("string", IsNullable: false) { LogicalName = "readonlyattribute", SchemaName = "ReadOnlyAttribute", DisplayName = "A ReadOnly Attribute" },
19+
new BooleanManagedColumnModel { LogicalName = "managedbooleanattribute", SchemaName = "ManagedBooleanAttribute", DisplayName = "A Managed Boolean Attribute" },
20+
new ManagedColumnModel("DateTime", IsNullable: true) { LogicalName = "manageddatetimeattribute", SchemaName = "ManagedDateTimeAttribute", DisplayName = "A Managed DateTime Attribute" },
1821
new StringColumnModel { LogicalName = "obsoleteattribute", SchemaName = "ObsoleteAttribute", DisplayName = "An Obsolete Attribute", IsObsolete = true },
1922
new StringColumnModel { LogicalName = "name", SchemaName = "Name", DisplayName = "Name" },
2023
new StringColumnModel { LogicalName = "prefix_pascalcasetest_withname", SchemaName = "prefix_pascalCaseTest_withName", DisplayName = "Pascal Test" },
@@ -49,6 +52,7 @@ public async Task Generates_Correct_Code_For_All_AttributeTypes()
4952
RelationshipName = "contact_account",
5053
},
5154
new PartyListColumnModel { LogicalName = "participants", SchemaName = "Participants", DisplayName = "Participants" },
55+
new UniqueIdentifierColumnModel { LogicalName = "uniqueid", SchemaName = "UniqueId", DisplayName = "Unique Identifier" },
5256
},
5357
};
5458

tests/DataverseProxyGenerator.Tests/RetrieveMethodTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public void EntityClass_ShouldGenerateStaticRetrieveMethod()
4343
// Assert
4444
file.Should().NotBeNull();
4545
file!.Content.Should().Contain("using System.Linq.Expressions;");
46-
file.Content.Should().Contain("public static Account Retrieve(IOrganizationService service, Guid id, params Expression<Func<Account, object>>[] attrs)");
47-
file.Content.Should().Contain("return service.Retrieve(id, attrs);");
46+
file.Content.Should().Contain("public static Account Retrieve(IOrganizationService service, Guid id, params Expression<Func<Account, object>>[] columns)");
47+
file.Content.Should().Contain("return service.Retrieve(id, columns);");
4848
}
4949

5050
[Fact]
@@ -88,12 +88,12 @@ public void EntityClass_ShouldGenerateStaticGetColumnNameMethod()
8888
file.Content.Should().Contain("/// <summary>");
8989
file.Content.Should().Contain("/// Gets the logical column name for a property on the Account entity, using the AttributeLogicalNameAttribute if present.");
9090
file.Content.Should().Contain("/// </summary>");
91-
file.Content.Should().Contain("/// <param name=\"lambda\">Expression to pick the column</param>");
91+
file.Content.Should().Contain("/// <param name=\"columns\">Expressions that specify columns to retrieve</param>");
9292
file.Content.Should().Contain("/// <returns>Name of column</returns>");
9393
file.Content.Should().Contain("/// <exception cref=\"ArgumentNullException\">If no expression is provided</exception>");
9494
file.Content.Should().Contain("/// <exception cref=\"ArgumentException\">If the expression is not x => x.column</exception>");
95-
file.Content.Should().Contain("public static string GetColumnName(Expression<Func<Account, object>> lambda)");
96-
file.Content.Should().Contain("return TableAttributeHelpers.GetColumnName(lambda);");
95+
file.Content.Should().Contain("public static string GetColumnName(Expression<Func<Account, object>> column)");
96+
file.Content.Should().Contain("return TableAttributeHelpers.GetColumnName(column);");
9797
}
9898

9999
[Fact]
@@ -136,7 +136,7 @@ public void TableAttributeHelpers_ShouldGenerateRetrieveExtensionMethod()
136136
file!.Content.Should().Contain("using Microsoft.Xrm.Sdk.Query;");
137137
file.Content.Should().Contain("public static T Retrieve<T>(this IOrganizationService service, Guid id, params Expression<Func<T, object>>[] attrs)");
138138
file.Content.Should().Contain("where T : Entity, new()");
139-
file.Content.Should().Contain("var columnNames = attrs.Select(attr => entity.GetColumnName(attr)).ToArray();");
139+
file.Content.Should().Contain("var columnNames = attrs.Select(attr => GetColumnName(attr)).ToArray();");
140140
file.Content.Should().Contain("return service.Retrieve(entityLogicalName, id, columnSet).ToEntity<T>();");
141141
}
142142
}

0 commit comments

Comments
 (0)