diff --git a/src/DataverseProxyGenerator.Core/Domain/BooleanManagedColumnModel.cs b/src/DataverseProxyGenerator.Core/Domain/BooleanManagedColumnModel.cs new file mode 100644 index 0000000..fceefbe --- /dev/null +++ b/src/DataverseProxyGenerator.Core/Domain/BooleanManagedColumnModel.cs @@ -0,0 +1,5 @@ +namespace DataverseProxyGenerator.Core.Domain; + +public record BooleanManagedColumnModel() : ManagedColumnModel("bool", IsNullable: false) +{ +} \ No newline at end of file diff --git a/src/DataverseProxyGenerator.Core/Domain/ManagedColumnModel.cs b/src/DataverseProxyGenerator.Core/Domain/ManagedColumnModel.cs new file mode 100644 index 0000000..21696b3 --- /dev/null +++ b/src/DataverseProxyGenerator.Core/Domain/ManagedColumnModel.cs @@ -0,0 +1,6 @@ +namespace DataverseProxyGenerator.Core.Domain; + +public record ManagedColumnModel(string ReturnType, bool IsNullable) : ColumnModel +{ + public string FullReturnType => IsNullable && ReturnType != "string" ? ReturnType + "?" : ReturnType; +} \ No newline at end of file diff --git a/src/DataverseProxyGenerator.Core/Domain/UniqueIdentifierColumnModel.cs b/src/DataverseProxyGenerator.Core/Domain/UniqueIdentifierColumnModel.cs new file mode 100644 index 0000000..bdfc0ae --- /dev/null +++ b/src/DataverseProxyGenerator.Core/Domain/UniqueIdentifierColumnModel.cs @@ -0,0 +1,5 @@ +namespace DataverseProxyGenerator.Core.Domain; + +public record UniqueIdentifierColumnModel : ColumnModel +{ +} \ No newline at end of file diff --git a/src/DataverseProxyGenerator.Core/Metadata/DataverseMetadataFetcher.cs b/src/DataverseProxyGenerator.Core/Metadata/DataverseMetadataFetcher.cs index e851be6..1c7757e 100644 --- a/src/DataverseProxyGenerator.Core/Metadata/DataverseMetadataFetcher.cs +++ b/src/DataverseProxyGenerator.Core/Metadata/DataverseMetadataFetcher.cs @@ -188,7 +188,7 @@ private TableModel BuildTableModelFromMetadata(Dictionary x.AttributeOf == null) + .Where(x => x.AttributeOf == null && x.LogicalName != entityMetadata.PrimaryIdAttribute) .ToList(); foreach (var attr in validAttributes) @@ -225,6 +225,9 @@ private TableModel BuildTableModelFromMetadata(Dictionary BuildLookupColumn(lookupAttr), FileAttributeMetadata fileAttr => BuildFileColumn(fileAttr), ImageAttributeMetadata imageAttr => BuildImageColumn(imageAttr), + ManagedPropertyAttributeMetadata managedAttr => BuildManagedPropertyColumn(managedAttr), + UniqueIdentifierAttributeMetadata uniqueAttr => BuildUniqueIdentifierColumn(uniqueAttr), + AttributeMetadata attrAttr when attrAttr.AttributeType == AttributeTypeCode.Uniqueidentifier => BuildUniqueIdentifierColumn(attrAttr), _ => null, }; @@ -451,6 +454,49 @@ private static Dictionary> BuildOptionLocalizations Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty), }; + private ManagedColumnModel? BuildManagedPropertyColumn(ManagedPropertyAttributeMetadata attr) + { + return attr.ValueAttributeTypeCode switch + { + AttributeTypeCode.Boolean => BuildBooleanManagedColumnModel(attr), + AttributeTypeCode.DateTime => BuildManagedColumnModel(attr, "DateTime", nullable: true), + AttributeTypeCode.Decimal => BuildManagedColumnModel(attr, "decimal", nullable: true), + AttributeTypeCode.Double => BuildManagedColumnModel(attr, "double", nullable: true), + AttributeTypeCode.Integer => BuildManagedColumnModel(attr, "int", nullable: true), + AttributeTypeCode.BigInt => BuildManagedColumnModel(attr, "long", nullable: true), + AttributeTypeCode.Lookup => BuildManagedColumnModel(attr, "EntityReference", nullable: true), + AttributeTypeCode.Money => BuildManagedColumnModel(attr, "decimal", nullable: true), + AttributeTypeCode.Memo => BuildManagedColumnModel(attr, "string"), + AttributeTypeCode.PartyList => BuildManagedColumnModel(attr, "IEnumerable"), + AttributeTypeCode.String => BuildManagedColumnModel(attr, "string"), + _ => null, + }; + } + + private ManagedColumnModel BuildManagedColumnModel(AttributeMetadata attr, string returnType, bool nullable = false) => new ManagedColumnModel(returnType, nullable) + { + LogicalName = attr.LogicalName, + SchemaName = attr.SchemaName, + DisplayName = ApplyLabelMapping(attr.DisplayName?.UserLocalizedLabel?.Label ?? attr.LogicalName), + Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty), + }; + + private BooleanManagedColumnModel BuildBooleanManagedColumnModel(AttributeMetadata attr) => new BooleanManagedColumnModel + { + LogicalName = attr.LogicalName, + SchemaName = attr.SchemaName, + DisplayName = ApplyLabelMapping(attr.DisplayName?.UserLocalizedLabel?.Label ?? attr.LogicalName), + Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty), + }; + + private UniqueIdentifierColumnModel BuildUniqueIdentifierColumn(AttributeMetadata attr) => new UniqueIdentifierColumnModel + { + LogicalName = attr.LogicalName, + SchemaName = attr.SchemaName, + DisplayName = ApplyLabelMapping(attr.DisplayName?.UserLocalizedLabel?.Label ?? attr.LogicalName), + Description = ApplyLabelMapping(attr.Description?.UserLocalizedLabel?.Label ?? string.Empty), + }; + private static void MapRelationships(Dictionary logicalNameToMetadata, EntityMetadata entityMetadata, TableModel table) { MapManyToOne(logicalNameToMetadata, entityMetadata, table); diff --git a/src/DataverseProxyGenerator.Core/Templates/Body/EntityClass.scriban-cs b/src/DataverseProxyGenerator.Core/Templates/Body/EntityClass.scriban-cs index abca4ee..6c8c8de 100644 --- a/src/DataverseProxyGenerator.Core/Templates/Body/EntityClass.scriban-cs +++ b/src/DataverseProxyGenerator.Core/Templates/Body/EntityClass.scriban-cs @@ -130,6 +130,24 @@ public partial class {{table.SchemaName}} : ExtendedEntity{{ if table.Interfaces get => GetAttributeValue("{{column.LogicalName}}"); set => SetAttributeValue("{{column.LogicalName}}", value); } + {{~ else if column.TypeName == "UniqueIdentifierColumnModel" ~}} + public Guid? {{column.SchemaName}} + { + get => GetAttributeValue("{{column.LogicalName}}"); + set => SetAttributeValue("{{column.LogicalName}}", value); + } + {{~ else if column.TypeName == "BooleanManagedColumnModel" ~}} + public BooleanManagedProperty {{column.SchemaName}} + { + get => GetAttributeValue("{{column.LogicalName}}"); + set => SetAttributeValue("{{column.LogicalName}}", value); + } + {{~ else if column.TypeName == "ManagedColumnModel" ~}} + public ManagedProperty<{{column.FullReturnType}}> {{column.SchemaName}} + { + get => GetAttributeValue>("{{column.LogicalName}}"); + set => SetAttributeValue("{{column.LogicalName}}", value); + } {{~ else if column.TypeName == "PrimaryIdColumnModel" ~}} public Guid {{column.SchemaName}} { diff --git a/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.Generates_Correct_Code_For_All_AttributeTypes.verified.txt b/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.Generates_Correct_Code_For_All_AttributeTypes.verified.txt index a36ffcb..9b5f4f8 100644 --- a/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.Generates_Correct_Code_For_All_AttributeTypes.verified.txt +++ b/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.Generates_Correct_Code_For_All_AttributeTypes.verified.txt @@ -115,6 +115,28 @@ public partial class TestEntity : ExtendedEntity set => SetAttributeValue("isactive", value); } + /// + /// Display Name: A Managed Boolean Attribute + /// + [AttributeLogicalName("managedbooleanattribute")] + [DisplayName("A Managed Boolean Attribute")] + public BooleanManagedProperty ManagedBooleanAttribute + { + get => GetAttributeValue("managedbooleanattribute"); + set => SetAttributeValue("managedbooleanattribute", value); + } + + /// + /// Display Name: A Managed DateTime Attribute + /// + [AttributeLogicalName("manageddatetimeattribute")] + [DisplayName("A Managed DateTime Attribute")] + public ManagedProperty ManagedDateTimeAttribute + { + get => GetAttributeValue>("manageddatetimeattribute"); + set => SetAttributeValue("manageddatetimeattribute", value); + } + /// /// Display Name: Name /// @@ -197,6 +219,17 @@ public partial class TestEntity : ExtendedEntity set => SetAttributeValue("ratio", value); } + /// + /// Display Name: A ReadOnly Attribute + /// + [AttributeLogicalName("readonlyattribute")] + [DisplayName("A ReadOnly Attribute")] + public ManagedProperty ReadOnlyAttribute + { + get => GetAttributeValue>("readonlyattribute"); + set => SetAttributeValue("readonlyattribute", value); + } + /// /// Display Name: Revenue /// @@ -231,28 +264,39 @@ public partial class TestEntity : ExtendedEntity set => this.SetOptionSetValue("status", value); } + /// + /// Display Name: Unique Identifier + /// + [AttributeLogicalName("uniqueid")] + [DisplayName("Unique Identifier")] + public Guid? UniqueId + { + get => GetAttributeValue("uniqueid"); + set => SetAttributeValue("uniqueid", value); + } + /// /// Gets the logical column name for a property on the TestEntity entity, using the AttributeLogicalNameAttribute if present. /// - /// Expression to pick the column + /// Expression to pick the column /// Name of column /// If no expression is provided /// If the expression is not x => x.column - public static string GetColumnName(Expression> lambda) + public static string GetColumnName(Expression> column) { - return TableAttributeHelpers.GetColumnName(lambda); + return TableAttributeHelpers.GetColumnName(column); } /// - /// Retrieves a TestEntity with the specified attributes. + /// Retrieves the TestEntity with the specified columns. /// /// Organization service /// Id of TestEntity to retrieve - /// Expressions that specify attributes to retrieve + /// Expressions that specify columns to retrieve /// The retrieved TestEntity - public static TestEntity Retrieve(IOrganizationService service, Guid id, params Expression>[] attrs) + public static TestEntity Retrieve(IOrganizationService service, Guid id, params Expression>[] columns) { - return service.Retrieve(id, attrs); + return service.Retrieve(id, columns); } } \ No newline at end of file diff --git a/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.cs b/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.cs index be625db..e90d2b4 100644 --- a/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.cs +++ b/tests/DataverseProxyGenerator.Tests/AttributeTypeCodeGenTests.cs @@ -15,6 +15,9 @@ public async Task Generates_Correct_Code_For_All_AttributeTypes() DisplayName = "Test Entity", Columns = new List { + new ManagedColumnModel("string", IsNullable: false) { LogicalName = "readonlyattribute", SchemaName = "ReadOnlyAttribute", DisplayName = "A ReadOnly Attribute" }, + new BooleanManagedColumnModel { LogicalName = "managedbooleanattribute", SchemaName = "ManagedBooleanAttribute", DisplayName = "A Managed Boolean Attribute" }, + new ManagedColumnModel("DateTime", IsNullable: true) { LogicalName = "manageddatetimeattribute", SchemaName = "ManagedDateTimeAttribute", DisplayName = "A Managed DateTime Attribute" }, new StringColumnModel { LogicalName = "obsoleteattribute", SchemaName = "ObsoleteAttribute", DisplayName = "An Obsolete Attribute", IsObsolete = true }, new StringColumnModel { LogicalName = "name", SchemaName = "Name", DisplayName = "Name" }, 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() RelationshipName = "contact_account", }, new PartyListColumnModel { LogicalName = "participants", SchemaName = "Participants", DisplayName = "Participants" }, + new UniqueIdentifierColumnModel { LogicalName = "uniqueid", SchemaName = "UniqueId", DisplayName = "Unique Identifier" }, }, }; diff --git a/tests/DataverseProxyGenerator.Tests/RetrieveMethodTests.cs b/tests/DataverseProxyGenerator.Tests/RetrieveMethodTests.cs index 62e3052..ebc96a9 100644 --- a/tests/DataverseProxyGenerator.Tests/RetrieveMethodTests.cs +++ b/tests/DataverseProxyGenerator.Tests/RetrieveMethodTests.cs @@ -43,8 +43,8 @@ public void EntityClass_ShouldGenerateStaticRetrieveMethod() // Assert file.Should().NotBeNull(); file!.Content.Should().Contain("using System.Linq.Expressions;"); - file.Content.Should().Contain("public static Account Retrieve(IOrganizationService service, Guid id, params Expression>[] attrs)"); - file.Content.Should().Contain("return service.Retrieve(id, attrs);"); + file.Content.Should().Contain("public static Account Retrieve(IOrganizationService service, Guid id, params Expression>[] columns)"); + file.Content.Should().Contain("return service.Retrieve(id, columns);"); } [Fact] @@ -88,12 +88,12 @@ public void EntityClass_ShouldGenerateStaticGetColumnNameMethod() file.Content.Should().Contain("/// "); file.Content.Should().Contain("/// Gets the logical column name for a property on the Account entity, using the AttributeLogicalNameAttribute if present."); file.Content.Should().Contain("/// "); - file.Content.Should().Contain("/// Expression to pick the column"); + file.Content.Should().Contain("/// Expressions that specify columns to retrieve"); file.Content.Should().Contain("/// Name of column"); file.Content.Should().Contain("/// If no expression is provided"); file.Content.Should().Contain("/// If the expression is not x => x.column"); - file.Content.Should().Contain("public static string GetColumnName(Expression> lambda)"); - file.Content.Should().Contain("return TableAttributeHelpers.GetColumnName(lambda);"); + file.Content.Should().Contain("public static string GetColumnName(Expression> column)"); + file.Content.Should().Contain("return TableAttributeHelpers.GetColumnName(column);"); } [Fact] @@ -136,7 +136,7 @@ public void TableAttributeHelpers_ShouldGenerateRetrieveExtensionMethod() file!.Content.Should().Contain("using Microsoft.Xrm.Sdk.Query;"); file.Content.Should().Contain("public static T Retrieve(this IOrganizationService service, Guid id, params Expression>[] attrs)"); file.Content.Should().Contain("where T : Entity, new()"); - file.Content.Should().Contain("var columnNames = attrs.Select(attr => entity.GetColumnName(attr)).ToArray();"); + file.Content.Should().Contain("var columnNames = attrs.Select(attr => GetColumnName(attr)).ToArray();"); file.Content.Should().Contain("return service.Retrieve(entityLogicalName, id, columnSet).ToEntity();"); } } \ No newline at end of file