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

loresoft / EntityFrameworkCore.Generator / 26121762180

19 May 2026 07:58PM UTC coverage: 54.885% (-0.03%) from 54.917%
26121762180

push

github

pwelter34
Skip Create/Update models for views; improve DI setup

653 of 1353 branches covered (48.26%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

46 existing lines in 4 files now uncovered.

1892 of 3284 relevant lines covered (57.61%)

63.09 hits per line

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

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

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

9
using Humanizer;
10

11
using Microsoft.EntityFrameworkCore.Metadata;
12
using Microsoft.EntityFrameworkCore.Metadata.Internal;
13
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
14
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata.Internal;
15
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
16
using Microsoft.EntityFrameworkCore.Storage;
17
using Microsoft.Extensions.Logging;
18

19
using Model = EntityFrameworkCore.Generator.Metadata.Generation.Model;
20
using Property = EntityFrameworkCore.Generator.Metadata.Generation.Property;
21
using PropertyCollection = EntityFrameworkCore.Generator.Metadata.Generation.PropertyCollection;
22

23
namespace EntityFrameworkCore.Generator;
24

25
public partial class ModelGenerator
26
{
27
    private readonly UniqueNamer _namer;
28
    private readonly ILogger _logger;
29
    private GeneratorOptions _options = null!;
30
    private IRelationalTypeMappingSource _typeMapper = null!;
31

32
    public ModelGenerator(ILoggerFactory logger)
11✔
33
    {
34
        _logger = logger.CreateLogger<ModelGenerator>();
11✔
35
        _namer = new UniqueNamer();
11✔
36
    }
11✔
37

38
    public EntityContext Generate(GeneratorOptions options, DatabaseModel databaseModel, IRelationalTypeMappingSource typeMappingSource)
39
    {
40
        ArgumentNullException.ThrowIfNull(options);
11✔
41
        ArgumentNullException.ThrowIfNull(databaseModel);
11✔
42
        ArgumentNullException.ThrowIfNull(typeMappingSource);
11✔
43

44
        _logger.LogInformation("Building code generation model from database: {databaseName}", databaseModel.DatabaseName);
11✔
45

46
        _options = options;
11✔
47
        _typeMapper = typeMappingSource;
11✔
48

49
        var entityContext = new EntityContext();
11✔
50
        entityContext.DatabaseName = databaseModel.DatabaseName;
11✔
51

52
        // update database variables
53
        _options.Database.Name = ToLegalName(databaseModel.DatabaseName);
11✔
54

55

56
        string contextClass = _options.Data.Context.Name ?? "DataContext";
11!
57
        contextClass = _namer.UniqueClassName(contextClass);
11✔
58

59
        string contextNamespace = _options.Data.Context.Namespace ?? "Data";
11!
60
        string contextBaseClass = _options.Data.Context.BaseClass ?? "DbContext";
11!
61

62
        entityContext.ContextClass = contextClass;
11✔
63
        entityContext.ContextNamespace = contextNamespace;
11✔
64
        entityContext.ContextBaseClass = contextBaseClass;
11✔
65

66
        var tables = databaseModel.Tables;
11✔
67

68
        foreach (var table in tables)
90✔
69
        {
70
            if (IsIgnored(table, _options.Database.Exclude.Tables))
34✔
71
            {
72
                _logger.LogDebug("  Skipping Table : {schema}.{name}", table.Schema, table.Name);
2✔
73
                continue;
2✔
74
            }
75

76
            _logger.LogDebug("  Processing Table : {schema}.{name}", table.Schema, table.Name);
32✔
77

78
            _options.Variables.Set(VariableConstants.TableSchema, ToLegalName(table.Schema));
32✔
79
            _options.Variables.Set(VariableConstants.TableName, ToLegalName(table.Name));
32✔
80

81
            var entity = GetEntity(entityContext, table);
32✔
82
            GetModels(entity);
32✔
83
        }
84

85
        _options.Variables.Remove(VariableConstants.TableName);
11✔
86
        _options.Variables.Remove(VariableConstants.TableSchema);
11✔
87

88
        return entityContext;
11✔
89
    }
90

91

92
    private Entity GetEntity(EntityContext entityContext, DatabaseTable tableSchema, bool processRelationships = true, bool processMethods = true)
93
    {
94
        var entity = entityContext.Entities.ByTable(tableSchema.Name, tableSchema.Schema)
46✔
95
            ?? CreateEntity(entityContext, tableSchema);
46✔
96

97
        if (!entity.Properties.IsProcessed)
46✔
98
            CreateProperties(entity, tableSchema);
32✔
99

100
        if (processRelationships && !entity.Relationships.IsProcessed)
46✔
101
            CreateRelationships(entityContext, entity, tableSchema);
32✔
102

103
        if (processMethods && !entity.Methods.IsProcessed)
46✔
104
            CreateMethods(entity, tableSchema);
32✔
105

106
        entity.IsProcessed = true;
46✔
107
        return entity;
46✔
108
    }
109

110
    private Entity CreateEntity(EntityContext entityContext, DatabaseTable tableSchema)
111
    {
112
        var entity = new Entity
32✔
113
        {
32✔
114
            Context = entityContext,
32✔
115
            TableName = tableSchema.Name,
32✔
116
            TableSchema = tableSchema.Schema
32✔
117
        };
32✔
118

119
        var entityClass = _options.Data.Entity.Name;
32✔
120
        if (entityClass.IsNullOrEmpty())
32✔
121
            entityClass = ToClassName(tableSchema.Name, tableSchema.Schema);
32✔
122

123
        entityClass = _namer.UniqueClassName(entityClass);
32✔
124

125
        var entityNamespace = _options.Data.Entity.Namespace ?? "Data.Entities";
32!
126
        var entiyBaseClass = _options.Data.Entity.BaseClass;
32✔
127

128

129
        var mappingName = entityClass + "Map";
32✔
130
        mappingName = _namer.UniqueClassName(mappingName);
32✔
131

132
        var mappingNamespace = _options.Data.Mapping.Namespace ?? "Data.Mapping";
32!
133

134
        var contextName = ContextName(entityClass);
32✔
135
        contextName = ToPropertyName(entityContext.ContextClass, contextName);
32✔
136
        contextName = _namer.UniqueContextName(contextName);
32✔
137

138
        entity.EntityClass = entityClass;
32✔
139
        entity.EntityNamespace = entityNamespace;
32✔
140
        entity.EntityBaseClass = entiyBaseClass;
32✔
141

142
        entity.MappingClass = mappingName;
32✔
143
        entity.MappingNamespace = mappingNamespace;
32✔
144

145
        entity.ContextProperty = contextName;
32✔
146

147
        entity.IsView = tableSchema is DatabaseView;
32✔
148

149
        bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?;
32✔
150
        if (isTemporal == true && _options.Data.Mapping.Temporal)
32!
151
        {
152
            entity.TemporalTableName = tableSchema[SqlServerAnnotationNames.TemporalHistoryTableName] as string;
×
153
            entity.TemporalTableSchema = tableSchema[SqlServerAnnotationNames.TemporalHistoryTableSchema] as string;
×
154

155
            entity.TemporalStartProperty = tableSchema[SqlServerAnnotationNames.TemporalPeriodStartPropertyName] as string;
×
156

157
            entity.TemporalStartColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodStartColumnName] as string
×
158
                ?? entity.TemporalStartProperty;
×
159

160
            entity.TemporalEndProperty = tableSchema[SqlServerAnnotationNames.TemporalPeriodEndPropertyName] as string;
×
161

162
            entity.TemporalEndColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodEndColumnName] as string
×
163
                ?? entity.TemporalEndProperty;
×
164
        }
165

166
        entityContext.Entities.Add(entity);
32✔
167

168
        return entity;
32✔
169
    }
