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

loresoft / EntityFrameworkCore.Generator / 27799729863

19 Jun 2026 01:26AM UTC coverage: 71.762% (+1.3%) from 70.481%
27799729863

push

github

pwelter34
Adjust README and fix SQL type mapping

985 of 1863 branches covered (52.87%)

Branch coverage included in aggregate %.

4217 of 5386 relevant lines covered (78.3%)

1278.11 hits per line

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

76.01
/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)
17✔
28
    {
29
        _logger = logger.CreateLogger<ModelGenerator>();
17✔
30
        _namer = new UniqueNamer();
17✔
31
    }
17✔
32

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

38
        _logger.LogInformation("Building code generation model from database: {databaseName}", databaseModel.DatabaseName);
17✔
39

40
        _options = options;
17✔
41

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

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

48

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

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

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

59
        foreach (var table in databaseModel.Tables)
368✔
60
        {
61
            if (IsIgnored(table, _options.Database.Exclude.Tables))
167✔
62
            {
63
                _logger.LogDebug("  Skipping Table : {schema}.{name}", table.Schema, table.Name);
2✔
64
                continue;
2✔
65
            }
66

67
            _logger.LogDebug("  Processing Table : {schema}.{name}", table.Schema, table.Name);
165✔
68

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

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

76
        foreach (var view in databaseModel.Views)
46✔
77
        {
78
            if (IsIgnored(view, _options.Database.Exclude.Tables))
6!
79
            {
80
                _logger.LogDebug("  Skipping View : {schema}.{name}", view.Schema, view.Name);
×
81
                continue;
×
82
            }
83

84
            _logger.LogDebug("  Processing View : {schema}.{name}", view.Schema, view.Name);
6✔
85

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

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

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

96
        return entityContext;
17✔
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)
248✔
103
            ?? CreateEntity(entityContext, relationSchema);
248✔
104

105
        if (!entity.Properties.IsProcessed)
248✔
106
            CreateProperties(entity, relationSchema);
171✔
107

108
        if (relationSchema is not Table tableSchema)
248✔
109
            return entity;
6✔
110

111
        if (processRelationships && !entity.Relationships.IsProcessed)
242!
112
            CreateRelationships(entityContext, entity, tableSchema);
165✔
113

114
        if (processMethods && !entity.Methods.IsProcessed)
242!
115
            CreateMethods(entity, tableSchema);
165✔
116

117
        entity.IsProcessed = true;
242✔
118
        return entity;
242✔
119
    }
120

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

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

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

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

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

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

146
        var mappingNamespace = _options.Data.Mapping.Namespace ?? "Data.Mapping";
171!
147

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

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

157
        entity.MappingClass = mappingName;
171✔
158
        entity.MappingNamespace = mappingNamespace;
171✔
159

160
        entity.ContextProperty = contextName;
171✔
161

162
        entity.IsView = relationSchema is View;
171✔
163

164
        if (relationSchema is not Table tableSchema)
171✔
165
            return entity;
6✔
166

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

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

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

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

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

186
            entity.TemporalStartProperty = temporalStartProperty;
1✔
187

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

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

194
        return entity;
165✔
195
    }
196

197

198
    private void CreateProperties(Entity entity, RelationBase relationSchema)
199
    {
200
        var columns = relationSchema.Columns;
171✔
201
        foreach (var column in columns)
2,948✔
202
        {
203
            var parentRelation = column.Parent;
1,303✔
204
            if (parentRelation is null || IsIgnored(column, _options.Database.Exclude.Columns))
1,303!
205
            {
206
                _logger.LogDebug("  Skipping Column : {Schema}.{Table}.{Column}", parentRelation?.Schema, parentRelation?.Name, column.Name);
×
207
                continue;
×
208
            }
209

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

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

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

225
            foreach (var selection in _options.Data.Entity.Renaming.Properties.Where(p => p.Expression.HasValue()))
2,606!
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())
1,303!
235
                propertyName = name;
×
236

237
            propertyName = _namer.UniqueName(entity.EntityClass, propertyName);
1,303✔
238

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

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

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

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

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

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

268
            // overwrite row version type
269
            if (property.IsRowVersion == true
1,303✔
270
                && _options.Data.Mapping.RowVersion != RowVersionMapping.ByteArray
1,303✔
271
                && property.SystemType == typeof(byte[]))
1,303✔
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;
1,303✔
283
        }
284

285
        entity.Properties.IsProcessed = true;
171✔
286

287
        var table = relationSchema as Table;
171✔
288
        if (table is null)
171✔
289
            return;
6✔
290

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

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

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

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

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

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

317
        temporalStart.IsProcessed = true;
×
318

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

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

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

334
        temporalEnd.IsProcessed = true;
×
335
    }
×
336

337

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

349
            if (IsIgnored(foreignKey, _options.Database.Exclude.Relationships))
77!
350
            {
351
                _logger.LogDebug("  Skipping Relationship : {name}", foreignKey.Name);
×
352
                continue;
×
353
            }
354

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

358
        entity.Relationships.IsProcessed = true;
165✔
359
    }
165✔
360

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

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

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

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

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

386
        var relationshipName = tableKeySchema.Name;
77✔
387

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

450
        primaryRelationship.PropertyName = primaryPropertyName;
77✔
451

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

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

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

462

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

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

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

481
        entity.Methods.IsProcessed = true;
165✔
482
    }
165✔
483

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

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

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

496
            columnNames.Clear();
80✔
497
        }
498
    }
165✔
499

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

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

512
            if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
67✔
513
                entity.Methods.Add(method);
67✔
514
        }
515
    }
165✔
516

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

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

527
        foreach (var columnName in columnNames)
1,238✔
528
        {
529
            if (string.IsNullOrEmpty(columnName))
324✔
530
                continue;
531

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

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

540
        if (method.Properties.Count == 0)
295✔
541
            return null;
2✔
542

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

547

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

553
        _options.Variables.Set(entity);
171✔
554

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

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

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

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

574
        _options.Variables.Remove(entity);
171✔
575

576
        entity.Models.IsProcessed = true;
171✔
577
    }
171✔
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))
409✔
583
            return;
8✔
584

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

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

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

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

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

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

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

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

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

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

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

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

632

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

637
        foreach (var memberName in memberNames)
628✔
638
        {
639
            if (string.IsNullOrEmpty(memberName))
160!
640
            {
641
                _logger.LogWarning("Could not resolve column name for relationship {relationshipName}.", relationshipName);
×
642
                continue;
×
643
            }
644

645
            var property = entity.Properties.ByColumn(memberName);
160✔
646

647
            if (property == null)
160!
648
                _logger.LogWarning("Could not find column {columnName} for relationship {relationshipName}.", memberName, relationshipName);
×
649
            else
650
                keyMembers.Add(property);
160✔
651
        }
652

653
        return keyMembers;
154✔
654
    }
655

656
    private string GetSystemTypeName(string? nativeType, Type systemType)
657
    {
658
        var mapping = _options.Data.Entity.TypeMapping
1,303✔
659
            .FirstOrDefault(m => string.Equals(m.NativeType, nativeType, StringComparison.OrdinalIgnoreCase));
1,303✔
660

661
        if (mapping?.SystemType.HasValue() == true)
1,303✔
662
            return mapping.SystemType;
11✔
663

664
        return systemType.ToType();
1,292✔
665
    }
666

667
    private static string GetMemberPrefix(Relationship relationship, string primaryClass, string foreignClass)
668
    {
669
        string thisKey = relationship.Properties
77!
670
            .Select(p => p.PropertyName)
77✔
671
            .FirstOrDefault() ?? string.Empty;
77✔
672

673
        string otherKey = relationship.PrimaryProperties
77!
674
            .Select(p => p.PropertyName)
77✔
675
            .FirstOrDefault() ?? string.Empty;
77✔
676

677
        bool isSameName = thisKey.Equals(otherKey, StringComparison.OrdinalIgnoreCase);
77✔
678
        isSameName = (isSameName || thisKey.Equals(primaryClass + otherKey, StringComparison.OrdinalIgnoreCase));
77✔
679

680
        string prefix = string.Empty;
77✔
681
        if (isSameName)
77✔
682
            return prefix;
44✔
683

684
        prefix = thisKey.Replace(otherKey, "");
33✔
685
        prefix = prefix.Replace(primaryClass, "");
33✔
686
        prefix = prefix.Replace(foreignClass, "");
33✔
687
        prefix = IdSuffixRegex().Replace(prefix, "");
33✔
688
        prefix = DigitPrefixRegex().Replace(prefix, "");
33✔
689

690
        return prefix;
33✔
691
    }
692

693
    private static bool IsOneToOne(ForeignKey tableKeySchema, Relationship foreignRelationship)
694
    {
695
        var foreignColumn = foreignRelationship.Properties
77✔
696
            .Select(p => p.ColumnName)
77✔
697
            .FirstOrDefault();
77✔
698

699
        return tableKeySchema.PrincipalTable.PrimaryKey != null
77✔
700
            && tableKeySchema.DependentTable.PrimaryKey != null
77✔
701
            && tableKeySchema.DependentTable.PrimaryKey.Columns.Count == 1
77✔
702
            && tableKeySchema.DependentTable.PrimaryKey.Columns.Any(c => c.ColumnName == foreignColumn);
77✔
703

704
        // if f.key is unique
705
        //return tableKeySchema.ForeignKeyMemberColumns.All(column => column.IsUnique);
706
    }
707

708

709
    private string RelationshipName(string name)
710
    {
711
        var naming = _options.Data.Entity.RelationshipNaming;
71✔
712
        if (naming == RelationshipNaming.Preserve)
71!
713
            return name;
×
714

715
        if (naming == RelationshipNaming.Suffix)
71!
716
            return name + "List";
×
717

718
        return name.Pluralize(false);
71✔
719
    }
720

721
    private string ContextName(string name)
722
    {
723
        var naming = _options.Data.Context.PropertyNaming;
171✔
724
        if (naming == ContextNaming.Preserve)
171!
725
            return name;
×
726

727
        if (naming == ContextNaming.Suffix)
171!
728
            return name + "DataSet";
×
729

730
        return name.Pluralize(false);
171✔
731
    }
732

733
    private string EntityName(string name)
734
    {
735
        var tableNaming = _options.Database.TableNaming;
171✔
736
        var entityNaming = _options.Data.Entity.EntityNaming;
171✔
737

738
        if (tableNaming != TableNaming.Plural && entityNaming == EntityNaming.Plural)
171!
739
            name = name.Pluralize(false);
×
740
        else if (tableNaming != TableNaming.Singular && entityNaming == EntityNaming.Singular)
171!
741
            name = name.Singularize(false);
×
742

743
        var rename = name;
171✔
744
        foreach (var selection in _options.Data.Entity.Renaming.Entities.Where(p => p.Expression.HasValue()))
342!
745
        {
746
            if (selection.Expression.IsNullOrEmpty())
×
747
                continue;
748

749
            rename = Regex.Replace(rename, selection.Expression, string.Empty);
×
750
        }
751

752
        // make sure regex doesn't remove everything
753
        return rename.HasValue() ? rename : name;
171!
754
    }
755

756
    private string ToClassName(RelationBase tableSchema)
757
    {
758
        return ToClassName(
171✔
759
            tableSchema.QualifiedName.Name,
171✔
760
            tableSchema.QualifiedName.Schema
171✔
761
        );
171✔
762
    }
763

764
    private string ToClassName(string tableName, string? tableSchema)
765
    {
766
        tableName = EntityName(tableName);
171✔
767
        var className = tableName;
171✔
768

769
        if (_options.Data.Entity.PrefixWithSchemaName && tableSchema != null)
171!
770
            className = $"{tableSchema}{tableName}";
2✔
771

772
        return ToLegalName(className);
171✔
773
    }
774

775
    private string ToPropertyName(string className, string name)
776
    {
777
        string propertyName = ToLegalName(name);
1,630✔
778
        if (className.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
1,630✔
779
            propertyName += "Member";
11✔
780

781
        return propertyName;
1,630✔
782
    }
783

784
    private static string ToLegalName(string? name)
785
    {
786
        if (string.IsNullOrWhiteSpace(name))
3,103✔
787
            return string.Empty;
34✔
788

789
        string legalName = name;
3,069✔
790

791
        // remove invalid leading
792
        var expression = LeadingNonAlphaRegex();
3,069✔
793
        if (expression.IsMatch(name))
3,069✔
794
            legalName = expression.Replace(legalName, string.Empty);
13✔
795

796
        // prefix with column when all characters removed
797
        if (legalName.IsNullOrWhiteSpace())
3,069✔
798
            legalName = "Number" + name;
1✔
799

800
        return legalName.ToPascalCase();
3,069✔
801
    }
802

803

804
    private static object? TryParseDefault(string? defaultValueSql, Type type)
805
    {
806
        defaultValueSql = defaultValueSql?.Trim();
1,303✔
807
        if (string.IsNullOrEmpty(defaultValueSql))
1,303✔
808
            return null;
1,160✔
809

810
        // unwrap parentheses
811
        Unwrap();
143✔
812

813
        if (defaultValueSql.StartsWith("CONVERT", StringComparison.OrdinalIgnoreCase))
143!
814
        {
815
            // extract value from CONVERT statement
816
            defaultValueSql = defaultValueSql[(defaultValueSql.IndexOf(',') + 1)..];
×
817
            defaultValueSql = defaultValueSql[..defaultValueSql.LastIndexOf(')')];
×
818

819
            // unwrap parentheses again
820
            Unwrap();
×
821
        }
822

823
        // handle NULL default
824
        if (defaultValueSql.Equals("NULL", StringComparison.OrdinalIgnoreCase))
143!
825
            return null;
×
826

827
        // handle boolean defaults represented as 0 or 1
828
        if (type == typeof(bool) && int.TryParse(defaultValueSql, out var intValue))
143✔
829
            return intValue != 0;
26✔
830

831
        // handle numeric types
832
        if (type.IsNumeric())
117✔
833
        {
834
            try
835
            {
836
                return Convert.ChangeType(defaultValueSql, type, CultureInfo.InvariantCulture);
29✔
837
            }
838
            catch
×
839
            {
840
                // Ignored
841
                return null;
×
842
            }
843
        }
844

845
        // handle string literals
846
        if ((defaultValueSql.StartsWith('\'') || defaultValueSql.StartsWith("N'", StringComparison.OrdinalIgnoreCase))
88!
847
            && defaultValueSql.EndsWith('\''))
88✔
848
        {
849
            // extract string value from quotes
850
            var startIndex = defaultValueSql.IndexOf('\'');
×
851
            defaultValueSql = defaultValueSql.Substring(startIndex + 1, defaultValueSql.Length - (startIndex + 2));
×
852

853
            if (type == typeof(string))
×
854
                return defaultValueSql;
×
855

856
            if (type == typeof(bool) && bool.TryParse(defaultValueSql, out var boolValue))
×
857
                return boolValue;
×
858

859
            if (type == typeof(Guid) && Guid.TryParse(defaultValueSql, out var guid))
×
860
                return guid;
×
861

862
            if (type == typeof(DateTime) && DateTime.TryParse(defaultValueSql, out var dateTime))
×
863
                return dateTime;
×
864

865
            if (type == typeof(DateOnly) && DateOnly.TryParse(defaultValueSql, out var dateOnly))
×
866
                return dateOnly;
×
867

868
            if (type == typeof(TimeOnly) && TimeOnly.TryParse(defaultValueSql, out var timeOnly))
×
869
                return timeOnly;
×
870

871
            if (type == typeof(DateTimeOffset) && DateTimeOffset.TryParse(defaultValueSql, out var dateTimeOffset))
×
872
                return dateTimeOffset;
×
873
        }
874

875
        return null;
88✔
876

877
        // local function to unwrap parentheses
878
        void Unwrap()
879
        {
880
            while (defaultValueSql.StartsWith('(') && defaultValueSql.EndsWith(')'))
881
                defaultValueSql = defaultValueSql[1..^1].Trim();
882
        }
883
    }
29✔
884

885

886
    private static bool IsIgnored(RelationBase? relation, IEnumerable<MatchOptions> exclude)
887
    {
888
        if (relation is null)
250!
889
            return true;
×
890

891
        var name = relation.QualifiedName;
250✔
892
        var includeExpressions = Enumerable.Empty<MatchOptions>();
250✔
893
        var excludeExpressions = exclude ?? [];
250!
894

895
        return IsIgnored(name, excludeExpressions, includeExpressions);
250✔
896
    }
897

898
    private static bool IsIgnored(Column column, IEnumerable<MatchOptions> exclude)
899
    {
900
        var table = column.Parent;
1,303✔
901
        if (table == null)
1,303!
902
            return true;
×
903

904
        var name = $"{table.Schema}.{table.Name}.{column.Name}";
1,303✔
905
        var includeExpressions = Enumerable.Empty<MatchOptions>();
1,303✔
906
        var excludeExpressions = exclude ?? [];
1,303!
907

908
        return IsIgnored(name, excludeExpressions, includeExpressions);
1,303✔
909
    }
910

911
    private static bool IsIgnored(ForeignKey relationship, IEnumerable<MatchOptions> exclude)
912
    {
913
        var table = relationship.PrincipalTable;
77✔
914
        if (table == null)
77!
915
            return true;
×
916

917
        var name = $"{table.Schema}.{table.Name}.{relationship.Name}";
77✔
918
        var includeExpressions = Enumerable.Empty<MatchOptions>();
77✔
919
        var excludeExpressions = exclude ?? [];
77!
920

921
        return IsIgnored(name, excludeExpressions, includeExpressions);
77✔
922
    }
923

924
    private static bool IsIgnored<TOption>(Property property, TOption options, SharedModelOptions sharedOptions)
925
        where TOption : ModelOptionsBase
926
    {
927
        var name = $"{property.Entity.EntityClass}.{property.PropertyName}";
3,058✔
928

929
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Properties ?? []);
3,058!
930
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Properties ?? []);
3,058!
931

932
        var includeProperties = options?.Include?.Properties ?? [];
3,058!
933
        foreach (var expression in includeProperties)
6,116!
934
            includeExpressions.Add(expression);
×
935

936
        var excludeProperties = options?.Exclude?.Properties ?? [];
3,058!
937
        foreach (var expression in excludeProperties)
6,116!
938
            excludeExpressions.Add(expression);
×
939

940
        return IsIgnored(name, excludeExpressions, includeExpressions);
3,058✔
941
    }
942

943
    private static bool IsIgnored<TOption>(Entity entity, TOption options, SharedModelOptions sharedOptions)
944
        where TOption : ModelOptionsBase
945
    {
946
        var name = entity.EntityClass;
409✔
947

948
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Entities ?? []);
409!
949
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Entities ?? []);
409!
950

951
        var includeEntities = options?.Include?.Entities ?? [];
409!
952
        foreach (var expression in includeEntities)
818!
953
            includeExpressions.Add(expression);
×
954

955
        var excludeEntities = options?.Exclude?.Entities ?? [];
409!
956
        foreach (var expression in excludeEntities)
1,346✔
957
            excludeExpressions.Add(expression);
264✔
958

959
        return IsIgnored(name, excludeExpressions, includeExpressions);
409✔
960
    }
961

962
    private static bool IsIgnored(string name, IEnumerable<MatchOptions> excludeExpressions, IEnumerable<MatchOptions> includeExpressions)
963
    {
964
        foreach (var expression in includeExpressions)
10,194!
965
        {
966
            if (expression.IsMatch(name))
×
967
                return false;
×
968
        }
969

970
        foreach (var expression in excludeExpressions)
28,972✔
971
        {
972
            if (expression.IsMatch(name))
9,412✔
973
                return true;
46✔
974
        }
975

976
        return false;
5,051✔
977
    }
46✔
978

979

980
    [GeneratedRegex(@"^[^a-zA-Z_]+")]
981
    private static partial Regex LeadingNonAlphaRegex();
982

983
    [GeneratedRegex(@"(_ID|_id|_Id|\.ID|\.id|\.Id|ID|Id)$")]
984
    private static partial Regex IdSuffixRegex();
985

986
    [GeneratedRegex(@"^\d")]
987
    private static partial Regex DigitPrefixRegex();
988
}
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