Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace DataverseProxyGenerator.Core.Domain;

public record BooleanManagedColumnModel() : ManagedColumnModel("bool", IsNullable: false)
{
}
6 changes: 6 additions & 0 deletions src/DataverseProxyGenerator.Core/Domain/ManagedColumnModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DataverseProxyGenerator.Core.Domain;

public record ManagedColumnModel(string ReturnType, bool IsNullable) : ColumnModel
{
public string FullReturnType => IsNullable && ReturnType != "string" ? ReturnType + "?" : ReturnType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace DataverseProxyGenerator.Core.Domain;

public record UniqueIdentifierColumnModel : ColumnModel
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ private TableModel BuildTableModelFromMetadata(Dictionary<string, EntityMetadata
};

var validAttributes = entityMetadata.Attributes
.Where(x => x.AttributeOf == null)
.Where(x => x.AttributeOf == null && x.LogicalName != entityMetadata.PrimaryIdAttribute)
.ToList();

foreach (var attr in validAttributes)
Expand Down Expand Up @@ -225,6 +225,9 @@ private TableModel BuildTableModelFromMetadata(Dictionary<string, EntityMetadata
LookupAttributeMetadata lookupAttr => 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,
};

Expand Down Expand Up @@ -451,6 +454,49 @@ private static Dictionary<int, Dictionary<int, string>> 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<ActivityParty>"),
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<string, EntityMetadata> logicalNameToMetadata, EntityMetadata entityMetadata, TableModel table)
{
MapManyToOne(logicalNameToMetadata, entityMetadata, table);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ public partial class {{table.SchemaName}} : ExtendedEntity{{ if table.Interfaces
get => GetAttributeValue<byte[]>("{{column.LogicalName}}");
set => SetAttributeValue("{{column.LogicalName}}", value);
}
{{~ else if column.TypeName == "UniqueIdentifierColumnModel" ~}}
public Guid? {{column.SchemaName}}
{
get => GetAttributeValue<Guid?>("{{column.LogicalName}}");
set => SetAttributeValue("{{column.LogicalName}}", value);
}
{{~ else if column.TypeName == "BooleanManagedColumnModel" ~}}
public BooleanManagedProperty {{column.SchemaName}}
{
get => GetAttributeValue<BooleanManagedProperty>("{{column.LogicalName}}");
set => SetAttributeValue("{{column.LogicalName}}", value);
}
{{~ else if column.TypeName == "ManagedColumnModel" ~}}
public ManagedProperty<{{column.FullReturnType}}> {{column.SchemaName}}
{
get => GetAttributeValue<ManagedProperty<{{column.FullReturnType}}>>("{{column.LogicalName}}");
set => SetAttributeValue("{{column.LogicalName}}", value);
}
{{~ else if column.TypeName == "PrimaryIdColumnModel" ~}}
public Guid {{column.SchemaName}}
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ public partial class TestEntity : ExtendedEntity
set => SetAttributeValue("isactive", value);
}

/// <summary>
/// <para>Display Name: A Managed Boolean Attribute</para>
/// </summary>
[AttributeLogicalName("managedbooleanattribute")]
[DisplayName("A Managed Boolean Attribute")]
public BooleanManagedProperty ManagedBooleanAttribute
{
get => GetAttributeValue<BooleanManagedProperty>("managedbooleanattribute");
set => SetAttributeValue("managedbooleanattribute", value);
}

/// <summary>
/// <para>Display Name: A Managed DateTime Attribute</para>
/// </summary>
[AttributeLogicalName("manageddatetimeattribute")]
[DisplayName("A Managed DateTime Attribute")]
public ManagedProperty<DateTime?> ManagedDateTimeAttribute
{
get => GetAttributeValue<ManagedProperty<DateTime?>>("manageddatetimeattribute");
set => SetAttributeValue("manageddatetimeattribute", value);
}

/// <summary>
/// <para>Display Name: Name</para>
/// </summary>
Expand Down Expand Up @@ -197,6 +219,17 @@ public partial class TestEntity : ExtendedEntity
set => SetAttributeValue("ratio", value);
}