170

171

172
    private void CreateProperties(Entity entity, DatabaseTable tableSchema)
173
    {
174
        var columns = tableSchema.Columns;
32✔
175
        foreach (var column in columns)
522✔
176
        {
177
            var table = column.Table;
229✔
178
            if (IsIgnored(column, _options.Database.Exclude.Columns))
229!
179
            {
180
                _logger.LogDebug("  Skipping Column : {Schema}.{Table}.{Column}", table.Schema, table.Name, column.Name);
×
181
                continue;
×
182
            }
183

184
            var mapping = column.StoreType.HasValue() ? _typeMapper.FindMapping(column.StoreType) : null;
229!
185
            if (mapping == null)
229!
186
            {
187
                _logger.LogWarning("Failed to map type {storeType} for {column}.", column.StoreType, column.Name);
×
188
                continue;
×
189
            }
190

191
            var property = entity.Properties.ByColumn(column.Name);
229✔
192

193
            if (property == null)
229✔
194
            {
195
                property = new Property
227✔
196
                {
227✔
197
                    Entity = entity,
227✔
198
                    ColumnName = column.Name
227✔
199
                };
227✔
200
                entity.Properties.Add(property);
227✔
201
            }
202

203
            string name = ToPropertyName(entity.EntityClass, column.Name);
229✔
204
            string propertyName = name;
229✔
205

206
            foreach (var selection in _options.Data.Entity.Renaming.Properties.Where(p => p.Expression.HasValue()))
458!
207
            {
208
                if (selection.Expression.IsNullOrEmpty())
×
209
                    continue;
210

211
                propertyName = Regex.Replace(propertyName, selection.Expression, string.Empty);
×
212
            }
213

214
            // make sure regex doesn't remove everything
215
            if (propertyName.IsNullOrEmpty())
229!
216
                propertyName = name;
×
217

218
            propertyName = _namer.UniqueName(entity.EntityClass, propertyName);
229✔
219

220
            property.PropertyName = propertyName;
229✔
221

222
            property.IsNullable = column.IsNullable;
229✔
223

224
            property.IsRowVersion = column.IsRowVersion();
229✔
225
            property.IsConcurrencyToken = (bool?)column[ScaffoldingAnnotationNames.ConcurrencyToken] == true;
229✔
226

227
            property.IsPrimaryKey = table.PrimaryKey?.Columns.Contains(column) == true;
229✔
228
            property.IsForeignKey = table.ForeignKeys.Any(c => c.Columns.Contains(column));
365✔
229

230
            property.IsUnique = table.UniqueConstraints.Any(c => c.Columns.Contains(column))
229!
231
                                || table.Indexes.Where(i => i.IsUnique).Any(c => c.Columns.Contains(column));
481✔
232

233
            property.DefaultValue = column.DefaultValue;
229✔
234
            property.Default = column.DefaultValueSql;
229✔
235

236
            property.ValueGenerated = column.ValueGenerated;
229✔
237

238
            if (property.ValueGenerated == null && !string.IsNullOrWhiteSpace(column.ComputedColumnSql))
229!
239
                property.ValueGenerated = ValueGenerated.OnAddOrUpdate;
×
240

241
            property.StoreType = mapping.StoreType;
229✔
242
            property.NativeType = mapping.StoreTypeNameBase;
229✔
243
            property.DataType = mapping.DbType ?? DbType.AnsiString;
229✔
244
            property.SystemType = mapping.ClrType;
229✔
245
            property.Size = mapping.Size;
229✔
246

247
            // overwrite row version type
248
            if (property.IsRowVersion == true && _options.Data.Mapping.RowVersion != RowVersionMapping.ByteArray && property.SystemType == typeof(byte[]))
229!
249
            {
250
                property.SystemType = _options.Data.Mapping.RowVersion switch
×
251
                {
×
252
                    RowVersionMapping.ByteArray => typeof(byte[]),
×
253
                    RowVersionMapping.Long => typeof(long),
×
254
                    RowVersionMapping.ULong => typeof(ulong),
×
255
                    _ => typeof(byte[])
×
256
                };
×
257
            }
258

259
            property.IsProcessed = true;
229✔
260
        }
261

262
        entity.Properties.IsProcessed = true;
32✔
263

264
        bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?;
32✔
265
        if (isTemporal != true || _options.Data.Mapping.Temporal)
32!
266
            return;
32✔
267

268
        // add temporal period columns
269
        var temporalStartColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodStartColumnName] as string
