• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / EntityFrameworkCore.Generator / 28023361569

23 Jun 2026 11:37AM UTC coverage: 72.209%. First build
28023361569

Pull #548

github

web-flow
Merge 91759c43b into b377f2e8a
Pull Request #548: Closes #547 Add option to add relationship property attributes

1000 of 1877 branches covered (53.28%)

Branch coverage included in aggregate %.

3 of 11 new or added lines in 2 files covered. (27.27%)

4233 of 5370 relevant lines covered (78.83%)

2014.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.14
/src/EntityFrameworkCore.Generator.Core/Templates/EntityClassTemplate.cs
1
using EntityFrameworkCore.Generator.Extensions;
2
using EntityFrameworkCore.Generator.Metadata.Generation;
3
using EntityFrameworkCore.Generator.Options;
4

5
namespace EntityFrameworkCore.Generator.Templates;
6

7
public class EntityClassTemplate : CodeTemplateBase
8
{
9
    private readonly Entity _entity;
10

11
    public EntityClassTemplate(Entity entity, GeneratorOptions options) : base(options)
165✔
12
    {
13
        _entity = entity;
165✔
14
    }
165✔
15

16
    public override string WriteCode()
17
    {
18
        CodeBuilder.Clear();
165✔
19

20
        if (Options.Data.Entity.Header.HasValue())
165!
21
            CodeBuilder.AppendLine(Options.Data.Entity.Header).AppendLine();
×
22

23
        CodeBuilder.AppendLine("using System;");
165✔
24
        CodeBuilder.AppendLine("using System.Collections.Generic;");
165✔
25
        if (Options.Data.Entity.MappingAttributes)
165✔
26
        {
27
            CodeBuilder.AppendLine("using System.ComponentModel.DataAnnotations;");
137✔
28
            CodeBuilder.AppendLine("using System.ComponentModel.DataAnnotations.Schema;");
137✔
29
        }
30
        CodeBuilder.AppendLine();
165✔
31

32
        CodeBuilder.Append($"namespace {_entity.EntityNamespace}");
165✔
33

34
        if (Options.Project.FileScopedNamespace)
165✔
35
        {
36
            CodeBuilder.AppendLine(";");
137✔
37
            CodeBuilder.AppendLine();
137✔
38
            GenerateClass();
137✔
39
        }
40
        else
41
        {
42
            CodeBuilder.AppendLine();
28✔
43
            CodeBuilder.AppendLine("{");
28✔
44

45
            using (CodeBuilder.Indent())
28✔
46
            {
47
                GenerateClass();
28✔
48
            }
28✔
49

50
            CodeBuilder.AppendLine("}");
28✔
51
        }
52

53
        return CodeBuilder.ToString();
165✔
54
    }
55

56
    private void GenerateClass()
57
    {
58
        var entityClass = _entity.EntityClass.ToSafeName();
165✔
59

60
        if (Options.Data.Entity.Document)
165✔
61
        {
62
            GenerateClassDocumentation();
138✔
63
        }
64
        if (Options.Data.Entity.MappingAttributes)
165✔
65
        {
66
            if (_entity.TableSchema.HasValue())
137✔
67
                CodeBuilder.AppendLine($"[Table(\"{_entity.TableName}\", Schema = \"{_entity.TableSchema}\")]");
103✔
68
            else
69
                CodeBuilder.AppendLine($"[Table(\"{_entity.TableName}\")]");
34✔
70
        }
71
        CodeBuilder.AppendLine($"public partial class {entityClass}");
165✔
72

73
        if (_entity.EntityBaseClass.HasValue())
165!
74
        {
75
            var entityBaseClass = _entity.EntityBaseClass.ToSafeName();
×
76
            using (CodeBuilder.Indent())
×
77
                CodeBuilder.AppendLine($": {entityBaseClass}");
×
78
        }
79

80
        CodeBuilder.AppendLine("{");
165✔
81

82
        using (CodeBuilder.Indent())
165✔
83
        {
84
            GenerateConstructor();
165✔
85

86
            GenerateProperties();
165✔
87
            GenerateRelationshipProperties();
165✔
88
        }
165✔
89

90
        CodeBuilder.AppendLine("}");
165✔
91

92
    }
165✔
93

94
    private void GenerateConstructor()
95
    {
96
        var relationships = _entity.Relationships
165✔
97
            .Where(r => r.Cardinality == Cardinality.Many)
165✔
98
            .OrderBy(r => r.PropertyName)
165✔
99
            .ToList();
165✔
100

101
        var entityClass = _entity.EntityClass.ToSafeName();
165✔
102

103
        if (Options.Data.Entity.Document)
165✔
104
        {
105
            GenerateConstructorDocumentation(entityClass, relationships.Count > 0);
138✔
106
        }
107

108
        CodeBuilder.AppendLine($"public {entityClass}()");
165✔
109
        CodeBuilder.AppendLine("{");
165✔
110

111
        using (CodeBuilder.Indent())
165✔
112
        {
113
            CodeBuilder.AppendLine("#region Generated Constructor");
165✔
114
            foreach (var relationship in relationships)
474✔
115
            {
116
                var propertyName = relationship.PropertyName.ToSafeName();
72✔
117

118
                var primaryNamespace = relationship.PrimaryEntity.EntityNamespace;
72✔
119
                var primaryName = relationship.PrimaryEntity.EntityClass.ToSafeName();
72✔
120
                var primaryFullName = _entity.EntityNamespace != primaryNamespace
72!
121
                    ? $"{primaryNamespace}.{primaryName}"
72✔
122
                    : primaryName;
72✔
123

124
                CodeBuilder.AppendLine($"{propertyName} = new HashSet<{primaryFullName}>();");
72✔
125
            }
126
            CodeBuilder.AppendLine("#endregion");
165✔
127
        }
165✔
128

129
        CodeBuilder.AppendLine("}");
165✔
130
        CodeBuilder.AppendLine();
165✔
131
    }
165✔
132

133
    private void GenerateProperties()
134
    {
135
        CodeBuilder.AppendLine("#region Generated Properties");
165✔
136
        foreach (var property in _entity.Properties)
3,030✔
137
        {
138
            var propertyType = GetPropertyType(property);
1,350✔
139
            var propertyName = property.PropertyName.ToSafeName();
1,350✔
140

141
            if (Options.Data.Entity.Document)
1,350✔
142
            {
143
                GeneratePropertyDocumentation(property);
1,074✔
144
            }
145

146
            if (Options.Data.Entity.MappingAttributes)
1,350✔
147
            {
148
                if (property.IsPrimaryKey == true)
1,073✔
149
                {
150
                    CodeBuilder.AppendLine("[Key]");
140✔
151
                }
152

153
                if (property.IsConcurrencyToken == true)
1,073✔
154
                {
155
                    CodeBuilder.AppendLine("[ConcurrencyCheck]");
10✔
156
                }
157

158
                CodeBuilder.AppendLine($"[Column(\"{property.ColumnName}\", TypeName = \"{property.NativeType}\")]");
1,073✔
159

160
                if (property.IsIdentity == true)
1,073✔
161
                {
162
                    CodeBuilder.AppendLine("[DatabaseGenerated(DatabaseGeneratedOption.Identity)]");
59✔
163
                }
164
                else if (property.IsRowVersion == true || property.IsComputed == true)
1,014✔
165
                {
166
                    CodeBuilder.AppendLine("[DatabaseGenerated(DatabaseGeneratedOption.Computed)]");
28✔
167
                }
168
            }
169

170
            if (property.IsNullable == true && (property.SystemType.IsValueType || Options.Project.Nullable))
1,350✔
171
                CodeBuilder.AppendLine($"public {ToNullablePropertyType(propertyType)} {propertyName} {{ get; set; }}");
572✔
172
            else if (Options.Project.Nullable && !property.SystemType.IsValueType)
778✔
173
                CodeBuilder.AppendLine($"public {propertyType} {propertyName} {{ get; set; }} = null!;");
161✔
174
            else
175
                CodeBuilder.AppendLine($"public {propertyType} {propertyName} {{ get; set; }}");
617✔
176

177
            CodeBuilder.AppendLine();
1,350✔
178
        }
179
        CodeBuilder.AppendLine("#endregion");
165✔
180
        CodeBuilder.AppendLine();
165✔
181
    }
165✔
182

183
    private static string GetPropertyType(Property property)
184
    {
185
        return property.SystemTypeName.HasValue()
1,350✔
186
            ? property.SystemTypeName
1,350✔
187
            : property.SystemType.ToType();
1,350✔
188
    }
189

190
    private static string ToNullablePropertyType(string propertyType)
191
    {
192
        return propertyType.EndsWith('?')
572!
193
            ? propertyType
572✔
194
            : propertyType + "?";
572✔
195
    }
196

197
    private void GenerateClassDocumentation()
198
    {
199
        var entityName = ToXmlText(_entity.EntityClass);
138✔
200
        var sourceName = ToXmlText(GetQualifiedTableName());
138✔
201
        var sourceType = _entity.IsView ? "view" : "table";
138✔
202

203
        CodeBuilder.AppendLine("/// <summary>");
138✔
204

205
        if (sourceName.HasValue())
138!
206
            CodeBuilder.AppendLine($"/// Represents the <c>{entityName}</c> entity mapped to the <c>{sourceName}</c> {sourceType}.");
138✔
207
        else
208
            CodeBuilder.AppendLine($"/// Represents the <c>{entityName}</c> entity.");
×
209

210
        CodeBuilder.AppendLine("/// </summary>");
138✔
211
    }
138✔
212

213
    private void GenerateConstructorDocumentation(string entityClass, bool initializesCollections)
214
    {
215
        CodeBuilder.AppendLine("/// <summary>");
138✔
216

217
        if (initializesCollections)
138✔
218
            CodeBuilder.AppendLine($"/// Initializes a new instance of the <see cref=\"{entityClass}\"/> class and its collection navigation properties.");
29✔
219
        else
220
            CodeBuilder.AppendLine($"/// Initializes a new instance of the <see cref=\"{entityClass}\"/> class.");
109✔
221

222
        CodeBuilder.AppendLine("/// </summary>");
138✔
223
    }
138✔
224

225
    private void GeneratePropertyDocumentation(Property property)
226
    {
227
        var propertyName = ToXmlText(property.PropertyName);
1,074✔
228
        var columnName = ToXmlText(property.ColumnName);
1,074✔
229

230
        CodeBuilder.AppendLine("/// <summary>");
1,074✔
231

232
        if (columnName.HasValue())
1,074!
233
            CodeBuilder.AppendLine($"/// Gets or sets the <c>{propertyName}</c> value mapped to the <c>{columnName}</c> column.");
1,074✔
234
        else
235
            CodeBuilder.AppendLine($"/// Gets or sets the <c>{propertyName}</c> value.");
×
236

237
        CodeBuilder.AppendLine("/// </summary>");
1,074✔
238
        CodeBuilder.AppendLine("/// <value>");
1,074✔
239
        CodeBuilder.AppendLine($"/// The <c>{propertyName}</c> entity value.");
1,074✔
240
        CodeBuilder.AppendLine("/// </value>");
1,074✔
241
    }
1,074✔
242

243
    private string? GetQualifiedTableName()
244
    {
245
        if (_entity.TableName.IsNullOrEmpty())
138!
246
            return _entity.TableName;
×
247

248
        return _entity.TableSchema.HasValue()
138✔
249
            ? $"{_entity.TableSchema}.{_entity.TableName}"
138✔
250
            : _entity.TableName;
138✔
251
    }
252

253
    private void GenerateRelationshipProperties()
254
    {
255
        CodeBuilder.AppendLine("#region Generated Relationships");
165✔
256
        foreach (var relationship in _entity.Relationships.OrderBy(r => r.PropertyName))
642✔
257
        {
258
            var propertyName = relationship.PropertyName.ToSafeName();
156✔
259
            var primaryNamespace = relationship.PrimaryEntity.EntityNamespace;
156✔
260
            var primaryName = relationship.PrimaryEntity.EntityClass.ToSafeName();
156✔
261
            var primaryFullName = _entity.EntityNamespace != primaryNamespace
156!
262
                ? $"{primaryNamespace}.{primaryName}"
156✔
263
                : primaryName;
156✔
264

265
            if (relationship.Cardinality == Cardinality.Many)
156✔
266
            {
267
                if (Options.Data.Entity.Document)
72✔
268
                {
269
                    GenerateCollectionRelationshipDocumentation(primaryFullName);
60✔
270
                }
271

272
                if (!Options.Data.Entity.RelationshipAttributes.IsNullOrWhiteSpace())
72!
273
                {
NEW
274
                    var splittedAttributes = Options.Data.Entity.RelationshipAttributes.Split(';').Distinct()
×
NEW
275
                        .Where(s => !s.IsNullOrWhiteSpace());
×
276

NEW
277
                    foreach (var attribute in splittedAttributes)
×
NEW
278
                        CodeBuilder.AppendLine($"[{attribute}]");
×
279
                }
280

281
                CodeBuilder.AppendLine($"public virtual ICollection<{primaryFullName}> {propertyName} {{ get; set; }}");
72✔
282
                CodeBuilder.AppendLine();
72✔
283
            }
284
            else
285
            {
286
                if (Options.Data.Entity.Document)
84✔
287
                {
288
                    GenerateReferenceRelationshipDocumentation(primaryFullName);
68✔
289

290
                    foreach (var property in relationship.Properties)
278✔
291
                    {
292
                        var relatedPropertyName = property.PropertyName.ToSafeName();
71✔
293
                        CodeBuilder.AppendLine($"/// <seealso cref=\"{relatedPropertyName}\" />");
71✔
294
                    }
295
                }
296

297
                if (!Options.Data.Entity.RelationshipAttributes.IsNullOrWhiteSpace())
84!
298
                {
NEW
299
                    var splittedAttributes = Options.Data.Entity.RelationshipAttributes.Split(';').Distinct()
×
NEW
300
                        .Where(s => !s.IsNullOrWhiteSpace());
×
301

NEW
302
                    foreach (var attribute in splittedAttributes)
×
NEW
303
                        CodeBuilder.AppendLine($"[{attribute}]");
×
304
                }
305

306
                if (!Options.Project.Nullable)
84✔
307
                    CodeBuilder.AppendLine($"public virtual {primaryFullName} {propertyName} {{ get; set; }}");
17✔
308
                else if (relationship.Cardinality == Cardinality.One)
67✔
309
                    CodeBuilder.AppendLine($"public virtual {primaryFullName} {propertyName} {{ get; set; }} = null!;");
53✔
310
                else
311
                    CodeBuilder.AppendLine($"public virtual {primaryFullName}? {propertyName} {{ get; set; }}");
14✔
312

313
                CodeBuilder.AppendLine();
84✔
314
            }
315
        }
316

317
        CodeBuilder.AppendLine("#endregion");
165✔
318
        CodeBuilder.AppendLine();
165✔
319
    }
165✔
320

321
    private void GenerateCollectionRelationshipDocumentation(string primaryFullName)
322
    {
323
        CodeBuilder.AppendLine("/// <summary>");
60✔
324
        CodeBuilder.AppendLine($"/// Gets or sets the related <see cref=\"{primaryFullName}\" /> entity collection.");
60✔
325
        CodeBuilder.AppendLine("/// </summary>");
60✔
326
        CodeBuilder.AppendLine("/// <value>");
60✔
327
        CodeBuilder.AppendLine($"/// The related <see cref=\"{primaryFullName}\" /> entity collection.");
60✔
328
        CodeBuilder.AppendLine("/// </value>");
60✔
329
    }
60✔
330

331
    private void GenerateReferenceRelationshipDocumentation(string primaryFullName)
332
    {
333
        CodeBuilder.AppendLine("/// <summary>");
68✔
334
        CodeBuilder.AppendLine($"/// Gets or sets the related <see cref=\"{primaryFullName}\" /> entity.");
68✔
335
        CodeBuilder.AppendLine("/// </summary>");
68✔
336
        CodeBuilder.AppendLine("/// <value>");
68✔
337
        CodeBuilder.AppendLine($"/// The related <see cref=\"{primaryFullName}\" /> entity.");
68✔
338
        CodeBuilder.AppendLine("/// </value>");
68✔
339
    }
68✔
340
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc