Skip to content

Commit d53ef2d

Browse files
authored
Merge pull request #63 from delegateas/rewrite-single-file
Enabled single-file generation
2 parents ac11440 + ab10cb8 commit d53ef2d

25 files changed

Lines changed: 891 additions & 171 deletions

appsettings.json

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/DataverseProxyGenerator.Core/Generation/CSharpProxyGenerator.cs

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DataverseProxyGenerator.Core.Domain;
22
using DataverseProxyGenerator.Core.Generation.Generators;
3+
using DataverseProxyGenerator.Core.Generation.Mappers;
34
using DataverseProxyGenerator.Core.Templates;
45

56
namespace DataverseProxyGenerator.Core.Generation;
@@ -40,22 +41,135 @@ public IEnumerable<GeneratedFile> GenerateCode(IEnumerable<TableModel> tables, X
4041
ArgumentNullException.ThrowIfNull(tables);
4142
ArgumentNullException.ThrowIfNull(config);
4243

43-
var files = new List<GeneratedFile>();
44-
var version = GetAssemblyVersion();
44+
var context = CreateGenerationContext(config);
45+
var tablesList = tables.ToList();
46+
var (interfaceColumns, tableToInterfaces) = PrepareIntersectionData(tablesList, config);
4547

46-
var context = new GenerationContext
48+
if (config.SingleFile)
49+
{
50+
return GenerateSingleFile(tablesList, interfaceColumns, tableToInterfaces, context);
51+
}
52+
53+
return GenerateMultipleFiles(tablesList, interfaceColumns, tableToInterfaces, context);
54+
}
55+
56+
private GenerationContext CreateGenerationContext(XrmGenerationConfig config)
57+
{
58+
return new GenerationContext
4759
{
4860
Namespace = config.NamespaceSetting ?? "DataverseContext",
49-
Version = version,
61+
Version = GetAssemblyVersion(),
5062
Templates = templateProvider,
5163
ServiceContextName = config.ServiceContextName,
5264
IntersectMapping = config.IntersectMapping,
5365
};
66+
}
5467