×
270
            ?? tableSchema[SqlServerAnnotationNames.TemporalPeriodStartPropertyName] as string;
×
271

272
        var temporalEndColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodEndColumnName] as string
×
273
            ?? tableSchema[SqlServerAnnotationNames.TemporalPeriodEndPropertyName] as string;
×
274

275
        if (temporalStartColumn.IsNullOrEmpty() || temporalEndColumn.IsNullOrEmpty())
×
276
            return;
×
277

278
        var temporalStart = entity.Properties.ByColumn(temporalStartColumn);
×
279

280
        if (temporalStart == null)
×
281
        {
282
            temporalStart = new Property { Entity = entity, ColumnName = temporalStartColumn };
×
283
            entity.Properties.Add(temporalStart);
×
284
        }
285

286
        temporalStart.PropertyName = ToPropertyName(entity.EntityClass, temporalStartColumn);
×
287
        temporalStart.ValueGenerated = ValueGenerated.OnAddOrUpdate;
×
288
        temporalStart.StoreType = "datetime2";
×
289
        temporalStart.DataType = DbType.DateTime2;
×
290
        temporalStart.SystemType = typeof(DateTime);
×
291

292
        temporalStart.IsProcessed = true;
×
293

294
        var temporalEnd = entity.Properties.ByColumn(temporalEndColumn);
×
295

296
        if (temporalEnd == null)
×
297
        {
298
            temporalEnd = new Property { Entity = entity, ColumnName = temporalEndColumn };
×
299
            entity.Properties.Add(temporalEnd);
×
300
        }
301

