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

loresoft / EntityFrameworkCore.Generator / 27851701975

19 Jun 2026 10:42PM UTC coverage: 72.302% (+0.5%) from 71.825%
27851701975

push

github

pwelter34
tweak tests

997 of 1869 branches covered (53.34%)

Branch coverage included in aggregate %.

4229 of 5359 relevant lines covered (78.91%)

2018.55 hits per line

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

79.45
/src/EntityFrameworkCore.Generator.Core/ModelGenerator.cs
1
using System.Data;
2
using System.Globalization;
3
using System.Text;
4
using System.Text.RegularExpressions;
5

6
using EntityFrameworkCore.Generator.Extensions;
7
using EntityFrameworkCore.Generator.Metadata.Generation;
8
using EntityFrameworkCore.Generator.Options;
9

10
using Humanizer;
11

12
using Microsoft.Extensions.Logging;
13

14
using SchemaSaurus.Metadata;
15

16
using Model = EntityFrameworkCore.Generator.Metadata.Generation.Model;
17
using Property = EntityFrameworkCore.Generator.Metadata.Generation.Property;
18

19
namespace EntityFrameworkCore.Generator;
20

21
public partial class ModelGenerator
22
{
23
    private readonly UniqueNamer _namer;
24
    private readonly ILogger _logger;
25
    private GeneratorOptions _options = null!;
26

27
    public ModelGenerator(ILoggerFactory logger)
68✔
28
    {
29
        _logger = logger.CreateLogger<ModelGenerator>();
68✔
30
        _namer = new UniqueNamer();
68✔
31
    }
68✔
32

33
    public EntityContext Generate(GeneratorOptions options, DatabaseModel databaseModel)
34
    {
35
        ArgumentNullException.ThrowIfNull(options);
68✔
36
        ArgumentNullException.ThrowIfNull(databaseModel);
68✔
37

38
        LogBuildingCodeGenerationModel(_logger, databaseModel.DatabaseName);
68✔
39

40
        _options = options;
68✔
41

42
        var entityContext = new EntityContext();
68✔
43
        entityContext.DatabaseName = databaseModel.DatabaseName;
68✔
44

45
        // update database variables
46
        _options.Database.Name = ToLegalName(databaseModel.DatabaseName);
68✔
47

48

49
        string contextClass = _options.Data.Context.Name ?? "DataContext";
68!
50
        contextClass = _namer.UniqueClassName(contextClass);
68✔
51

52
        string contextNamespace = _options.Data.Context.Namespace ?? "Data";
68!
53
        string contextBaseClass = _options.Data.Context.BaseClass ?? "DbContext";
68!
54

55
        entityContext.ContextClass = contextClass;
68✔
56
        entityContext.ContextNamespace = contextNamespace;
68✔
57
        entityContext.ContextBaseClass = contextBaseClass;
68✔
58

59
        foreach (var table in databaseModel.Tables)
3,736✔
60
        {
61
            if (IsIgnored(table, _options.Database.Exclude.Tables))
1,800✔
62
            {
63
                LogSkippingRelation(_logger, "Table", table.Schema, table.Name);
2✔
64
                continue;
2✔
65
            }
66

67
            LogProcessingRelation(_logger, "Table", table.Schema, table.Name);
1,798✔
68

69
            _options.Variables.Set(VariableConstants.TableSchema, ToLegalName(table.Schema));
1,798✔
70
            _options.Variables.Set(VariableConstants.TableName, ToLegalName(table.Name));
1,798✔
71

72
            var entity = GetEntity(entityContext, table);
1,798✔
73
            GetModels(entity);
1,798✔
74
        }
75

76
        foreach (var view in databaseModel.Views)
340✔
77
        {
78
            if (IsIgnored(view, _options.Database.Exclude.Tables))
102!
79
            {
80
                LogSkippingRelation(_logger, "View", view.Schema, view.Name);
×
81
                continue;
×
82
            }
83

84
            LogProcessingRelation(_logger, "View", view.Schema, view.Name);
102✔
85

86
            _options.Variables.Set(VariableConstants.TableSchema, ToLegalName(view.Schema));
102✔
87
            _options.Variables.Set(VariableConstants.TableName, ToLegalName(view.Name));
102✔
88

89
            var entity = GetEntity(entityContext, view);
102✔
90
            GetModels(entity);
102✔
91
        }
92

93
        _options.Variables.Remove(VariableConstants.TableName);
68✔
94
        _options.Variables.Remove(VariableConstants.TableSchema);
68✔
95

96
        return entityContext;
68✔
97
    }
98

99

100
    private Entity GetEntity(EntityContext entityContext, RelationBase relationSchema, bool processRelationships = true, bool processMethods = true)
101
    {
102
        var entity = entityContext.Entities.ByTable(relationSchema.Name, relationSchema.Schema)
2,889✔
103
            ?? CreateEntity(entityContext, relationSchema);
2,889✔
104

105
        if (!entity.Properties.IsProcessed)
2,889✔
106
            CreateProperties(entity, relationSchema);
1,900✔
107

108
        if (relationSchema is not Table tableSchema)
2,889✔
109
            return entity;
102✔
110

111
        if (processRelationships && !entity.Relationships.IsProcessed)
2,787!
112
            CreateRelationships(entityContext, entity, tableSchema);
1,798✔
113

114
        if (processMethods && !entity.Methods.IsProcessed)
2,787!
115
            CreateMethods(entity, tableSchema);
1,798✔
116

117
        entity.IsProcessed = true;
2,787✔
118
        return entity;
2,787✔
119
    }
120

121
    private Entity CreateEntity(EntityContext entityContext, RelationBase relationSchema)
122
    {
123
        var entity = new Entity
1,900✔
124
        {
1,900✔
125
            Context = entityContext,
1,900✔
126
            TableName = relationSchema.Name,
1,900✔
127
            TableSchema = relationSchema.Schema
1,900✔
128
        };
1,900✔
129

130
        // add to context
131
        entityContext.Entities.Add(entity);
1,900✔
132

133
        var entityClass = _options.Data.Entity.Name;
1,900✔
134
        if (entityClass.IsNullOrEmpty())
1,900!
135
            entityClass = ToClassName(relationSchema);
1,900✔
136

137
        entityClass = _namer.UniqueClassName(entityClass);
1,900✔
138
        var pluralName = entityClass.Pluralize(false);
1,900✔
139

140
        var entityNamespace = _options.Data.Entity.Namespace ?? "Data.Entities";
1,900!
141
        var entiyBaseClass = _options.Data.Entity.BaseClass;
1,900✔
142

143
        var mappingName = entityClass + "Map";
1,900✔
144
        mappingName = _namer.UniqueClassName(mappingName);
1,900✔
145

146
        var mappingNamespace = _options.Data.Mapping.Namespace ?? "Data.Mapping";
1,900!
147

148
        var contextName = ContextName(entityClass);
1,900✔
149
        contextName = ToPropertyName(entityContext.ContextClass, contextName);
1,900✔
150
        contextName = _namer.UniqueContextName(contextName);
1,900✔
151

152
        entity.EntityClass = entityClass;
1,900✔
153
        entity.EntityPlural = pluralName;
1,900✔
154
        entity.EntityNamespace = entityNamespace;
1,900✔
155
        entity.EntityBaseClass = entiyBaseClass;
1,900✔
156

157
        entity.MappingClass = mappingName;
1,900✔
158
        entity.MappingNamespace = mappingNamespace;
1,900✔
159

160
        entity.ContextProperty = contextName;
1,900✔
161

162
        entity.IsView = relationSchema is View;
1,900✔
163

164
        if (relationSchema is not Table tableSchema)
1,900✔
165
            return entity;
102✔
166

167
        var isTemporal = tableSchema.Options.IsTemporalTable
1,798✔
168
            && tableSchema.Options.HistoryTable != null
1,798✔
169
            && tableSchema.Options.PeriodStartColumnName.HasValue()
1,798✔
170
            && tableSchema.Options.PeriodEndColumnName.HasValue();
1,798✔
171

172
        if (isTemporal && _options.Data.Mapping.Temporal)
1,798✔
173
        {
174
            entity.TemporalTableName = tableSchema.Options.HistoryTable?.Name;
23!
175
            entity.TemporalTableSchema = tableSchema.Options.HistoryTable?.Schema;
23!
176

177
            var periodStartColumnName = tableSchema.Options.PeriodStartColumnName;
23✔
178
            var periodEndColumnName = tableSchema.Options.PeriodEndColumnName;
23✔
179

180
            entity.TemporalStartColumn = periodStartColumnName;
23✔
181
            entity.TemporalEndColumn = periodEndColumnName;
23✔
182

183
            var temporalStartProperty = ToPropertyName(entity.EntityClass, periodStartColumnName!);
23✔
184
            temporalStartProperty = _namer.UniqueName(entity.EntityClass, temporalStartProperty);
23✔
185

186
            entity.TemporalStartProperty = temporalStartProperty;
23✔
187

188
            var temporalEndProperty = ToPropertyName(entity.EntityClass, periodEndColumnName!);
23✔
189
            temporalEndProperty = _namer.UniqueName(entity.EntityClass, temporalEndProperty);
23✔
190

191
            entity.TemporalEndProperty = temporalEndProperty;
23✔
192
        }
193

194
        return entity;
1,798✔
195
    }
196

197

198
    private void CreateProperties(Entity entity, RelationBase relationSchema)
199
    {
200
        var columns = relationSchema.Columns;
1,900✔
201
        foreach (var column in columns)
32,668✔
202
        {
203
            var parentRelation = column.Parent;
14,434✔
204
            if (parentRelation is null || IsIgnored(column, _options.Database.Exclude.Columns))
14,434!
205
            {
206
                LogSkippingColumn(_logger, parentRelation?.Schema, parentRelation?.Name, column.Name);
×
207
                continue;
×
208
            }
209

210
            var property = entity.Properties.ByColumn(column.Name);
14,434✔
211

212
            if (property == null)
14,434✔
213
            {
214
                property = new Property
14,434✔
215
                {
14,434✔
216
                    Entity = entity,
14,434✔
217
                    ColumnName = column.Name
14,434✔
218
                };
14,434✔
219
                entity.Properties.Add(property);
14,434✔
220
            }
221

222
            string name = ToPropertyName(entity.EntityClass, column.Name);
14,434✔
223
            string propertyName = name;
14,434✔
224

225
            foreach (var selection in _options.Data.Entity.Renaming.Properties.Where(p => p.Expression.HasValue()))
28,868!
226
            {
227
                if (selection.Expression.IsNullOrEmpty())
×
228
                    continue;
229

230
                propertyName = Regex.Replace(propertyName, selection.Expression, string.Empty);
×
231
            }
232

233
            // make sure regex doesn't remove everything
234
            if (propertyName.IsNullOrEmpty())
14,434!
235
                propertyName = name;
×
236

237
            propertyName = _namer.UniqueName(entity.EntityClass, propertyName);
14,434✔
238

239
            property.PropertyName = propertyName;
14,434✔
240
            property.NativeType = column.NativeTypeName;
14,434✔
241
            property.DataType = column.DbType;
14,434✔
242
            property.SystemType = column.SystemType;
14,434✔
243
            property.SystemTypeName = GetSystemTypeName(column);
14,434✔
244
            property.Size = column.MaxLength;
14,434✔
245

246
            property.Default = column.DefaultValueSql;
14,434✔
247
            property.DefaultValue = TryParseDefault(column.DefaultValueSql, column.SystemType);
14,434✔
248

249
            property.IsComputed = column.IsComputed;
14,434✔
250
            property.IsIdentity = column.IsIdentity;
14,434✔
251
            property.IsNullable = column.IsNullable;
14,434✔
252
            property.IsRowVersion = column.IsRowVersion;
14,434✔
253
            property.IsConcurrencyToken = column.IsConcurrencyToken;
14,434✔
254

255
            var parentTable = parentRelation as Table;
14,434✔
256
            if (parentTable != null)
14,434✔
257
            {
258
                property.IsPrimaryKey = parentTable.PrimaryKey?.Columns
14,129✔
259
                    .Any(c => c.ColumnName == column.Name) == true;
14,129✔
260

261
                property.IsForeignKey = parentTable.ForeignKeys
14,129✔
262
                    .Any(c => c.ColumnMappings.Any(col => col.DependentColumnName == column.Name));
14,129✔
263

264
                property.IsUnique = parentTable.UniqueConstraints.Any(c => c.Columns.Any(col => col.ColumnName == column.Name))
14,129✔
265
                                    || parentTable.Indexes.Where(i => i.IsUnique).Any(c => c.Columns.Any(col => col.ColumnName == column.Name));
14,129✔
266
            }
267

268
            // overwrite row version type
269
            if (property.IsRowVersion == true
14,434✔
270
                && _options.Data.Mapping.RowVersion != RowVersionMapping.ByteArray
14,434✔
271
                && property.SystemType == typeof(byte[]))
14,434✔
272
            {
273
                property.SystemType = _options.Data.Mapping.RowVersion switch
1!
274
                {
1✔
275
                    RowVersionMapping.Long => typeof(long),
1✔
276
                    RowVersionMapping.ULong => typeof(ulong),
×
277
                    _ => typeof(byte[])
×
278
                };
1✔
279
                property.SystemTypeName = property.SystemType.ToType();
1✔
280
            }
281

282
            property.IsProcessed = true;
14,434✔
283
        }
284

285
        entity.Properties.IsProcessed = true;
1,900✔
286

287
        var table = relationSchema as Table;
1,900✔
288
        if (table is null)
1,900✔
289
            return;
102✔
290

291
        bool? isTemporal =   table.Options.IsTemporalTable;
1,798✔
292
        if (isTemporal != true || _options.Data.Mapping.Temporal)
1,798✔
293
            return;
1,797✔
294

295
        // add temporal period columns
296
        var temporalStartColumn = table.Options.PeriodStartColumnName;
1✔
297
        var temporalEndColumn = table.Options.PeriodEndColumnName;
1✔
298

299
        if (temporalStartColumn.IsNullOrEmpty() || temporalEndColumn.IsNullOrEmpty())
1!
300
            return;
×
301

302
        var temporalStart = entity.Properties.ByColumn(temporalStartColumn);
1✔
303

304
        if (temporalStart == null)
1!
305
        {
306
            temporalStart = new Property { Entity = entity, ColumnName = temporalStartColumn };
×
307
            entity.Properties.Add(temporalStart);
×
308
        }
309

310
        temporalStart.PropertyName = ToPropertyName(entity.EntityClass, temporalStartColumn);
1✔
311
        temporalStart.IsComputed = true;
1✔
312
        temporalStart.NativeType = "datetime2";
1✔
313
        temporalStart.DataType = DbType.DateTime2;
1✔
314
        temporalStart.SystemType = typeof(DateTime);
1✔
315
        temporalStart.SystemTypeName = typeof(DateTime).ToType();
1✔
316

317
        temporalStart.IsProcessed = true;
1✔
318

319
        var temporalEnd = entity.Properties.ByColumn(temporalEndColumn);
1✔
320

321
        if (temporalEnd == null)
1!
322
        {
323
            temporalEnd = new Property { Entity = entity, ColumnName = temporalEndColumn };
×
324
            entity.Properties.Add(temporalEnd);
×
325
        }
326

327
        temporalEnd.PropertyName = ToPropertyName(entity.EntityClass, temporalEndColumn);
1✔
328
        temporalEnd.IsComputed = true;
1✔
329
        temporalEnd.NativeType = "datetime2";
1✔
330
        temporalEnd.DataType = DbType.DateTime2;
1✔
331
        temporalEnd.SystemType = typeof(DateTime);
1✔
332
        temporalEnd.SystemTypeName = typeof(DateTime).ToType();
1✔
333

334
        temporalEnd.IsProcessed = true;
1✔
335
    }
1✔
336

337

338
    private void CreateRelationships(EntityContext entityContext, Entity entity, Table tableSchema)
339
    {
340
        foreach (var foreignKey in tableSchema.ForeignKeys.OrderBy(fk => fk.Name))
5,574✔
341
        {
342
            // skip relationship if principal table is ignored
343
            if (IsIgnored(foreignKey.PrincipalTable, _options.Database.Exclude.Tables))
989!
344
            {
345
                LogSkippingRelationship(_logger, foreignKey.Name);
×
346
                continue;
×
347
            }
348

349
            if (IsIgnored(foreignKey, _options.Database.Exclude.Relationships))
989!
350
            {
351
                LogSkippingRelationship(_logger, foreignKey.Name);
×
352
                continue;
×
353
            }
354

355
            CreateRelationship(entityContext, entity, foreignKey);
989✔
356
        }
357

358
        entity.Relationships.IsProcessed = true;
1,798✔
359
    }
1,798✔
360

361
    private void CreateRelationship(EntityContext entityContext, Entity foreignEntity, ForeignKey tableKeySchema)
362
    {
363
        Entity primaryEntity = GetEntity(entityContext, tableKeySchema.PrincipalTable, false, false);
989✔
364

365
        var primaryName = primaryEntity.EntityClass;
989✔
366
        var foreignName = foreignEntity.EntityClass;
989✔
367

368
        var foreignMembers = GetKeyMembers(
989✔
369
            foreignEntity,
989✔
370
            tableKeySchema.ColumnMappings.Select(c => c.DependentColumn?.Name ?? c.DependentColumnName),
989✔
371
            tableKeySchema.Name
989✔
372
        );
989✔
373
        bool foreignMembersRequired = foreignMembers.Any(c => c.IsRequired);
989✔
374

375
        var primaryMembers = GetKeyMembers(
989✔
376
            primaryEntity,
989✔
377
            tableKeySchema.ColumnMappings.Select(c => c.PrincipalColumn?.Name ?? c.PrincipalColumnName),
989✔
378
            tableKeySchema.Name
989✔
379
        );
989✔
380
        bool primaryMembersRequired = primaryMembers.Any(c => c.IsRequired);
989✔
381

382
        // skip invalid fkeys
383
        if (foreignMembers.Count == 0 || primaryMembers.Count == 0)
989!
384
            return;
×
385

386
        var relationshipName = tableKeySchema.Name;
989✔
387

388
        // ensure relationship name for sync support
389
        if (relationshipName.IsNullOrEmpty())
989!
390
            relationshipName = $"FK_{foreignName}_{primaryName}_{primaryMembers.Select(p => p.PropertyName).ToDelimitedString("_")}";
×
391

392
        relationshipName = _namer.UniqueRelationshipName(relationshipName);
989✔
393

394
        var foreignRelationship = foreignEntity.Relationships
989✔
395
            .FirstOrDefault(r => r.RelationshipName == relationshipName && r.IsForeignKey);
989✔
396

397
        if (foreignRelationship == null)
989!
398
        {
399
            foreignRelationship = new Relationship { RelationshipName = relationshipName };
989✔
400
            foreignEntity.Relationships.Add(foreignRelationship);
989✔
401
        }
402
        foreignRelationship.IsMapped = true;
989✔
403
        foreignRelationship.IsForeignKey = true;
989✔
404
        foreignRelationship.Cardinality = foreignMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
989✔
405

406
        foreignRelationship.PrimaryEntity = primaryEntity;
989✔
407
        foreignRelationship.PrimaryProperties = [.. primaryMembers];
989✔
408

409
        foreignRelationship.Entity = foreignEntity;
989✔
410
        foreignRelationship.Properties = [.. foreignMembers];
989✔
411

412
        string prefix = GetMemberPrefix(foreignRelationship, primaryName, foreignName);
989✔
413

414
        string foreignPropertyName = ToPropertyName(foreignEntity.EntityClass, prefix + primaryName);
989✔
415
        foreignPropertyName = _namer.UniqueName(foreignEntity.EntityClass, foreignPropertyName);
989✔
416
        foreignRelationship.PropertyName = foreignPropertyName;
989✔
417

418
        // add reverse
419
        var primaryRelationship = primaryEntity.Relationships
989✔
420
            .FirstOrDefault(r => r.RelationshipName == relationshipName && !r.IsForeignKey);
989✔
421

422
        if (primaryRelationship == null)
989!
423
        {
424
            primaryRelationship = new Relationship { RelationshipName = relationshipName };
989✔
425
            primaryEntity.Relationships.Add(primaryRelationship);
989✔
426
        }
427

428
        primaryRelationship.IsMapped = false;
989✔
429
        primaryRelationship.IsForeignKey = false;
989✔
430

431
        primaryRelationship.PrimaryEntity = foreignEntity;
989✔
432
        primaryRelationship.PrimaryProperties = [.. foreignMembers];
989✔
433

434
        primaryRelationship.Entity = primaryEntity;
989✔
435
        primaryRelationship.Properties = [.. primaryMembers];
989✔
436

437
        bool isOneToOne = IsOneToOne(tableKeySchema, foreignRelationship);
989✔
438
        if (isOneToOne)
989✔
439
            primaryRelationship.Cardinality = primaryMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
54!
440
        else
441
            primaryRelationship.Cardinality = Cardinality.Many;
935✔
442

443
        string primaryPropertyName = prefix + foreignName;
989✔
444
        if (!isOneToOne)
989✔
445
            primaryPropertyName = RelationshipName(primaryPropertyName);
935✔
446

447
        primaryPropertyName = ToPropertyName(primaryEntity.EntityClass, primaryPropertyName);
989✔
448
        primaryPropertyName = _namer.UniqueName(primaryEntity.EntityClass, primaryPropertyName);
989✔
449

450
        primaryRelationship.PropertyName = primaryPropertyName;
989✔
451

452
        foreignRelationship.PrimaryPropertyName = primaryRelationship.PropertyName;
989✔
453
        foreignRelationship.PrimaryCardinality = primaryRelationship.Cardinality;
989✔
454

455
        primaryRelationship.PrimaryPropertyName = foreignRelationship.PropertyName;
989✔
456
        primaryRelationship.PrimaryCardinality = foreignRelationship.Cardinality;
989✔
457

458
        foreignRelationship.IsProcessed = true;
989✔
459
        primaryRelationship.IsProcessed = true;
989✔
460
    }
989✔
461

462

463
    private static void CreateMethods(Entity entity, Table tableSchema)
464
    {
465
        if (tableSchema.PrimaryKey != null)
1,798✔
466
        {
467
            var method = GetMethodFromColumns(entity, tableSchema.PrimaryKey.Columns.Select(c => c.Column));
1,680✔
468
            if (method != null)
1,680!
469
            {
470
                method.IsKey = true;
1,680✔
471
                method.SourceName = tableSchema.PrimaryKey.Name;
1,680✔
472

473
                if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
1,680!
474
                    entity.Methods.Add(method);
1,680✔
475
            }
476
        }
477

478
        GetIndexMethods(entity, tableSchema);
1,798✔
479
        GetForeignKeyMethods(entity, tableSchema);
1,798✔
480

481
        entity.Methods.IsProcessed = true;
1,798✔
482
    }
1,798✔
483

484
    private static void GetForeignKeyMethods(Entity entity, Table table)
485
    {
486
        var columnNames = new List<string?>();
1,798✔
487

488
        foreach (var columnName in table.ForeignKeys.SelectMany(c => c.ColumnMappings.Select(m => m.DependentColumn?.Name ?? m.DependentColumnName)))
5,676✔
489
        {
490
            columnNames.Add(columnName);
1,040✔
491

492
            var method = GetMethodFromColumnNames(entity, columnNames);
1,040✔
493
            if (method != null && entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
1,040✔
494
                entity.Methods.Add(method);
761✔
495

496
            columnNames.Clear();
1,040✔
497
        }
498
    }
1,798✔
499

500
    private static void GetIndexMethods(Entity entity, Table table)
501
    {
502
        foreach (var index in table.Indexes)
4,794✔
503
        {
504
            var method = GetMethodFromColumns(entity, index.Columns.Select(c => c.Column));
599✔
505
            if (method == null)
599✔
506
                continue;
507

508
            method.SourceName = index.Name;
572✔
509
            method.IsUnique = index.IsUnique;
572✔
510
            method.IsIndex = true;
572✔
511

512
            if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
572✔
513
                entity.Methods.Add(method);
572✔
514
        }
515
    }
1,798✔
516

517
    private static Method? GetMethodFromColumns(Entity entity, IEnumerable<Column?> columns)
518
    {
519
        return GetMethodFromColumnNames(entity, columns.Select(column => column?.Name));
2,279✔
520
    }
521

522
    private static Method? GetMethodFromColumnNames(Entity entity, IEnumerable<string?> columnNames)
523
    {
524
        var method = new Method { Entity = entity };
3,319✔
525
        var methodName = new StringBuilder();
3,319✔
526

527
        foreach (var columnName in columnNames)
13,956✔
528
        {
529
            if (string.IsNullOrEmpty(columnName))
3,659✔
530
                continue;
531

532
            var property = entity.Properties.ByColumn(columnName);
3,659✔
533
            if (property == null)
3,659✔
534
                continue;
535

536
            method.Properties.Add(property);
3,659✔
537
            methodName.Append(property.PropertyName);
3,659✔
538
        }
539

540
        if (method.Properties.Count == 0)
3,319✔
541
            return null;
27✔
542

543
        method.NameSuffix = methodName.ToString();
3,292✔
544
        return method;
3,292✔
545
    }
546

547

548
    private void GetModels(Entity? entity)
549
    {
550
        if (entity == null || entity.Models.IsProcessed)
1,900!
551
            return;
×
552

553
        _options.Variables.Set(entity);
1,900✔
554

555
        if (_options.Model.Read.Generate)
1,900✔
556
            CreateModel(entity, _options.Model.Read, ModelType.Read);
142✔
557
        if (!entity.IsView && _options.Model.Create.Generate)
1,900✔
558
            CreateModel(entity, _options.Model.Create, ModelType.Create);
134✔
559
        if (!entity.IsView && _options.Model.Update.Generate)
1,900✔
560
            CreateModel(entity, _options.Model.Update, ModelType.Update);
134✔
561

562
        if (entity.Models.Count > 0)
1,900✔
563
        {
564
            var mapperNamespace = _options.Model.Mapper.Namespace ?? "Data.Mapper";
142!
565

566
            var mapperClass = ToLegalName(_options.Model.Mapper.Name);
142✔
567
            mapperClass = _namer.UniqueModelName(mapperNamespace, mapperClass);
142✔
568

569
            entity.MapperClass = mapperClass;
142✔
570
            entity.MapperNamespace = mapperNamespace;
142✔
571
            entity.MapperBaseClass = _options.Model.Mapper.BaseClass;
142✔
572
        }
573

574
        _options.Variables.Remove(entity);
1,900✔
575

576
        entity.Models.IsProcessed = true;
1,900✔
577
    }
1,900✔
578

579
    private void CreateModel<TOption>(Entity entity, TOption options, ModelType modelType)
580
        where TOption : ModelOptionsBase
581
    {
582
        if (IsIgnored(entity, options, _options.Model.Shared))
410✔
583
            return;
8✔
584

585
        var modelNamespace = options.Namespace.HasValue()
402!
586
            ? options.Namespace
402✔
587
            : _options.Model.Shared.Namespace;
402✔
588

589
        var modelHeader = options.Header.HasValue()
402!
590
            ? options.Header
402✔
591
            : _options.Model.Shared.Header;
402✔
592

593
        modelNamespace ??= "Data.Models";
402!
594

595
        var modelClass = ToLegalName(options.Name);
402✔
596
        modelClass = _namer.UniqueModelName(modelNamespace, modelClass);
402✔
597

598
        var model = new Model
402✔
599
        {
402✔
600
            Entity = entity,
402✔
601
            ModelType = modelType,
402✔
602
            ModelBaseClass = options.BaseClass,
402✔
603
            ModelNamespace = modelNamespace,
402✔
604
            ModelClass = modelClass,
402✔
605
            ModelAttributes = options.Attributes,
402✔
606
            ModelHeader = modelHeader
402✔
607
        };
402✔
608

609
        foreach (var property in entity.Properties)
6,948✔
610
        {
611
            if (IsIgnored(property, options, _options.Model.Shared))
3,072✔
612
                continue;
613

614
            model.Properties.Add(property);
3,036✔
615
        }
616

617
        _options.Variables.Set(model);
402✔
618

619
        var validatorNamespace = _options.Model.Validator.Namespace ?? "Data.Validation";
402!
620
        var validatorClass = ToLegalName(_options.Model.Validator.Name);
402✔
621
        validatorClass = _namer.UniqueModelName(validatorNamespace, validatorClass);
402✔
622

623
        model.ValidatorBaseClass = _options.Model.Validator.BaseClass;
402✔
624
        model.ValidatorClass = validatorClass;
402✔
625
        model.ValidatorNamespace = validatorNamespace;
402✔
626

627
        entity.Models.Add(model);
402✔
628

629
        _options.Variables.Remove(model);
402✔
630
    }
402✔
631

632

633
    private List<Property> GetKeyMembers(Entity entity, IEnumerable<string?> memberNames, string? relationshipName)
634
    {
635
        var keyMembers = new List<Property>();
1,978✔
636

637
        foreach (var memberName in memberNames)
8,116✔
638
        {
639
            if (string.IsNullOrEmpty(memberName))
2,080!
640
            {
641
                LogCouldNotResolveColumnName(_logger, relationshipName);
×
642
                continue;
×
643
            }
644

645
            var property = entity.Properties.ByColumn(memberName);
2,080✔
646

647
            if (property == null)
2,080!
648
                LogCouldNotFindColumn(_logger, memberName, relationshipName);
×
649
            else
650
                keyMembers.Add(property);
2,080✔
651
        }
652

653
        return keyMembers;
1,978✔
654
    }
655

656
    private string GetSystemTypeName(Column column)
657
    {
658
        var annotationName = _options.Data.Entity.SystemTypeAnnotation;
14,434✔
659
        if (annotationName.HasValue()
14,434✔
660
            && column.Annotations.TryGetValue(annotationName, out var annotationValue))
14,434✔
661
        {
662
            var annotationSystemType = Convert.ToString(annotationValue, CultureInfo.InvariantCulture);
567✔
663
            if (annotationSystemType.HasValue())
567✔
664
                return annotationSystemType;
567✔
665
        }
666

667
        return GetSystemTypeName(column.NativeTypeName, column.SystemType);
13,867✔
668
    }
669

670
    private string GetSystemTypeName(string? nativeType, Type systemType)
671
    {
672
        var mapping = _options.Data.Entity.TypeMapping
13,867✔
673
            .FirstOrDefault(m => string.Equals(m.NativeType, nativeType, StringComparison.OrdinalIgnoreCase));
13,867✔
674

675
        if (mapping?.SystemType.HasValue() == true)
13,867✔
676
            return mapping.SystemType;
70✔
677

678
        return systemType.ToType();
13,797✔
679
    }
680

681
    private static string GetMemberPrefix(Relationship relationship, string primaryClass, string foreignClass)
682
    {
683
        string thisKey = relationship.Properties
989!
684
            .Select(p => p.PropertyName)
989✔
685
            .FirstOrDefault() ?? string.Empty;
989✔
686

687
        string otherKey = relationship.PrimaryProperties
989!
688
            .Select(p => p.PropertyName)
989✔
689
            .FirstOrDefault() ?? string.Empty;
989✔
690

691
        bool isSameName = thisKey.Equals(otherKey, StringComparison.OrdinalIgnoreCase);
989✔
692
        isSameName = (isSameName || thisKey.Equals(primaryClass + otherKey, StringComparison.OrdinalIgnoreCase));
989✔
693

694
        string prefix = string.Empty;
989✔
695
        if (isSameName)
989✔
696
            return prefix;
476✔
697

698
        prefix = thisKey.Replace(otherKey, "");
513✔
699
        prefix = prefix.Replace(primaryClass, "");
513✔
700
        prefix = prefix.Replace(foreignClass, "");
513✔
701
        prefix = IdSuffixRegex().Replace(prefix, "");
513✔
702
        prefix = DigitPrefixRegex().Replace(prefix, "");
513✔
703

704
        return prefix;
513✔
705
    }
706

707
    private static bool IsOneToOne(ForeignKey tableKeySchema, Relationship foreignRelationship)
708
    {
709
        var foreignColumn = foreignRelationship.Properties
989✔
710
            .Select(p => p.ColumnName)
989✔
711
            .FirstOrDefault();
989✔
712

713
        return tableKeySchema.PrincipalTable.PrimaryKey != null
989✔
714
            && tableKeySchema.DependentTable.PrimaryKey != null
989✔
715
            && tableKeySchema.DependentTable.PrimaryKey.Columns.Count == 1
989✔
716
            && tableKeySchema.DependentTable.PrimaryKey.Columns.Any(c => c.ColumnName == foreignColumn);
989✔
717

718
        // if f.key is unique
719
        //return tableKeySchema.ForeignKeyMemberColumns.All(column => column.IsUnique);
720
    }
721

722

723
    private string RelationshipName(string name)
724
    {
725
        var naming = _options.Data.Entity.RelationshipNaming;
935✔
726
        if (naming == RelationshipNaming.Preserve)
935!
727
            return name;
×
728

729
        if (naming == RelationshipNaming.Suffix)
935!
730
            return name + "List";
×
731

732
        return name.Pluralize(false);
935✔
733
    }
734

735
    private string ContextName(string name)
736
    {
737
        var naming = _options.Data.Context.PropertyNaming;
1,900✔
738
        if (naming == ContextNaming.Preserve)
1,900!
739
            return name;
×
740

741
        if (naming == ContextNaming.Suffix)
1,900!
742
            return name + "DataSet";
×
743

744
        return name.Pluralize(false);
1,900✔
745
    }
746

747
    private string EntityName(string name)
748
    {
749
        var tableNaming = _options.Database.TableNaming;
1,900✔
750
        var entityNaming = _options.Data.Entity.EntityNaming;
1,900✔
751

752
        if (tableNaming != TableNaming.Plural && entityNaming == EntityNaming.Plural)
1,900!
753
            name = name.Pluralize(false);
×
754
        else if (tableNaming != TableNaming.Singular && entityNaming == EntityNaming.Singular)
1,900!
755
            name = name.Singularize(false);
×
756

757
        var rename = name;
1,900✔
758
        foreach (var selection in _options.Data.Entity.Renaming.Entities.Where(p => p.Expression.HasValue()))
3,800!
759
        {
760
            if (selection.Expression.IsNullOrEmpty())
×
761
                continue;
762

763
            rename = Regex.Replace(rename, selection.Expression, string.Empty);
×
764
        }
765

766
        // make sure regex doesn't remove everything
767
        return rename.HasValue() ? rename : name;
1,900!
768
    }
769

770
    private string ToClassName(RelationBase tableSchema)
771
    {
772
        return ToClassName(
1,900✔
773
            tableSchema.QualifiedName.Name,
1,900✔
774
            tableSchema.QualifiedName.Schema
1,900✔
775
        );
1,900✔
776
    }
777

778
    private string ToClassName(string tableName, string? tableSchema)
779
    {
780
        tableName = EntityName(tableName);
1,900✔
781
        var className = tableName;
1,900✔
782

783
        if (_options.Data.Entity.PrefixWithSchemaName && tableSchema != null)
1,900!
784
            className = $"{tableSchema}{tableName}";
2✔
785

786
        return ToLegalName(className);
1,900✔
787
    }
788

789
    private string ToPropertyName(string className, string name)
790
    {
791
        string propertyName = ToLegalName(name);
18,360✔
792
        if (className.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
18,360✔
793
            propertyName += "Member";
130✔
794

795
        return propertyName;
18,360✔
796
    }
797

798
    private static string ToLegalName(string? name)
799
    {
800
        if (string.IsNullOrWhiteSpace(name))
25,074✔
801
            return string.Empty;
34✔
802

803
        string legalName = name;
25,040✔
804

805
        // remove invalid leading
806
        var expression = LeadingNonAlphaRegex();
25,040✔
807
        if (expression.IsMatch(name))
25,040✔
808
            legalName = expression.Replace(legalName, string.Empty);
157✔
809

810
        // prefix with column when all characters removed
811
        if (legalName.IsNullOrWhiteSpace())
25,040✔
812
            legalName = "Number" + name;
1✔
813

814
        return legalName.ToPascalCase();
25,040✔
815
    }
816

817

818
    private static object? TryParseDefault(string? defaultValueSql, Type type)
819
    {
820
        defaultValueSql = defaultValueSql?.Trim();
14,434✔
821
        if (string.IsNullOrEmpty(defaultValueSql))
14,434✔
822
            return null;
12,995✔
823

824
        // unwrap parentheses
825
        Unwrap();
1,439✔
826

827
        if (defaultValueSql.StartsWith("CONVERT", StringComparison.OrdinalIgnoreCase))
1,439!
828
        {
829
            // extract value from CONVERT statement
830
            defaultValueSql = defaultValueSql[(defaultValueSql.IndexOf(',') + 1)..];
×
831
            defaultValueSql = defaultValueSql[..defaultValueSql.LastIndexOf(')')];
×
832

833
            // unwrap parentheses again
834
            Unwrap();
×
835
        }
836

837
        // handle NULL default
838
        if (defaultValueSql.Equals("NULL", StringComparison.OrdinalIgnoreCase))
1,439!
839
            return null;
×
840

841
        // handle boolean defaults represented as 0 or 1
842
        if (type == typeof(bool) && int.TryParse(defaultValueSql, out var intValue))
1,439✔
843
            return intValue != 0;
187✔
844

845
        // handle numeric types
846
        if (type.IsNumeric())
1,252✔
847
        {
848
            try
849
            {
850
                return Convert.ChangeType(defaultValueSql, type, CultureInfo.InvariantCulture);
221✔
851
            }
852
            catch
×
853
            {
854
                // Ignored
855
                return null;
×
856
            }
857
        }
858

859
        // handle string literals
860
        if ((defaultValueSql.StartsWith('\'') || defaultValueSql.StartsWith("N'", StringComparison.OrdinalIgnoreCase))
1,031!
861
            && defaultValueSql.EndsWith('\''))
1,031✔
862
        {
863
            // extract string value from quotes
864
            var startIndex = defaultValueSql.IndexOf('\'');
×
865
            defaultValueSql = defaultValueSql.Substring(startIndex + 1, defaultValueSql.Length - (startIndex + 2));
×
866

867
            if (type == typeof(string))
×
868
                return defaultValueSql;
×
869

870
            if (type == typeof(bool) && bool.TryParse(defaultValueSql, out var boolValue))
×
871
                return boolValue;
×
872

873
            if (type == typeof(Guid) && Guid.TryParse(defaultValueSql, out var guid))
×
874
                return guid;
×
875

876
            if (type == typeof(DateTime) && DateTime.TryParse(defaultValueSql, out var dateTime))
×
877
                return dateTime;
×
878

879
            if (type == typeof(DateOnly) && DateOnly.TryParse(defaultValueSql, out var dateOnly))
×
880
                return dateOnly;
×
881

882
            if (type == typeof(TimeOnly) && TimeOnly.TryParse(defaultValueSql, out var timeOnly))
×
883
                return timeOnly;
×
884

885
            if (type == typeof(DateTimeOffset) && DateTimeOffset.TryParse(defaultValueSql, out var dateTimeOffset))
×
886
                return dateTimeOffset;
×
887
        }
888

889
        return null;
1,031✔
890

891
        // local function to unwrap parentheses
892
        void Unwrap()
893
        {
894
            while (defaultValueSql.StartsWith('(') && defaultValueSql.EndsWith(')'))
895
                defaultValueSql = defaultValueSql[1..^1].Trim();
896
        }
897
    }
221✔
898

899

900
    private static bool IsIgnored(RelationBase? relation, IEnumerable<MatchOptions> exclude)
901
    {
902
        if (relation is null)
2,891!
903
            return true;
×
904

905
        var name = relation.QualifiedName;
2,891✔
906
        var includeExpressions = Enumerable.Empty<MatchOptions>();
2,891✔
907
        var excludeExpressions = exclude ?? [];
2,891!
908

909
        return IsIgnored(name, excludeExpressions, includeExpressions);
2,891✔
910
    }
911

912
    private static bool IsIgnored(Column column, IEnumerable<MatchOptions> exclude)
913
    {
914
        var table = column.Parent;
14,434✔
915
        if (table == null)
14,434!
916
            return true;
×
917

918
        var name = $"{table.Schema}.{table.Name}.{column.Name}";
14,434✔
919
        var includeExpressions = Enumerable.Empty<MatchOptions>();
14,434✔
920
        var excludeExpressions = exclude ?? [];
14,434!
921

922
        return IsIgnored(name, excludeExpressions, includeExpressions);
14,434✔
923
    }
924

925
    private static bool IsIgnored(ForeignKey relationship, IEnumerable<MatchOptions> exclude)
926
    {
927
        var table = relationship.PrincipalTable;
989✔
928
        if (table == null)
989!
929
            return true;
×
930

931
        var name = $"{table.Schema}.{table.Name}.{relationship.Name}";
989✔
932
        var includeExpressions = Enumerable.Empty<MatchOptions>();
989✔
933
        var excludeExpressions = exclude ?? [];
989!
934

935
        return IsIgnored(name, excludeExpressions, includeExpressions);
989✔
936
    }
937

938
    private static bool IsIgnored<TOption>(Property property, TOption options, SharedModelOptions sharedOptions)
939
        where TOption : ModelOptionsBase
940
    {
941
        var name = $"{property.Entity.EntityClass}.{property.PropertyName}";
3,072✔
942

943
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Properties ?? []);
3,072!
944
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Properties ?? []);
3,072!
945

946
        var includeProperties = options?.Include?.Properties ?? [];
3,072!
947
        foreach (var expression in includeProperties)
6,144!
948
            includeExpressions.Add(expression);
×
949

950
        var excludeProperties = options?.Exclude?.Properties ?? [];
3,072!
951
        foreach (var expression in excludeProperties)
6,144!
952
            excludeExpressions.Add(expression);
×
953

954
        return IsIgnored(name, excludeExpressions, includeExpressions);
3,072✔
955
    }
956

957
    private static bool IsIgnored<TOption>(Entity entity, TOption options, SharedModelOptions sharedOptions)
958
        where TOption : ModelOptionsBase
959
    {
960
        var name = entity.EntityClass;
410✔
961

962
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Entities ?? []);
410!
963
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Entities ?? []);
410!
964