55-
var tablesList = tables.ToList();
68+
private static (Dictionary<string, HashSet<ColumnSignature>> InterfaceColumns, Dictionary<string, List<string>> TableToInterfaces)
69+
PrepareIntersectionData(List<TableModel> tablesList, XrmGenerationConfig config)
70+
{
5671
var tableDict = tablesList.ToDictionary(t => t.LogicalName, t => t, StringComparer.InvariantCulture);
5772
var tableColumns = BuildTableColumns(tablesList);
58-
var (interfaceColumns, tableToInterfaces) = BuildIntersectionData(config.IntersectMapping, tableDict, tableColumns);
73+
return BuildIntersectionData(config.IntersectMapping, tableDict, tableColumns);
74+
}
75+
76+
private static IEnumerable<GeneratedFile> GenerateSingleFile(
77+
List<TableModel> tablesList,
78+
Dictionary<string, HashSet<ColumnSignature>> interfaceColumns,
79+
Dictionary<string, List<string>> tableToInterfaces,
80+
GenerationContext context)
81+
{
82+
var templateModel = CreateSingleFileTemplateModel(tablesList, interfaceColumns, tableToInterfaces, context);
83+
84+
var templateName = "SingleFile.scriban-cs";
85+
var template = context.Templates.GetTemplate(templateName);
86+
var content = template.Render(templateModel, member => member.Name);
87+
88+
yield return new GeneratedFile($"{context.ServiceContextName}.cs", content);
89+
}
90+
91+
private static object CreateSingleFileTemplateModel(
92+
List<TableModel> tablesList,
93+
Dictionary<string, HashSet<ColumnSignature>> interfaceColumns,
94+
Dictionary<string, List<string>> tableToInterfaces,
95+
GenerationContext context)
96+
{
97+
var globalOptionsets = GetGlobalOptionsets(tablesList)
98+
.Select(enumCol => EnumMapper.MapToTemplateModel(enumCol, context))
99+
.ToList();
100+
var interfaces = CreateInterfaceModels(interfaceColumns, tablesList);
101+
102+
// Add interface lists to tables (without modifying TableModel structure)
103+
var tablesWithInterfaces = tablesList.Select(table =>
104+
{
105+
var tableInterfaces = tableToInterfaces.TryGetValue(table.LogicalName, out var ifaces) ? ifaces : new List<string>();
106+
return new
107+
{
108+
table,
109+
InterfacesList = tableInterfaces,
110+
};
111+
}).ToList();
112+
113+
// Prepare the template model with correct property names
114+
return new
115+
{
116+
@namespace = context.Namespace,
117+
version = context.Version,
118+
serviceContextName = context.ServiceContextName,
119+
tables = tablesWithInterfaces.Select(t => new
120+
{
121+
t.table.SchemaName,
122+
t.table.LogicalName,
123+
t.table.DisplayName,
124+
t.table.EntityTypeCode,
125+
t.table.PrimaryNameAttribute,
126+
t.table.PrimaryIdAttribute,
127+
t.table.IsIntersect,
128+
t.table.Columns,
129+
t.table.Relationships,
130+
InterfacesList = t.InterfacesList,
131+
}).ToList(),
132+
optionsets = globalOptionsets,
133+
interfaces = interfaces,
134+
};
135+
}
136+
137+
private static List<object> CreateInterfaceModels(
138+
Dictionary<string, HashSet<ColumnSignature>> interfaceColumns,
139+
List<TableModel> tablesList)
140+
{
141+
return interfaceColumns.Select(kvp => new
142+
{
143+
Name = kvp.Key,
144+
Columns = kvp.Value.Select(sig => FindMatchingColumn(sig, tablesList))
145+
.Where(c => c != null)
146+
.Select(col => new
147+
{
148+
SchemaName = Utilities.GenerationUtilities.SanitizeName(col!.SchemaName),
149+
col!.LogicalName,
150+
col.DisplayName,
151+
col.Description,
152+
TypeSignature = Utilities.GenerationUtilities.GetTypeSignature(col),
153+
})
154+
.ToList(),
155+
}).ToList<object>();
156+
}
157+
158+
private static ColumnModel? FindMatchingColumn(ColumnSignature sig, List<TableModel> tablesList)
159+
{
160+
return tablesList.SelectMany(t => t.Columns)
161+
.FirstOrDefault(c => c.SchemaName == sig.SchemaName &&
162+
(c.TypeName == sig.TypeName ||
163+
(c is EnumColumnModel enumCol && sig.TypeName == $"EnumColumnModel:{enumCol.OptionsetName}")));
164+
}
165+
166+
private IEnumerable<GeneratedFile> GenerateMultipleFiles(
167+
List<TableModel> tablesList,
168+
Dictionary<string, HashSet<ColumnSignature>> interfaceColumns,
169+
Dictionary<string, List<string>> tableToInterfaces,
170+
GenerationContext context)
171+
{
172+
var files = new List<GeneratedFile>();
59173

60174
// Generate intersection interfaces
61175
foreach (var kvp in interfaceColumns)
@@ -79,8 +193,8 @@ public IEnumerable<GeneratedFile> GenerateCode(IEnumerable<TableModel> tables, X
79193
}
80194

81195
// Generate enums
82-
var globalOptionsets = GetGlobalOptionsets(tablesList);
83-
foreach (var optionset in globalOptionsets)
196+
var globalOptionsetsMulti = GetGlobalOptionsets(tablesList);
197+
foreach (var optionset in globalOptionsetsMulti)
84198
{
85199
files.AddRange(enumGenerator.Generate(optionset, context));
86200
}
@@ -94,7 +208,8 @@ public IEnumerable<GeneratedFile> GenerateCode(IEnumerable<TableModel> tables, X
94208
files.AddRange(helperFileGenerator.Generate("TableAttributeHelpers", context));
95209
files.AddRange(helperFileGenerator.Generate("ExtendedEntity", context));
96210

97-
return files;
211+
foreach (var file in files)
212+
yield return file;
98213
}
99214

100215
private static IEnumerable<EnumColumnModel> GetGlobalOptionsets(IEnumerable<TableModel> tables)

src/DataverseProxyGenerator.Core/Generation/Common/BaseFileGenerator.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using DataverseProxyGenerator.Core.Generation.Utilities;
21
using Scriban;
32

43
namespace DataverseProxyGenerator.Core.Generation.Common;
@@ -44,25 +43,4 @@ protected static TemplateContext CreateTemplateContext(object model)
4443
templateContext.PushGlobal(Scriban.Runtime.ScriptObject.From(model));
4544
return templateContext;
4645
}
47-
48-
/// <summary>
49-
/// Sanitizes a name using the shared utility.
50-
/// </summary>
51-
/// <param name="name">The name to sanitize.</param>
52-
/// <param name="fallbackPrefix">Optional fallback prefix.</param>
53-
/// <returns>A sanitized name.</returns>
54-
protected static string SanitizeName(string name, string fallbackPrefix = "Item")
55-
{
56-
return NameSanitizer.SanitizeName(name, fallbackPrefix);
57-
}
58-
59-
/// <summary>
60-
/// Gets the type signature for a column using the shared utility.
61-
/// </summary>
62-
/// <param name="column">The column model.</param>
63-
/// <returns>The type signature.</returns>
64-
protected static string GetTypeSignature(Domain.ColumnModel column)
65-
{
66-
return TypeSignatureHelper.GetPropertyTypeSignature(column);
67-
}
6846
}