302
        temporalEnd.PropertyName = ToPropertyName(entity.EntityClass, temporalEndColumn);
×
303
        temporalEnd.ValueGenerated = ValueGenerated.OnAddOrUpdate;
×
304
        temporalEnd.StoreType = "datetime2";
×
305
        temporalEnd.DataType = DbType.DateTime2;
×
306
        temporalEnd.SystemType = typeof(DateTime);
×
307

308
        temporalEnd.IsProcessed = true;
×
309
    }
×
310

311

312
    private void CreateRelationships(EntityContext entityContext, Entity entity, DatabaseTable tableSchema)
313
    {
314
        foreach (var foreignKey in tableSchema.ForeignKeys.OrderBy(fk => fk.Name))
106✔
315
        {
316
            // skip relationship if principal table is ignored
317
            if (IsIgnored(foreignKey.PrincipalTable, _options.Database.Exclude.Tables))
14!
318
            {
319
                _logger.LogDebug("  Skipping Relationship : {name}", foreignKey.Name);
×
320
                continue;
×
321
            }
322

323
            if (IsIgnored(foreignKey, _options.Database.Exclude.Relationships))
14!
324
            {
UNCOV
325
                _logger.LogDebug("  Skipping Relationship : {name}", foreignKey.Name);
×
UNCOV
326
                continue;
×
327
            }
328

329
            CreateRelationship(entityContext, entity, foreignKey);
14✔
330
        }
331

332
        entity.Relationships.IsProcessed = true;
32✔
333
    }
32✔
334

335
    private void CreateRelationship(EntityContext entityContext, Entity foreignEntity, DatabaseForeignKey tableKeySchema)
336
    {
337
        Entity primaryEntity = GetEntity(entityContext, tableKeySchema.PrincipalTable, false, false);
14✔
338

339
        var primaryName = primaryEntity.EntityClass;
14✔
340
        var foreignName = foreignEntity.EntityClass;
14✔
341

342
        var foreignMembers = GetKeyMembers(foreignEntity, tableKeySchema.Columns, tableKeySchema.Name);
14✔
343
        bool foreignMembersRequired = foreignMembers.Any(c => c.IsRequired);
28✔
344

345
        var primaryMembers = GetKeyMembers(primaryEntity, tableKeySchema.PrincipalColumns, tableKeySchema.Name);
14✔
346
        bool primaryMembersRequired = primaryMembers.Any(c => c.IsRequired);
28✔
347

348
        // skip invalid fkeys
349
        if (foreignMembers.Count == 0 || primaryMembers.Count == 0)
14!
350
            return;
×
351

352
        var relationshipName = tableKeySchema.Name;
14✔
353

354
        // ensure relationship name for sync support
355
        if (relationshipName.IsNullOrEmpty())
14!
UNCOV
356
            relationshipName = $"FK_{foreignName}_{primaryName}_{primaryMembers.Select(p => p.PropertyName).ToDelimitedString("_")}";
×
357

358
        relationshipName = _namer.UniqueRelationshipName(relationshipName);
14✔
359

360
        var foreignRelationship = foreignEntity.Relationships
14✔
361
            .FirstOrDefault(r => r.RelationshipName == relationshipName && r.IsForeignKey);
22!
362

363
        if (foreignRelationship == null)
14✔
364
        {
365
            foreignRelationship = new Relationship { RelationshipName = relationshipName };
14✔
366
            foreignEntity.Relationships.Add(foreignRelationship);
14✔
367
        }
368
        foreignRelationship.IsMapped = true;
14✔
369
        foreignRelationship.IsForeignKey = true;
14✔
370
        foreignRelationship.Cardinality = foreignMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
14✔
371

372
        foreignRelationship.PrimaryEntity = primaryEntity;
14✔
373
        foreignRelationship.PrimaryProperties = [.. primaryMembers];
14✔
374

375
        foreignRelationship.Entity = foreignEntity;
14✔
376
        foreignRelationship.Properties = [.. foreignMembers];
14✔
377

378
        string prefix = GetMemberPrefix(foreignRelationship, primaryName, foreignName);
14✔
379

380
        string foreignPropertyName = ToPropertyName(foreignEntity.EntityClass, prefix + primaryName);
14✔
381
        foreignPropertyName = _namer.UniqueName(foreignEntity.EntityClass, foreignPropertyName);
14✔
382
        foreignRelationship.PropertyName = foreignPropertyName;
14✔
383

384
        // add reverse
385
        var primaryRelationship = primaryEntity.Relationships
14✔
386
            .FirstOrDefault(r => r.RelationshipName == relationshipName && !r.IsForeignKey);
26!
387

388
        if (primaryRelationship == null)
14✔
389
        {
390
            primaryRelationship = new Relationship { RelationshipName = relationshipName };
14✔
391
            primaryEntity.Relationships.Add(primaryRelationship);
14✔
392
        }
393

394
        primaryRelationship.IsMapped = false;
14✔
395
        primaryRelationship.IsForeignKey = false;
14✔
396

397
        primaryRelationship.PrimaryEntity = foreignEntity;
14✔
398
        primaryRelationship.PrimaryProperties = [.. foreignMembers];
14✔
399

400
        primaryRelationship.Entity = primaryEntity;
14✔
401
        primaryRelationship.Properties = [.. primaryMembers];
14✔
402

403
        bool isOneToOne = IsOneToOne(tableKeySchema, foreignRelationship);
14✔
404
        if (isOneToOne)
14✔
405
            primaryRelationship.Cardinality = primaryMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
2!
406
        else
407
            primaryRelationship.Cardinality = Cardinality.Many;
12✔
408

409
        string primaryPropertyName = prefix + foreignName;
14✔
410
        if (!isOneToOne)
14✔
411
            primaryPropertyName = RelationshipName(primaryPropertyName);
12✔
412

413
        primaryPropertyName = ToPropertyName(primaryEntity.EntityClass, primaryPropertyName);
14✔
414
        primaryPropertyName = _namer.UniqueName(primaryEntity.EntityClass, primaryPropertyName);
14✔
415

416
        primaryRelationship.PropertyName = primaryPropertyName;
14✔
417

418
        foreignRelationship.PrimaryPropertyName = primaryRelationship.PropertyName;
14✔
419
        foreignRelationship.PrimaryCardinality = primaryRelationship.Cardinality;
14✔
420

421
        primaryRelationship.PrimaryPropertyName = foreignRelationship.PropertyName;
14✔
422
        primaryRelationship.PrimaryCardinality = foreignRelationship.Cardinality;
14✔
423

424
        foreignRelationship.IsProcessed = true;
14✔
425
        primaryRelationship.IsProcessed = true;
14✔
426
    }
