Skip to content

Commit 9809060

Browse files
committed
Preserve model attributes; support plural name
1 parent 82562ae commit 9809060

15 files changed

Lines changed: 489 additions & 4 deletions

File tree

docs/variables.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ The following are common variables to use in the configuration file
3535

3636
`{Entity.Name}` is the name of the current entity
3737

38+
### Entity Plural Name
39+
40+
`{Entity.Plural}` is the plural name of the current entity
41+
3842
### Model Name
3943

4044
`{Model.Name}` is the name of the current model

src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Entity.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ public Entity()
5353
/// </value>
5454
public string EntityClass { get; set; } = null!;
5555

56+
/// <summary>
57+
/// Gets or sets the plural name of the entity.
58+
/// </summary>
59+
/// <value>
60+
/// The plural name of the entity.
61+
/// </value>
62+
public string EntityPlural { get; set; } = null!;
63+
5664
/// <summary>
5765
/// Gets or sets the entity base class.
5866
/// </summary>
@@ -216,12 +224,14 @@ void IOptionVariable.Set(VariableDictionary variableDictionary)
216224
variableDictionary.Set(VariableConstants.TableSchema, TableSchema);
217225
variableDictionary.Set(VariableConstants.TableName, TableName);
218226
variableDictionary.Set(VariableConstants.EntityName, EntityClass);
227+
variableDictionary.Set(VariableConstants.EntityPlural, EntityPlural);
219228
}
220229

221230
void IOptionVariable.Remove(VariableDictionary variableDictionary)
222231
{
223232
variableDictionary.Remove(VariableConstants.TableSchema);
224233
variableDictionary.Remove(VariableConstants.TableName);
225234
variableDictionary.Remove(VariableConstants.EntityName);
235+
variableDictionary.Remove(VariableConstants.EntityPlural);
226236
}
227237
}

src/EntityFrameworkCore.Generator.Core/Metadata/Generation/Model.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ public class Model : ModelBase, IOptionVariable
55
public Model()
66
{
77
Properties = [];
8+
PropertyAttributes = [];
89
}
910

1011
public Entity Entity { get; set; } = null!;
@@ -31,6 +32,8 @@ public Model()
3132

3233
public PropertyCollection Properties { get; set; }
3334

35+
public Dictionary<string, List<string>> PropertyAttributes { get; }
36+
3437