965
        var includeEntities = options?.Include?.Entities ?? [];
410!
966
        foreach (var expression in includeEntities)
820!
967
            includeExpressions.Add(expression);
×
968

969
        var excludeEntities = options?.Exclude?.Entities ?? [];
410!
970
        foreach (var expression in excludeEntities)
1,348✔
971
            excludeExpressions.Add(expression);
264✔
972

973
        return IsIgnored(name, excludeExpressions, includeExpressions);
410✔
974
    }
975

976
    private static bool IsIgnored(string name, IEnumerable<MatchOptions> excludeExpressions, IEnumerable<MatchOptions> includeExpressions)
977
    {
978
        foreach (var expression in includeExpressions)
43,592!
979
        {
980
            if (expression.IsMatch(name))
×
981
                return false;
×
982
        }
983

984
        foreach (var expression in excludeExpressions)
62,442✔
985
        {
986
            if (expression.IsMatch(name))
9,448✔
987
                return true;
46✔
988
        }
989

990
        return false;
21,750✔
991
    }
46✔
992

993

994
    [GeneratedRegex(@"^[^a-zA-Z_]+")]
995
    private static partial Regex LeadingNonAlphaRegex();
996

997
    [GeneratedRegex(@"(_ID|_id|_Id|\.ID|\.id|\.Id|ID|Id)$")]