14✔
427

428

429
    private static void CreateMethods(Entity entity, DatabaseTable tableSchema)
430
    {
431
        if (tableSchema.PrimaryKey != null)
32✔
432
        {
433
            var method = GetMethodFromColumns(entity, tableSchema.PrimaryKey.Columns);
22✔
434
            if (method != null)
22✔
435
            {
436
                method.IsKey = true;
22✔
437
                method.SourceName = tableSchema.PrimaryKey.Name;
22✔
438

439
                if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
22✔
440
                    entity.Methods.Add(method);
22✔
441
            }
442
        }
443

444
        GetIndexMethods(entity, tableSchema);
32✔
445
        GetForeignKeyMethods(entity, tableSchema);
32✔
446

447
        entity.Methods.IsProcessed = true;
32✔
448
    }
32✔
449

450
    private static void GetForeignKeyMethods(Entity entity, DatabaseTable table)
451
    {
452
        var columns = new List<DatabaseColumn>();
32✔
453

454
        foreach (var column in table.ForeignKeys.SelectMany(c => c.Columns))
106✔
455
        {
456
            columns.Add(column);
14✔
457

458
            var method = GetMethodFromColumns(entity, columns);
14✔
459
            if (method != null && entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
46✔
460
                entity.Methods.Add(method);
4✔
461

462
            columns.Clear();
14✔
463
        }
464
    }
32✔
465

466
    private static void GetIndexMethods(Entity entity, DatabaseTable table)
467
    {
468
        foreach (var index in table.Indexes)
92✔
469
        {
470
            var method = GetMethodFromColumns(entity, index.Columns);
14✔
471
            if (method == null)
14✔
472
                continue;
473

474
            method.SourceName = index.Name;
14✔
475
            method.IsUnique = index.IsUnique;
14✔
476
            method.IsIndex = true;
14✔
477

478
            if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
36✔
479
                entity.Methods.Add(method);
14✔
480
        }
481
    }
32✔
482

483
    private static Method? GetMethodFromColumns(Entity entity, IEnumerable<DatabaseColumn> columns)
484
    {
485
        var method = new Method { Entity = entity };
50✔
486
        var methodName = new StringBuilder();
50✔
487

488
        foreach (var column in columns)
204✔
489
        {
490
            var property = entity.Properties.ByColumn(column.Name);
52✔
491
            if (property == null)
52✔
492
                continue;
493

494
            method.Properties.Add(property);
52✔
495
            methodName.Append(property.PropertyName);
52✔
496
        }
497

498
        if (method.Properties.Count == 0)
50!
UNCOV
499
            return null;
×
500

501
        method.NameSuffix = methodName.ToString();
50✔
502
        return method;
50✔
503
    }
504

505

506
    private void GetModels(Entity? entity)
507
    {
508
        if (entity == null || entity.Models.IsProcessed)
32!
UNCOV
509
            return;
×
510

511
        _options.Variables.Set(entity);
32✔
512

513
        if (_options.Model.Read.Generate)
32✔
514
            CreateModel(entity, _options.Model.Read, ModelType.Read);
3✔
515
        if (!entity.IsView && _options.Model.Create.Generate)
32✔
516
            CreateModel(entity, _options.Model.Create, ModelType.Create);
2✔
517
        if (!entity.IsView && _options.Model.Update.Generate)
32✔
518
            CreateModel(entity, _options.Model.Update, ModelType.Update);
2✔
519

520
        if (entity.Models.Count > 0)
32✔
521
        {
522
            var mapperNamespace = _options.Model.Mapper.Namespace ?? "Data.Mapper";
3!
523

524
            var mapperClass = ToLegalName(_options.Model.Mapper.Name);
3✔
525
            mapperClass = _namer.UniqueModelName(mapperNamespace, mapperClass);
3✔
526

527
            entity.MapperClass = mapperClass;
3✔
528
            entity.MapperNamespace = mapperNamespace;
3✔
529
            entity.MapperBaseClass = _options.Model.Mapper.BaseClass;
3✔
530
        }
531

532
        _options.Variables.Remove(entity);
32✔
533

534
        entity.Models.IsProcessed = true;
32✔
535
    }
32✔
536

537
    private void CreateModel<TOption>(Entity entity, TOption options, ModelType modelType)
538
        where TOption : ModelOptionsBase
539
    {
540
        if (IsIgnored(entity, options, _options.Model.Shared))
7!
UNCOV
541
            return;
×
542

543
        var modelNamespace = options.Namespace.HasValue()
7!
544
            ? options.Namespace
7✔
545
            : _options.Model.Shared.Namespace;
7✔
546

547
        var modelHeader = options.Header.HasValue()
7!
548
            ? options.Header
7✔
549
            : _options.Model.Shared.Header;
7✔
550

551
        modelNamespace ??= "Data.Models";
7!
552

553
        var modelClass = ToLegalName(options.Name);
7✔
554
        modelClass = _namer.UniqueModelName(modelNamespace, modelClass);
7✔
555

556
        var model = new Model
7✔
557
        {
7✔
558
            Entity = entity,
7✔
559
            ModelType = modelType,
7✔
560
            ModelBaseClass = options.BaseClass,
7✔
561
            ModelNamespace = modelNamespace,
7✔
562
            ModelClass = modelClass,
7✔
563
            ModelAttributes = options.Attributes,
7✔
564
            ModelHeader = modelHeader
7✔
565
        };
7✔
566

567
        foreach (var property in entity.Properties)
42✔
568
        {
569
            if (IsIgnored(property, options, _options.Model.Shared))
14✔
570
                continue;
571

572
            model.Properties.Add(property);
14✔
573
        }
574

575
        _options.Variables.Set(model);
7✔
576

577
        var validatorNamespace = _options.Model.Validator.Namespace ?? "Data.Validation";
7!
578
        var validatorClass = ToLegalName(_options.Model.Validator.Name);
7✔
579
        validatorClass = _namer.UniqueModelName(validatorNamespace, validatorClass);
7✔
580

581
        model.ValidatorBaseClass = _options.Model.Validator.BaseClass;
7✔
582
        model.ValidatorClass = validatorClass;
7✔
583
        model.ValidatorNamespace = validatorNamespace;
7✔
584

585
        entity.Models.Add(model);
7✔
586

587
        _options.Variables.Remove(model);
7✔
588
    }
7✔
589

590

591
    private List<Property> GetKeyMembers(Entity entity, IEnumerable<DatabaseColumn> members, string? relationshipName)
592
    {
593
        var keyMembers = new List<Property>();
28✔
594

595
        foreach (var member in members)
112✔
596
        {
597
            var property = entity.Properties.ByColumn(member.Name);
28✔
598

599
            if (property == null)
28!
UNCOV
600
                _logger.LogWarning("Could not find column {columnName} for relationship {relationshipName}.", member.Name, relationshipName);
×
601
            else
602
                keyMembers.Add(property);
28✔
603
        }
604

605
        return keyMembers;
28✔
606
    }
607

608
    private static string GetMemberPrefix(Relationship relationship, string primaryClass, string foreignClass)
609
    {
610
        string thisKey = relationship.Properties
14!
611
            .Select(p => p.PropertyName)
14✔
612
            .FirstOrDefault() ?? string.Empty;
14✔
613

614
        string otherKey = relationship.PrimaryProperties
14!
615
            .Select(p => p.PropertyName)
14✔
616
            .FirstOrDefault() ?? string.Empty;
14✔
617

618
        bool isSameName = thisKey.Equals(otherKey, StringComparison.OrdinalIgnoreCase);
14✔
619
        isSameName = (isSameName || thisKey.Equals(primaryClass + otherKey, StringComparison.OrdinalIgnoreCase));
14!
620

621
        string prefix = string.Empty;
14✔
622
        if (isSameName)
14✔
623
            return prefix;
12✔
624

625
        prefix = thisKey.Replace(otherKey, "");
2✔
626
        prefix = prefix.Replace(primaryClass, "");
2✔
627
        prefix = prefix.Replace(foreignClass, "");
2✔
628
        prefix = IdSuffixRegex().Replace(prefix, "");
2✔
629
        prefix = DigitPrefixRegex().Replace(prefix, "");
2✔
630

631
        return prefix;
2✔
632
    }
633

634
    private static bool IsOneToOne(DatabaseForeignKey tableKeySchema, Relationship foreignRelationship)
635
    {
636
        var foreignColumn = foreignRelationship.Properties
14✔
637
            .Select(p => p.ColumnName)
14✔
638
            .FirstOrDefault();
14✔
639

640
        return tableKeySchema.PrincipalTable.PrimaryKey != null
14✔
641
            && tableKeySchema.Table.PrimaryKey != null
14✔
642
            && tableKeySchema.Table.PrimaryKey.Columns.Count == 1
14✔
643
            && tableKeySchema.Table.PrimaryKey.Columns.Any(c => c.Name == foreignColumn);
24✔
644

645
        // if f.key is unique
646
        //return tableKeySchema.ForeignKeyMemberColumns.All(column => column.IsUnique);
647
    }
648

649

650
    private string RelationshipName(string name)
651
    {
652
        var naming = _options.Data.Entity.RelationshipNaming;
12✔
653
        if (naming == RelationshipNaming.Preserve)
12!
UNCOV
654
            return name;
×
655

656
        if (naming == RelationshipNaming.Suffix)
12!
UNCOV
657
            return name + "List";
×
658

659
        return name.Pluralize(false);
12✔
660
    }
661

662
    private string ContextName(string name)
663
    {
664
        var naming = _options.Data.Context.PropertyNaming;
32✔
665
        if (naming == ContextNaming.Preserve)
32!
UNCOV
666
            return name;
×
667

668
        if (naming == ContextNaming.Suffix)
32!
UNCOV
669
            return name + "DataSet";
×
670

671
        return name.Pluralize(false);
32✔
672
    }
673

674
    private string EntityName(string name)
675
    {
676
        var tableNaming = _options.Database.TableNaming;
32✔
677
        var entityNaming = _options.Data.Entity.EntityNaming;
32✔
678

679
        if (tableNaming != TableNaming.Plural && entityNaming == EntityNaming.Plural)
32!
UNCOV
680
            name = name.Pluralize(false);
×
681
        else if (tableNaming != TableNaming.Singular && entityNaming == EntityNaming.Singular)
32!
UNCOV
682
            name = name.Singularize(false);
×
683

684
        var rename = name;
32✔
685
        foreach (var selection in _options.Data.Entity.Renaming.Entities.Where(p => p.Expression.HasValue()))
64!
686
        {
UNCOV
687
            if (selection.Expression.IsNullOrEmpty())
×
688
                continue;
689

UNCOV
690
            rename = Regex.Replace(rename, selection.Expression, string.Empty);
×
691
        }
692

693
        // make sure regex doesn't remove everything
694
        return rename.HasValue() ? rename : name;
32!
695
    }
696

697

698
    private string ToClassName(string tableName, string? tableSchema)
699
    {
700
        tableName = EntityName(tableName);
32✔
701
        var className = tableName;
32✔
702

703
        if (_options.Data.Entity.PrefixWithSchemaName && tableSchema != null)
32✔
704
            className = $"{tableSchema}{tableName}";
2✔
705

706
        return ToLegalName(className);
32✔
707
    }
708

709
    private string ToPropertyName(string className, string name)
710
    {
711
        string propertyName = ToLegalName(name);
289✔
712
        if (className.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
289!
UNCOV
713
            propertyName += "Member";
×
714

715
        return propertyName;
289✔
716
    }
717

718
    private static string ToLegalName(string? name)
719
    {
720
        if (string.IsNullOrWhiteSpace(name))
413!
UNCOV
721
            return string.Empty;
×
722

723
        string legalName = name;
413✔
724

725
        // remove invalid leading
726
        var expression = LeadingNonAlphaRegex();
413✔
727
        if (expression.IsMatch(name))
413✔
728
            legalName = expression.Replace(legalName, string.Empty);
1✔
729

730
        // prefix with column when all characters removed
731
        if (legalName.IsNullOrWhiteSpace())
413✔
732
            legalName = "Number" + name;
1✔
733

734
        return legalName.ToPascalCase();
413✔
735
    }
736

737

738
    private static bool IsIgnored(DatabaseTable table, IEnumerable<MatchOptions> exclude)
739
    {
740
        var name = $"{table.Schema}.{table.Name}";
48✔
741
        var includeExpressions = Enumerable.Empty<MatchOptions>();
48✔
742
        var excludeExpressions = exclude ?? [];
48!
743

744
        return IsIgnored(name, excludeExpressions, includeExpressions);
48✔
745
    }
746

747
    private static bool IsIgnored(DatabaseColumn column, IEnumerable<MatchOptions> exclude)
748
    {
749
        var table = column.Table;
229✔
750
        var name = $"{table.Schema}.{table.Name}.{column.Name}";
229✔
751
        var includeExpressions = Enumerable.Empty<MatchOptions>();
229✔
752
        var excludeExpressions = exclude ?? [];
229!
753

754
        return IsIgnored(name, excludeExpressions, includeExpressions);
229✔
755
    }
756

757
    private static bool IsIgnored(DatabaseForeignKey relationship, IEnumerable<MatchOptions> exclude)
758
    {
759
        var table = relationship.Table;
14✔
760
        var name = $"{table.Schema}.{table.Name}.{relationship.Name}";
14✔
761
        var includeExpressions = Enumerable.Empty<MatchOptions>();
14✔
762
        var excludeExpressions = exclude ?? [];
14!
763

764
        return IsIgnored(name, excludeExpressions, includeExpressions);
14✔
765
    }
766

767
    private static bool IsIgnored<TOption>(Property property, TOption options, SharedModelOptions sharedOptions)
768
        where TOption : ModelOptionsBase
769
    {
770
        var name = $"{property.Entity.EntityClass}.{property.PropertyName}";
14✔
771

772
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Properties ?? []);
14!
773
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Properties ?? []);
14!
774

775
        var includeProperties = options?.Include?.Properties ?? [];
14!
776
        foreach (var expression in includeProperties)
28!
UNCOV
777
            includeExpressions.Add(expression);
×
778

779
        var excludeProperties = options?.Exclude?.Properties ?? [];
14!
780
        foreach (var expression in excludeProperties)
28!
UNCOV
781
            excludeExpressions.Add(expression);
×
782

783
        return IsIgnored(name, excludeExpressions, includeExpressions);
14✔
784
    }