/// <summary>
/// <para>Display Name: A ReadOnly Attribute</para>
/// </summary>
[AttributeLogicalName("readonlyattribute")]
[DisplayName("A ReadOnly Attribute")]
public ManagedProperty<string> ReadOnlyAttribute
{
get => GetAttributeValue<ManagedProperty<string>>("readonlyattribute");
set => SetAttributeValue("readonlyattribute", value);
}

/// <summary>
/// <para>Display Name: Revenue</para>
/// </summary>
Expand Down Expand Up @@ -231,28 +264,39 @@ public partial class TestEntity : ExtendedEntity
set => this.SetOptionSetValue("status", value);
}

/// <summary>
/// <para>Display Name: Unique Identifier</para>
/// </summary>
[AttributeLogicalName("uniqueid")]
[DisplayName("Unique Identifier")]
public Guid? UniqueId
{
get => GetAttributeValue<Guid?>("uniqueid");
set => SetAttributeValue("uniqueid", value);
}


/// <summary>
/// Gets the logical column name for a property on the TestEntity entity, using the AttributeLogicalNameAttribute if present.
/// </summary>
/// <param name="lambda">Expression to pick the column</param>
/// <param name="column">Expression to pick the column</param>
/// <returns>Name of column</returns>
/// <exception cref="ArgumentNullException">If no expression is provided</exception>
/// <exception cref="ArgumentException">If the expression is not x => x.column</exception>
public static string GetColumnName(Expression<Func<TestEntity, object>> lambda)
public static string GetColumnName(Expression<Func<TestEntity, object>> column)
{
return TableAttributeHelpers.GetColumnName(lambda);
return TableAttributeHelpers.GetColumnName(column);
}

/// <summary>
/// Retrieves a TestEntity with the specified attributes.
/// Retrieves the TestEntity with the specified columns.
/// </summary>
/// <param name="service">Organization service</param>
/// <param name="id">Id of TestEntity to retrieve</param>
/// <param name="attrs">Expressions that specify attributes to retrieve</param>
/// <param name="columns">Expressions that specify columns to retrieve</param>
/// <returns>The retrieved TestEntity</returns>
public static TestEntity Retrieve(IOrganizationService service, Guid id, params Expression<Func<TestEntity, object>>[] attrs)
public static TestEntity Retrieve(IOrganizationService service, Guid id, params Expression<Func<TestEntity, object>>[] columns)
{
return service.Retrieve(id, attrs);
return service.Retrieve(id, columns);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public async Task Generates_Correct_Code_For_All_AttributeTypes()
DisplayName = "Test Entity",
Columns = new List<ColumnModel>
{
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" },
Expand Down Expand Up @@ -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" },
},
};

Expand Down
12 changes: 6 additions & 6 deletions tests/DataverseProxyGenerator.Tests/RetrieveMethodTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Func<Account, object>>[] attrs)");
file.Content.Should().Contain("return service.Retrieve(id, attrs);");
file.Content.Should().Contain("public static Account Retrieve(IOrganizationService service, Guid id, params Expression<Func<Account, object>>[] columns)");
file.Content.Should().Contain("return service.Retrieve(id, columns);");
}

[Fact]
Expand Down Expand Up @@ -88,12 +88,12 @@ public void EntityClass_ShouldGenerateStaticGetColumnNameMethod()
file.Content.Should().Contain("/// <summary>");
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("/// </summary>");
file.Content.Should().Contain("/// <param name=\"lambda\">Expression to pick the column</param>");
file.Content.Should().Contain("/// <param name=\"columns\">Expressions that specify columns to retrieve</param>");
file.Content.Should().Contain("/// <returns>Name of column</returns>");
file.Content.Should().Contain("/// <exception cref=\"ArgumentNullException\">If no expression is provided</exception>");
file.Content.Should().Contain("/// <exception cref=\"ArgumentException\">If the expression is not x => x.column</exception>");
file.Content.Should().Contain("public static string GetColumnName(Expression<Func<Account, object>> lambda)");
file.Content.Should().Contain("return TableAttributeHelpers.GetColumnName(lambda);");
file.Content.Should().Contain("public static string GetColumnName(Expression<Func<Account, object>> column)");
file.Content.Should().Contain("return TableAttributeHelpers.GetColumnName(column);");
}

[Fact]
Expand Down Expand Up @@ -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<T>(this IOrganizationService service, Guid id, params Expression<Func<T, object>>[] 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<T>();");
}
}