src/DataverseProxyGenerator.Core/Generation/Generators/CustomApiGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public IEnumerable<GeneratedFile> Generate(CustomApiModel customApi, GenerationC
1818
var requestTemplate = context.Templates.GetTemplate("CustomApiRequest.scriban-cs");
1919
var requestModel = CreateTemplateModel(customApi, context);
2020
var requestContent = requestTemplate.Render(requestModel);
21-
var sanitizedUniqueName = SanitizeName(customApi.UniqueName);
21+
var sanitizedUniqueName = GenerationUtilities.SanitizeName(customApi.UniqueName);
2222
var requestFilename = Path.Combine(FilePathHelper.CustomApiPath, $"{sanitizedUniqueName}Request.cs");
2323

2424
files.Add(new GeneratedFile(requestFilename, requestContent));
@@ -39,15 +39,15 @@ private static object CreateTemplateModel(CustomApiModel customApi, GenerationCo
3939
return new
4040
{
4141
unique_name = customApi.UniqueName, // Original for RequestName/ResponseName
42-
sanitized_unique_name = SanitizeName(customApi.UniqueName), // Sanitized for class names
42+
sanitized_unique_name = GenerationUtilities.SanitizeName(customApi.UniqueName), // Sanitized for class names
4343
display_name = customApi.DisplayName,
4444
description = customApi.Description,
4545
is_function = customApi.IsFunction,
4646
version = context.Version,
4747
@namespace = context.Namespace,
4848
request_parameters = customApi.RequestParameters.Select(p => new
4949
{
50-
name = SanitizeName(p.Name), // Sanitized for C# property names
50+
name = GenerationUtilities.SanitizeName(p.Name), // Sanitized for C# property names
5151
original_name = p.Name, // Original for Parameters collection access
5252
unique_name = p.UniqueName,
5353
display_name = p.DisplayName,
@@ -59,7 +59,7 @@ private static object CreateTemplateModel(CustomApiModel customApi, GenerationCo
5959
}).ToList(),
6060
response_properties = customApi.ResponseProperties.Select(p => new
6161
{
62-
name = SanitizeName(p.Name), // Sanitized for C# property names
62+
name = GenerationUtilities.SanitizeName(p.Name), // Sanitized for C# property names
6363
original_name = p.Name, // Original for Results collection access
6464
unique_name = p.UniqueName,
6565
display_name = p.DisplayName,
Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DataverseProxyGenerator.Core.Domain;
22
using DataverseProxyGenerator.Core.Generation.Common;
3+
using DataverseProxyGenerator.Core.Generation.Mappers;
34
using DataverseProxyGenerator.Core.Generation.Utilities;
45

56
namespace DataverseProxyGenerator.Core.Generation.Generators;
@@ -17,41 +18,11 @@ private static IEnumerable<GeneratedFile> GenerateInternal(EnumColumnModel input
1718
{
1819
ValidateContext(context);
1920

21+
var templateModel = EnumMapper.MapToTemplateModel(input, context);
2022
var template = context.Templates.GetTemplate("EnumOptionset.scriban-cs");
21-
var sanitizedOptionSetName = SanitizeName(input.OptionsetName, "UnknownOptionSet");
22-
23-
// Generate unique enum member names to handle duplicate labels using groupBy approach
24-
var sanitizedOptions = input.OptionsetValues
25-
.Select(kvp => new { kvp.Key, kvp.Value, SanitizedName = NameSanitizer.SanitizeEnumOptionName(kvp.Value, kvp.Key) })
26-
.ToList();
27-
28-
var optionsetValuesWithUniqueNames = sanitizedOptions
29-
.GroupBy(item => item.SanitizedName, StringComparer.OrdinalIgnoreCase)
30-
.SelectMany(group =>
31-
{
32-
var items = group.ToList();
33-
return items.Select((item, index) => new
34-
{
35-
Value = item.Key,
36-
Name = index == 0 ? item.SanitizedName : $"{item.SanitizedName}_{index}",
37-
Localizations =
38-
input.OptionLocalizations != null &&
39-
input.OptionLocalizations.TryGetValue(item.Key, out var value)
40-
? value : new Dictionary<int, string>(),
41-
});
42-
})
43-
.ToList();
44-
45-
var enumResult = template.Render(
46-
new
47-
{
48-
optionsetName = sanitizedOptionSetName,
49-
optionsetValues = optionsetValuesWithUniqueNames,
50-
@namespace = context.Namespace,
51-
version = context.Version,
52-
},
53-
member => member.Name);
23+
var enumResult = template.Render(templateModel, member => member.Name);
5424

25+
var sanitizedOptionSetName = DataverseProxyGenerator.Core.Generation.Utilities.GenerationUtilities.SanitizeName(input.OptionsetName, "UnknownOptionSet");
5526
yield return new GeneratedFile(FilePathHelper.GetOptionSetFilePath(sanitizedOptionSetName), enumResult);
5627
}
5728
}

src/DataverseProxyGenerator.Core/Generation/Generators/HelperFileGenerator.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using DataverseProxyGenerator.Core.Generation.Common;
2+
using DataverseProxyGenerator.Core.Generation.Mappers;
23
using DataverseProxyGenerator.Core.Generation.Utilities;
34

45
namespace DataverseProxyGenerator.Core.Generation.Generators;
@@ -16,15 +17,9 @@ private static IEnumerable<GeneratedFile> GenerateInternal(string templateName,
1617
{
1718
ValidateContext(context);
1819

20+
var templateModel = HelperFileMapper.MapToTemplateModel(templateName, context);
1921
var template = context.Templates.GetTemplate($"{templateName}.scriban-cs");
20-
21-
var result = template.Render(
22-
new
23-
{
24-
@namespace = context.Namespace,
25-
version = context.Version,
26-
},
27-
member => member.Name);
22+
var result = template.Render(templateModel, member => member.Name);
2823

2924
yield return new GeneratedFile(FilePathHelper.GetHelperFilePath(templateName), result);
3025
}
Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DataverseProxyGenerator.Core.Domain;
22
using DataverseProxyGenerator.Core.Generation.Common;
3+
using DataverseProxyGenerator.Core.Generation.Mappers;
34
using DataverseProxyGenerator.Core.Generation.Utilities;
45

56
namespace DataverseProxyGenerator.Core.Generation.Generators;
@@ -16,28 +17,12 @@ public IEnumerable<GeneratedFile> Generate((string InterfaceName, IEnumerable<Co
1617
private static IEnumerable<GeneratedFile> GenerateInternal((string InterfaceName, IEnumerable<ColumnModel> Columns) input, GenerationContext context)
1718
{
1819
ValidateContext(context);
19-
var (interfaceName, columns) = input;
20-
var template = context.Templates.GetTemplate("IntersectionInterface.scriban-cs");
21-
var sanitizedInterfaceName = SanitizeName(interfaceName);
22-
23-
var columnData = columns.Select(col => new
24-
{
25-
SchemaName = SanitizeName(col.SchemaName),
26-
col.DisplayName,
27-
col.Description,
28-
TypeSignature = GetTypeSignature(col),
29-
});
3020

31-
var interfaceResult = template.Render(
32-
new
33-
{
34-
interfaceName = sanitizedInterfaceName,
35-
@namespace = context.Namespace,
36-
columns = columnData,
37-
version = context.Version,
38-
},
39-
member => member.Name);
21+
var templateModel = IntersectionInterfaceMapper.MapToTemplateModel(input, context);
22+
var template = context.Templates.GetTemplate("IntersectionInterface.scriban-cs");
23+
var interfaceResult = template.Render(templateModel, member => member.Name);
4024

25+
var sanitizedInterfaceName = DataverseProxyGenerator.Core.Generation.Utilities.GenerationUtilities.SanitizeName(input.InterfaceName);
4126
yield return new GeneratedFile(FilePathHelper.GetIntersectionInterfaceFilePath(sanitizedInterfaceName), interfaceResult);
4227
}
4328
}

0 commit comments

Comments
 (0)