785

786
    private static bool IsIgnored<TOption>(Entity entity, TOption options, SharedModelOptions sharedOptions)
787
        where TOption : ModelOptionsBase
788
    {
789
        var name = entity.EntityClass;
7✔
790

791
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Entities ?? []);
7!
792
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Entities ?? []);
7!
793

794
        var includeEntities = options?.Include?.Entities ?? [];
7!
795
        foreach (var expression in includeEntities)
14!
UNCOV
796
            includeExpressions.Add(expression);
×
797

798
        var excludeEntities = options?.Exclude?.Entities ?? [];
7!
799
        foreach (var expression in excludeEntities)
14!
UNCOV
800
            excludeExpressions.Add(expression);
×
801

802
        return IsIgnored(name, excludeExpressions, includeExpressions);
7✔
803
    }
804

805
    private static bool IsIgnored(string name, IEnumerable<MatchOptions> excludeExpressions, IEnumerable<MatchOptions> includeExpressions)
806
    {
807
        foreach (var expression in includeExpressions)
624!
808
        {
UNCOV
809
            if (expression.IsMatch(name))
×
UNCOV
810
                return false;
×
811
        }
812

813
        foreach (var expression in excludeExpressions)
632✔
814
        {
815
            if (expression.IsMatch(name))
5✔
816
                return true;
2✔
817
        }
818

819
        return false;
310✔
820
    }
2✔
821

822

823
    [GeneratedRegex(@"^[^a-zA-Z_]+")]
824
    private static partial Regex LeadingNonAlphaRegex();
825

826
    [GeneratedRegex(@"(_ID|_id|_Id|\.ID|\.id|\.Id|ID|Id)$")]
827
    private static partial Regex IdSuffixRegex();
828

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