3538
void IOptionVariable.Set(VariableDictionary variableDictionary)
3639
{
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Diagnostics;
2+
3+
namespace EntityFrameworkCore.Generator.Metadata.Parsing;
4+
5+
[DebuggerDisplay("Model: {ModelClass}, Properties: {Properties.Count}")]
6+
public class ParsedModel
7+
{
8+
public ParsedModel()
9+
{
10+
Properties = [];
11+
}
12+
13+
public string ModelClass { get; set; } = null!;
14+
15+
public List<ParsedModelProperty> Properties { get; }
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Diagnostics;
2+
3+
namespace EntityFrameworkCore.Generator.Metadata.Parsing;
4+
5+
[DebuggerDisplay("Property: {PropertyName}")]
6+
public class ParsedModelProperty
7+
{
8+
public ParsedModelProperty()
9+
{
10+
Attributes = [];
11+
}
12+
13+
public string PropertyName { get; set; } = null!;
14+
15+
public List<string> Attributes { get; }
16+
}

src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ private Entity CreateEntity(EntityContext entityContext, RelationBase relationSc
134134
entityClass = ToClassName(relationSchema);
135135

136136
entityClass = _namer.UniqueClassName(entityClass);
137+
var pluralName = entityClass.Pluralize(false);
137138

138139
var entityNamespace = _options.Data.Entity.Namespace ?? "Data.Entities";
139140
var entiyBaseClass = _options.Data.Entity.BaseClass;
@@ -148,6 +149,7 @@ private Entity CreateEntity(EntityContext entityContext, RelationBase relationSc
148149
contextName = _namer.UniqueContextName(contextName);
149150

150151
entity.EntityClass = entityClass;
152+
entity.EntityPlural = pluralName;
151153
entity.EntityNamespace = entityNamespace;
152154
entity.EntityBaseClass = entiyBaseClass;
153155

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using EntityFrameworkCore.Generator.Metadata.Parsing;
2+
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace EntityFrameworkCore.Generator.Parsing;
8+
9+
public class ModelParser
10+
{
11+
private readonly ILogger _logger;
12+
13+
public ModelParser(ILoggerFactory loggerFactory)
14+
{
15+
_logger = loggerFactory.CreateLogger<ModelParser>();
16+
}
17+
18+
public ParsedModel? ParseFile(string modelFile)
19+
{
20+
if (string.IsNullOrEmpty(modelFile) || !File.Exists(modelFile))
21+
return null;
22+
23+
_logger.LogDebug(
24+
"Parsing Model File: '{ModelFile}'",
25+
Path.GetFileName(modelFile));
26+
27+
var code = File.ReadAllText(modelFile);
28+
return ParseCode(code);
29+
}
30+
31+
public ParsedModel? ParseCode(string code)
32+
{
33+
if (string.IsNullOrWhiteSpace(code))
34+
return null;
35+
36+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
37+
var root = (CompilationUnitSyntax)syntaxTree.GetRoot();
38+
39+
var visitor = new ModelVisitor();
40+
visitor.Visit(root);
41+
42+
var parsedModel = visitor.ParsedModel;
43+
if (parsedModel == null || parsedModel.Properties.Count == 0)
44+
return null;
45+
46+
_logger.LogDebug(
47+
"Parsed Model Class: '{ModelClass}'; Properties: {Properties}",
48+
parsedModel.ModelClass,
49+
parsedModel.Properties.Count);
50+
51+
return parsedModel;
52+
}
53+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using EntityFrameworkCore.Generator.Metadata.Parsing;
2+
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
6+
namespace EntityFrameworkCore.Generator.Parsing;
7+
8+
public class ModelVisitor : CSharpSyntaxWalker
9+
{
10+
private string? _currentClass;
11+
12+
public ParsedModel? ParsedModel { get; set; }
13+
14+
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
15+
{
16+
var previousClass = _currentClass;
17+
_currentClass = node.Identifier.ValueText;
18+
19+
ParsedModel ??= new ParsedModel { ModelClass = _currentClass };
20+
21+
base.VisitClassDeclaration(node);
22+
23+
_currentClass = previousClass;
24+
}
25+
26+
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
27+
{
28+
ParseProperty(node);
29+
base.VisitPropertyDeclaration(node);
30+
}
31+
32+
private void ParseProperty(PropertyDeclarationSyntax node)
33+
{
34+
if (string.IsNullOrEmpty(_currentClass) || ParsedModel == null || node.AttributeLists.Count == 0)
35+
return;
36+
37+
var propertyName = node.Identifier.ValueText;
38+
if (string.IsNullOrEmpty(propertyName))
39+
return;
40+
41+
var attributes = node.AttributeLists
42+
.Select(a => a.ToString())
43+
.Where(a => !string.IsNullOrWhiteSpace(a))
44+
.ToList();
45+
46+
if (attributes.Count == 0)
47+
return;
48+
49+
var parsedProperty = new ParsedModelProperty
50+
{
51+
PropertyName = propertyName
52+
};
53+
54+
parsedProperty.Attributes.AddRange(attributes);
55+
ParsedModel.Properties.Add(parsedProperty);
56+
}
57+
}

src/EntityFrameworkCore.Generator.Core/Parsing/SourceSynchronizer.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,82 @@ public bool UpdateFromSource(EntityContext generatedContext, GeneratorOptions op
2828
// make sure to update the entities before the context
2929
UpdateFromMapping(generatedContext, options.Data.Mapping.Directory);
3030
UpdateFromContext(generatedContext, options.Data.Context.Directory);
31+
UpdateFromModels(generatedContext, options);
3132

3233
return true;
3334
}
3435

36+
private void UpdateFromModels(EntityContext generatedContext, GeneratorOptions options)
37+
{
38+
if (generatedContext == null || options == null)
39+
return;
40+
41+
var parser = new ModelParser(_loggerFactory);
42+
43+
foreach (var entity in generatedContext.Entities)
44+
{
45+
options.Variables.Set(entity);
46+
47+
foreach (var model in entity.Models)
48+
{
49+
options.Variables.Set(model);
50+
51+
UpdateFromModel(parser, model, options);
52+
53+
options.Variables.Remove(model);
54+
}
55+
56+
options.Variables.Remove(entity);
57+
}
58+
}
59+
60+
private void UpdateFromModel(ModelParser parser, Model model, GeneratorOptions options)
61+
{
62+
var modelDirectory = GetModelDirectory(model, options) ?? "Data\\Models";
63+
if (!Directory.Exists(modelDirectory))
64+
return;
65+
66+
var modelFile = Path.Combine(modelDirectory, model.ModelClass + ".cs");
67+
var parsedModel = parser.ParseFile(modelFile);
68+
if (parsedModel == null || parsedModel.ModelClass != model.ModelClass)
69+
return;
70+
71+
foreach (var parsedProperty in parsedModel.Properties)
72+
{
73+
var property = model.Properties.ByProperty(parsedProperty.PropertyName);
74+
if (property == null)
75+
continue;
76+
77+
_logger.LogInformation(
78+
" Preserve attributes for Model Property '{PropertyName}' in Model '{ModelClass}'.",
79+
parsedProperty.PropertyName,
80+
model.ModelClass);
81+
82+
model.PropertyAttributes[parsedProperty.PropertyName] = [.. parsedProperty.Attributes];
83+
}
84+
}
85+
86+
private static string? GetModelDirectory(Model model, GeneratorOptions options)
87+
{
88+
if (model.ModelType == ModelType.Create)
89+
{
90+
return options.Model.Create.Directory.HasValue()
91+
? options.Model.Create.Directory
92+
: options.Model.Shared.Directory;
93+
}
94+
95+
if (model.ModelType == ModelType.Update)
96+
{
97+
return options.Model.Update.Directory.HasValue()
98+
? options.Model.Update.Directory
99+
: options.Model.Shared.Directory;
100+
}
101+
102+
return options.Model.Read.Directory.HasValue()
103+
? options.Model.Read.Directory
104+
: options.Model.Shared.Directory;
105+
}
106+
35107

36108
private void UpdateFromContext(EntityContext generatedContext, string? contextDirectory)
37109
{

src/EntityFrameworkCore.Generator.Core/Templates/ModelClassTemplate.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ private void GenerateProperties()
9595
GeneratePropertyDocumentation(property);
9696
}
9797

98+
if (_model.PropertyAttributes.TryGetValue(property.PropertyName, out var attributes) && attributes.Count > 0)
99+
{
100+
foreach (var attribute in attributes)
101+
CodeBuilder.AppendLine(attribute);
102+
}
103+
98104
if (property.IsNullable == true && (property.SystemType.IsValueType || Options.Project.Nullable))
99105
CodeBuilder.AppendLine($"public {ToNullablePropertyType(propertyType)} {propertyName} {{ get; set; }}");
100106
else if (Options.Project.Nullable && !property.SystemType.IsValueType)

0 commit comments

Comments
 (0)