998
    private static partial Regex IdSuffixRegex();
999

1000
    [GeneratedRegex(@"^\d")]
1001
    private static partial Regex DigitPrefixRegex();
1002

1003
    [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Building code generation model from database: {databaseName}")]
1004
    private static partial void LogBuildingCodeGenerationModel(ILogger logger, string? databaseName);
1005

1006
    [LoggerMessage(EventId = 2, Level = LogLevel.Debug, Message = "  Skipping {relationType} : {schema}.{name}")]
1007
    private static partial void LogSkippingRelation(ILogger logger, string relationType, string? schema, string name);
1008

1009
    [LoggerMessage(EventId = 3, Level = LogLevel.Debug, Message = "  Processing {relationType} : {schema}.{name}")]
1010
    private static partial void LogProcessingRelation(ILogger logger, string relationType, string? schema, string name);
1011

1012
    [LoggerMessage(EventId = 4, Level = LogLevel.Debug, Message = "  Skipping Column : {schema}.{table}.{column}")]
1013
    private static partial void LogSkippingColumn(ILogger logger, string? schema, string? table, string column);
1014

1015
    [LoggerMessage(EventId = 5, Level = LogLevel.Debug, Message = "  Skipping Relationship : {name}")]
1016
    private static partial void LogSkippingRelationship(ILogger logger, string? name);
1017

1018
    [LoggerMessage(EventId = 6, Level = LogLevel.Warning, Message = "Could not resolve column name for relationship {relationshipName}.")]
1019
    private static partial void LogCouldNotResolveColumnName(ILogger logger, string? relationshipName);
1020

1021
    [LoggerMessage(EventId = 7, Level = LogLevel.Warning, Message = "Could not find column {columnName} for relationship {relationshipName}.")]
1022
    private static partial void LogCouldNotFindColumn(ILogger logger, string columnName, string? relationshipName);
1023
}
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