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

loresoft / EntityFrameworkCore.Generator / 15072048022

16 May 2025 03:31PM UTC coverage: 55.392% (-1.4%) from 56.772%
15072048022

push

github

pwelter34
enable nullable support

616 of 1271 branches covered (48.47%)

Branch coverage included in aggregate %.

233 of 397 new or added lines in 61 files covered. (58.69%)

17 existing lines in 11 files now uncovered.

1824 of 3134 relevant lines covered (58.2%)

88.56 hits per line

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

75.53
/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)
110✔
69
        {
70
            if (IsIgnored(table, _options.Database.Exclude))
44✔
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);
42✔
77

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

81
            var entity = GetEntity(entityContext, table);
42✔
82
            GetModels(entity);
42✔
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)
63✔
95
            ?? CreateEntity(entityContext, tableSchema);
63✔
96

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

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

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

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

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

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

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

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

128

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

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

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

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

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

145
        entity.ContextProperty = contextName;
42✔
146

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

149
        bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?;
42✔
150
        if (isTemporal == true && _options.Data.Mapping.Temporal)
42!
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);
42✔
167

168
        return entity;
42✔
169
    }
170

171

172
    private void CreateProperties(Entity entity, DatabaseTable tableSchema)
173
    {
174
        var columns = tableSchema.Columns;
42✔
175
        foreach (var column in columns)
748✔
176
        {
177
            var table = column.Table;
332✔
178

179
            var mapping = column.StoreType.HasValue() ? _typeMapper.FindMapping(column.StoreType) : null;
332!
180
            if (mapping == null)
332!
181
            {
182
                _logger.LogWarning("Failed to map type {storeType} for {column}.", column.StoreType, column.Name);
×
183
                continue;
×
184
            }
185

186
            var property = entity.Properties.ByColumn(column.Name);
332✔
187

188
            if (property == null)
332✔
189
            {
190
                property = new Property
330✔
191
                {
330✔
192
                    Entity = entity,
330✔
193
                    ColumnName = column.Name
330✔
194
                };
330✔
195
                entity.Properties.Add(property);
330✔
196
            }
197

198
            string name = ToPropertyName(entity.EntityClass, column.Name);
332✔
199
            string propertyName = name;
332✔
200

201
            foreach (var selection in _options.Data.Entity.Renaming.Properties.Where(p => p.Expression.HasValue()))
664!
202
            {
NEW
203
                if (selection.Expression.IsNullOrEmpty())
×
204
                    continue;
205

UNCOV
206
                propertyName = Regex.Replace(propertyName, selection.Expression, string.Empty);
×
207
            }
208

209
            // make sure regex doesn't remove everything
210
            if (propertyName.IsNullOrEmpty())
332!
211
                propertyName = name;
×
212

213
            propertyName = _namer.UniqueName(entity.EntityClass, propertyName);
332✔
214

215
            property.PropertyName = propertyName;
332✔
216

217
            property.IsNullable = column.IsNullable;
332✔
218

219
            property.IsRowVersion = column.IsRowVersion();
332✔
220
            property.IsConcurrencyToken = (bool?)column[ScaffoldingAnnotationNames.ConcurrencyToken] == true;
332✔
221

222
            property.IsPrimaryKey = table.PrimaryKey?.Columns.Contains(column) == true;
332✔
223
            property.IsForeignKey = table.ForeignKeys.Any(c => c.Columns.Contains(column));
536✔
224

225
            property.IsUnique = table.UniqueConstraints.Any(c => c.Columns.Contains(column))
332!
226
                                || table.Indexes.Where(i => i.IsUnique).Any(c => c.Columns.Contains(column));
710✔
227

228
            property.DefaultValue = column.DefaultValue;
332✔
229
            property.Default = column.DefaultValueSql;
332✔
230

231
            property.ValueGenerated = column.ValueGenerated;
332✔
232

233
            if (property.ValueGenerated == null && !string.IsNullOrWhiteSpace(column.ComputedColumnSql))
332!
234
                property.ValueGenerated = ValueGenerated.OnAddOrUpdate;
×
235

236
            property.StoreType = mapping.StoreType;
332✔
237
            property.NativeType = mapping.StoreTypeNameBase;
332✔
238
            property.DataType = mapping.DbType ?? DbType.AnsiString;
332✔
239
            property.SystemType = mapping.ClrType;
332✔
240
            property.Size = mapping.Size;
332✔
241

242
            // overwrite row version type
243
            if (property.IsRowVersion == true && _options.Data.Mapping.RowVersion != RowVersionMapping.ByteArray && property.SystemType == typeof(byte[]))
332!
244
            {
245
                property.SystemType = _options.Data.Mapping.RowVersion switch
×
246
                {
×
247
                    RowVersionMapping.ByteArray => typeof(byte[]),
×
248
                    RowVersionMapping.Long => typeof(long),
×
249
                    RowVersionMapping.ULong => typeof(ulong),
×
250
                    _ => typeof(byte[])
×
251
                };
×
252
            }
253

254
            property.IsProcessed = true;
332✔
255
        }
256

257
        entity.Properties.IsProcessed = true;
42✔
258

259
        bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?;
42✔
260
        if (isTemporal != true || _options.Data.Mapping.Temporal)
42!
261
            return;
42✔
262

263
        // add temporal period columns
264
        var temporalStartColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodStartColumnName] as string
×
NEW
265
            ?? tableSchema[SqlServerAnnotationNames.TemporalPeriodStartPropertyName] as string;
×
266

NEW
267
        var temporalEndColumn = tableSchema[SqlServerAnnotationNames.TemporalPeriodEndColumnName] as string
×
NEW
268
            ?? tableSchema[SqlServerAnnotationNames.TemporalPeriodEndPropertyName] as string;
×
269

NEW
270
        if (temporalStartColumn.IsNullOrEmpty() || temporalEndColumn.IsNullOrEmpty())
×
NEW
271
            return;
×
272

273
        var temporalStart = entity.Properties.ByColumn(temporalStartColumn);
×
274

275
        if (temporalStart == null)
×
276
        {
277
            temporalStart = new Property { Entity = entity, ColumnName = temporalStartColumn };
×
278
            entity.Properties.Add(temporalStart);
×
279
        }
280

281
        temporalStart.PropertyName = ToPropertyName(entity.EntityClass, temporalStartColumn);
×
282
        temporalStart.ValueGenerated = ValueGenerated.OnAddOrUpdate;
×
283
        temporalStart.StoreType = "datetime2";
×
284
        temporalStart.DataType = DbType.DateTime2;
×
285
        temporalStart.SystemType = typeof(DateTime);
×
286

287
        temporalStart.IsProcessed = true;
×
288

UNCOV
289
        var temporalEnd = entity.Properties.ByColumn(temporalEndColumn);
×
290

291
        if (temporalEnd == null)
×
292
        {
293
            temporalEnd = new Property { Entity = entity, ColumnName = temporalEndColumn };
×
294
            entity.Properties.Add(temporalEnd);
×
295
        }
296

297
        temporalEnd.PropertyName = ToPropertyName(entity.EntityClass, temporalEndColumn);
×
298
        temporalEnd.ValueGenerated = ValueGenerated.OnAddOrUpdate;
×
299
        temporalEnd.StoreType = "datetime2";
×
300
        temporalEnd.DataType = DbType.DateTime2;
×
301
        temporalEnd.SystemType = typeof(DateTime);
×
302

303
        temporalEnd.IsProcessed = true;
×
304
    }
×
305

306

307
    private void CreateRelationships(EntityContext entityContext, Entity entity, DatabaseTable tableSchema)
308
    {
309
        foreach (var foreignKey in tableSchema.ForeignKeys)
126✔
310
        {
311
            // skip relationship if principal table is ignored
312
            if (IsIgnored(foreignKey.PrincipalTable, _options.Database.Exclude))
21!
313
            {
314
                _logger.LogDebug("  Skipping Relationship : {name}", foreignKey.Name);
×
315
                continue;
×
316
            }
317

318
            CreateRelationship(entityContext, entity, foreignKey);
21✔
319
        }
320

321
        entity.Relationships.IsProcessed = true;
42✔
322
    }
42✔
323

324
    private void CreateRelationship(EntityContext entityContext, Entity foreignEntity, DatabaseForeignKey tableKeySchema)
325
    {
326
        Entity primaryEntity = GetEntity(entityContext, tableKeySchema.PrincipalTable, false, false);
21✔
327

328
        var primaryName = primaryEntity.EntityClass;
21✔
329
        var foreignName = foreignEntity.EntityClass;
21✔
330

331
        var foreignMembers = GetKeyMembers(foreignEntity, tableKeySchema.Columns, tableKeySchema.Name);
21✔
332
        bool foreignMembersRequired = foreignMembers.Any(c => c.IsRequired);
42✔
333

334
        var primaryMembers = GetKeyMembers(primaryEntity, tableKeySchema.PrincipalColumns, tableKeySchema.Name);
21✔
335
        bool primaryMembersRequired = primaryMembers.Any(c => c.IsRequired);
42✔
336

337
        // skip invalid fkeys
338
        if (foreignMembers.Count == 0 || primaryMembers.Count == 0)
21!
339
            return;
×
340

341
        var relationshipName = tableKeySchema.Name;
21✔
342

343
        // ensure relationship name for sync support
344
        if (relationshipName.IsNullOrEmpty())
21!
345
            relationshipName = $"FK_{foreignName}_{primaryName}_{primaryMembers.Select(p => p.PropertyName).ToDelimitedString("_")}";
×
346

347
        relationshipName = _namer.UniqueRelationshipName(relationshipName);
21✔
348

349
        var foreignRelationship = foreignEntity.Relationships
21✔
350
            .FirstOrDefault(r => r.RelationshipName == relationshipName && r.IsForeignKey);
33!
351

352
        if (foreignRelationship == null)
21✔
353
        {
354
            foreignRelationship = new Relationship { RelationshipName = relationshipName };
21✔
355
            foreignEntity.Relationships.Add(foreignRelationship);
21✔
356
        }
357
        foreignRelationship.IsMapped = true;
21✔
358
        foreignRelationship.IsForeignKey = true;
21✔
359
        foreignRelationship.Cardinality = foreignMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
21✔
360

361
        foreignRelationship.PrimaryEntity = primaryEntity;
21✔
362
        foreignRelationship.PrimaryProperties = [.. primaryMembers];
21✔
363

364
        foreignRelationship.Entity = foreignEntity;
21✔
365
        foreignRelationship.Properties = [.. foreignMembers];
21✔
366

367
        string prefix = GetMemberPrefix(foreignRelationship, primaryName, foreignName);
21✔
368

369
        string foreignPropertyName = ToPropertyName(foreignEntity.EntityClass, prefix + primaryName);
21✔
370
        foreignPropertyName = _namer.UniqueName(foreignEntity.EntityClass, foreignPropertyName);
21✔
371
        foreignRelationship.PropertyName = foreignPropertyName;
21✔
372

373
        // add reverse
374
        var primaryRelationship = primaryEntity.Relationships
21✔
375
            .FirstOrDefault(r => r.RelationshipName == relationshipName && !r.IsForeignKey);
39!
376

377
        if (primaryRelationship == null)
21✔
378
        {
379
            primaryRelationship = new Relationship { RelationshipName = relationshipName };
21✔
380
            primaryEntity.Relationships.Add(primaryRelationship);
21✔
381
        }
382

383
        primaryRelationship.IsMapped = false;
21✔
384
        primaryRelationship.IsForeignKey = false;
21✔
385

386
        primaryRelationship.PrimaryEntity = foreignEntity;
21✔
387
        primaryRelationship.PrimaryProperties = [.. foreignMembers];
21✔
388

389
        primaryRelationship.Entity = primaryEntity;
21✔
390
        primaryRelationship.Properties = [.. primaryMembers];
21✔
391

392
        bool isOneToOne = IsOneToOne(tableKeySchema, foreignRelationship);
21✔
393
        if (isOneToOne)
21✔
394
            primaryRelationship.Cardinality = primaryMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
3!
395
        else
396
            primaryRelationship.Cardinality = Cardinality.Many;
18✔
397

398
        string primaryPropertyName = prefix + foreignName;
21✔
399
        if (!isOneToOne)
21✔
400
            primaryPropertyName = RelationshipName(primaryPropertyName);
18✔
401

402
        primaryPropertyName = ToPropertyName(primaryEntity.EntityClass, primaryPropertyName);
21✔
403
        primaryPropertyName = _namer.UniqueName(primaryEntity.EntityClass, primaryPropertyName);
21✔
404

405
        primaryRelationship.PropertyName = primaryPropertyName;
21✔
406

407
        foreignRelationship.PrimaryPropertyName = primaryRelationship.PropertyName;
21✔
408
        foreignRelationship.PrimaryCardinality = primaryRelationship.Cardinality;
21✔
409

410
        primaryRelationship.PrimaryPropertyName = foreignRelationship.PropertyName;
21✔
411
        primaryRelationship.PrimaryCardinality = foreignRelationship.Cardinality;
21✔
412

413
        foreignRelationship.IsProcessed = true;
21✔
414
        primaryRelationship.IsProcessed = true;
21✔
415
    }
21✔
416

417

418
    private static void CreateMethods(Entity entity, DatabaseTable tableSchema)
419
    {
420
        if (tableSchema.PrimaryKey != null)
42✔
421
        {
422
            var method = GetMethodFromColumns(entity, tableSchema.PrimaryKey.Columns);
33✔
423
            if (method != null)
33✔
424
            {
425
                method.IsKey = true;
33✔
426
                method.SourceName = tableSchema.PrimaryKey.Name;
33✔
427

428
                if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
33✔
429
                    entity.Methods.Add(method);
33✔
430
            }
431
        }
432

433
        GetIndexMethods(entity, tableSchema);
42✔
434
        GetForeignKeyMethods(entity, tableSchema);
42✔
435

436
        entity.Methods.IsProcessed = true;
42✔
437
    }
42✔
438

439
    private static void GetForeignKeyMethods(Entity entity, DatabaseTable table)
440
    {
441
        var columns = new List<DatabaseColumn>();
42✔
442

443
        foreach (var column in table.ForeignKeys.SelectMany(c => c.Columns))
147✔
444
        {
445
            columns.Add(column);
21✔
446

447
            var method = GetMethodFromColumns(entity, columns);
21✔
448
            if (method != null && entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
69✔
449
                entity.Methods.Add(method);
6✔
450

451
            columns.Clear();
21✔
452
        }
453
    }
42✔
454

455
    private static void GetIndexMethods(Entity entity, DatabaseTable table)
456
    {
457
        foreach (var index in table.Indexes)
126✔
458
        {
459
            var method = GetMethodFromColumns(entity, index.Columns);
21✔
460
            if (method == null)
21✔
461
                continue;
462

463
            method.SourceName = index.Name;
21✔
464
            method.IsUnique = index.IsUnique;
21✔
465
            method.IsIndex = true;
21✔
466

467
            if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
54✔
468
                entity.Methods.Add(method);
21✔
469
        }
470
    }
42✔
471

472
    private static Method? GetMethodFromColumns(Entity entity, IEnumerable<DatabaseColumn> columns)
473
    {
474
        var method = new Method { Entity = entity };
75✔
475
        var methodName = new StringBuilder();
75✔
476

477
        foreach (var column in columns)
306✔
478
        {
479
            var property = entity.Properties.ByColumn(column.Name);
78✔
480
            if (property == null)
78✔
481
                continue;
482

483
            method.Properties.Add(property);
78✔
484
            methodName.Append(property.PropertyName);
78✔
485
        }
486

487
        if (method.Properties.Count == 0)
75!
488
            return null;
×
489

490
        method.NameSuffix = methodName.ToString();
75✔
491
        return method;
75✔
492
    }
493

494

495
    private void GetModels(Entity? entity)
496
    {
497
        if (entity == null || entity.Models.IsProcessed)
42!
498
            return;
×
499

500
        _options.Variables.Set(entity);
42✔
501

502
        if (_options.Model.Read.Generate)
42✔
503
            CreateModel(entity, _options.Model.Read, ModelType.Read);
2✔
504
        if (_options.Model.Create.Generate)
42✔
505
            CreateModel(entity, _options.Model.Create, ModelType.Create);
2✔
506
        if (_options.Model.Update.Generate)
42✔
507
            CreateModel(entity, _options.Model.Update, ModelType.Update);
2✔
508

509
        if (entity.Models.Count > 0)
42✔
510
        {
511
            var mapperNamespace = _options.Model.Mapper.Namespace ?? "Data.Mapper";
2!
512

513
            var mapperClass = ToLegalName(_options.Model.Mapper.Name);
2✔
514
            mapperClass = _namer.UniqueModelName(mapperNamespace, mapperClass);
2✔
515

516
            entity.MapperClass = mapperClass;
2✔
517
            entity.MapperNamespace = mapperNamespace;
2✔
518
            entity.MapperBaseClass = _options.Model.Mapper.BaseClass;
2✔
519
        }
520

521
        _options.Variables.Remove(entity);
42✔
522

523
        entity.Models.IsProcessed = true;
42✔
524
    }
42✔
525

526
    private void CreateModel<TOption>(Entity entity, TOption options, ModelType modelType)
527
        where TOption : ModelOptionsBase
528
    {
529
        if (IsIgnored(entity, options, _options.Model.Shared))
6!
530
            return;
×
531

532
        var modelNamespace = options.Namespace.HasValue()
6!
533
            ? options.Namespace
6✔
534
            : _options.Model.Shared.Namespace;
6✔
535

536
        modelNamespace ??= "Data.Models";
6!
537

538
        var modelClass = ToLegalName(options.Name);
6✔
539
        modelClass = _namer.UniqueModelName(modelNamespace, modelClass);
6✔
540

541
        var model = new Model
6✔
542
        {
6✔
543
            Entity = entity,
6✔
544
            ModelType = modelType,
6✔
545
            ModelBaseClass = options.BaseClass,
6✔
546
            ModelNamespace = modelNamespace,
6✔
547
            ModelClass = modelClass,
6✔
548
            ModelAttributes = options.Attributes,
6✔
549
        };
6✔
550

551
        foreach (var property in entity.Properties)
36✔
552
        {
553
            if (IsIgnored(property, options, _options.Model.Shared))
12✔
554
                continue;
555

556
            model.Properties.Add(property);
12✔
557
        }
558

559
        _options.Variables.Set(model);
6✔
560

561
        var validatorNamespace = _options.Model.Validator.Namespace ?? "Data.Validation";
6!
562
        var validatorClass = ToLegalName(_options.Model.Validator.Name);
6✔
563
        validatorClass = _namer.UniqueModelName(validatorNamespace, validatorClass);
6✔
564

565
        model.ValidatorBaseClass = _options.Model.Validator.BaseClass;
6✔
566
        model.ValidatorClass = validatorClass;
6✔
567
        model.ValidatorNamespace = validatorNamespace;
6✔
568

569
        entity.Models.Add(model);
6✔
570

571
        _options.Variables.Remove(model);
6✔
572
    }
6✔
573

574

575
    private List<Property> GetKeyMembers(Entity entity, IEnumerable<DatabaseColumn> members, string? relationshipName)
576
    {
577
        var keyMembers = new List<Property>();
42✔
578

579
        foreach (var member in members)
168✔
580
        {
581
            var property = entity.Properties.ByColumn(member.Name);
42✔
582

583
            if (property == null)
42!
584
                _logger.LogWarning("Could not find column {columnName} for relationship {relationshipName}.", member.Name, relationshipName);
×
585
            else
586
                keyMembers.Add(property);
42✔
587
        }
588

589
        return keyMembers;
42✔
590
    }
591

592
    private static string GetMemberPrefix(Relationship relationship, string primaryClass, string foreignClass)
593
    {
594
        string thisKey = relationship.Properties
21!
595
            .Select(p => p.PropertyName)
21✔
596
            .FirstOrDefault() ?? string.Empty;
21✔
597

598
        string otherKey = relationship.PrimaryProperties
21!
599
            .Select(p => p.PropertyName)
21✔
600
            .FirstOrDefault() ?? string.Empty;
21✔
601

602
        bool isSameName = thisKey.Equals(otherKey, StringComparison.OrdinalIgnoreCase);
21✔
603
        isSameName = (isSameName || thisKey.Equals(primaryClass + otherKey, StringComparison.OrdinalIgnoreCase));
21!
604

605
        string prefix = string.Empty;
21✔
606
        if (isSameName)
21✔
607
            return prefix;
18✔
608

609
        prefix = thisKey.Replace(otherKey, "");
3✔
610
        prefix = prefix.Replace(primaryClass, "");
3✔
611
        prefix = prefix.Replace(foreignClass, "");
3✔
612
        prefix = IdSuffixRegex().Replace(prefix, "");
3✔
613
        prefix = DigitPrefixRegex().Replace(prefix, "");
3✔
614

615
        return prefix;
3✔
616
    }
617

618
    private static bool IsOneToOne(DatabaseForeignKey tableKeySchema, Relationship foreignRelationship)
619
    {
620
        var foreignColumn = foreignRelationship.Properties
21✔
621
            .Select(p => p.ColumnName)
21✔
622
            .FirstOrDefault();
21✔
623

624
        return tableKeySchema.PrincipalTable.PrimaryKey != null
21✔
625
            && tableKeySchema.Table.PrimaryKey != null
21✔
626
            && tableKeySchema.Table.PrimaryKey.Columns.Count == 1
21✔
627
            && tableKeySchema.Table.PrimaryKey.Columns.Any(c => c.Name == foreignColumn);
36✔
628

629
        // if f.key is unique
630
        //return tableKeySchema.ForeignKeyMemberColumns.All(column => column.IsUnique);
631
    }
632

633

634
    private string RelationshipName(string name)
635
    {
636
        var naming = _options.Data.Entity.RelationshipNaming;
18✔
637
        if (naming == RelationshipNaming.Preserve)
18!
638
            return name;
×
639

640
        if (naming == RelationshipNaming.Suffix)
18!
641
            return name + "List";
×
642

643
        return name.Pluralize(false);
18✔
644
    }
645

646
    private string ContextName(string name)
647
    {
648
        var naming = _options.Data.Context.PropertyNaming;
42✔
649
        if (naming == ContextNaming.Preserve)
42!
650
            return name;
×
651

652
        if (naming == ContextNaming.Suffix)
42!
653
            return name + "DataSet";
×
654

655
        return name.Pluralize(false);
42✔
656
    }
657

658
    private string EntityName(string name)
659
    {
660
        var tableNaming = _options.Database.TableNaming;
42✔
661
        var entityNaming = _options.Data.Entity.EntityNaming;
42✔
662

663
        if (tableNaming != TableNaming.Plural && entityNaming == EntityNaming.Plural)
42!
664
            name = name.Pluralize(false);
×
665
        else if (tableNaming != TableNaming.Singular && entityNaming == EntityNaming.Singular)
42!
666
            name = name.Singularize(false);
×
667

668
        var rename = name;
42✔
669
        foreach (var selection in _options.Data.Entity.Renaming.Entities.Where(p => p.Expression.HasValue()))
84!
670
        {
NEW
671
            if (selection.Expression.IsNullOrEmpty())
×
672
                continue;
673

UNCOV
674
            rename = Regex.Replace(rename, selection.Expression, string.Empty);
×
675
        }
676

677
        // make sure regex doesn't remove everything
678
        return rename.HasValue() ? rename : name;
42!
679
    }
680

681

682
    private string ToClassName(string tableName, string? tableSchema)
683
    {
684
        tableName = EntityName(tableName);
42✔
685
        var className = tableName;
42✔
686

687
        if (_options.Data.Entity.PrefixWithSchemaName && tableSchema != null)
42✔
688
            className = $"{tableSchema}{tableName}";
2✔
689

690
        return ToLegalName(className);
42✔
691
    }
692

693
    private string ToPropertyName(string className, string name)
694
    {
695
        string propertyName = ToLegalName(name);
416✔
696
        if (className.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
416!
697
            propertyName += "Member";
×
698

699
        return propertyName;
416✔
700
    }
701

702
    private static string ToLegalName(string? name)
703
    {
704
        if (string.IsNullOrWhiteSpace(name))
567!
705
            return string.Empty;
×
706

707
        string legalName = name;
567✔
708

709
        // remove invalid leading
710
        var expression = LeadingNonAlphaRegex();
567✔
711
        if (expression.IsMatch(name))
567✔
712
            legalName = expression.Replace(legalName, string.Empty);
1✔
713

714
        // prefix with column when all characters removed
715
        if (legalName.IsNullOrWhiteSpace())
567✔
716
            legalName = "Number" + name;
1✔
717

718
        return legalName.ToPascalCase();
567✔
719
    }
720

721

722
    private static bool IsIgnored(DatabaseTable table, IEnumerable<MatchOptions> exclude)
723
    {
724
        var name = $"{table.Schema}.{table.Name}";
65✔
725
        var includeExpressions = Enumerable.Empty<MatchOptions>();
65✔
726
        var excludeExpressions = exclude ?? [];
65!
727

728
        return IsIgnored(name, excludeExpressions, includeExpressions);
65✔
729
    }
730

731
    private static bool IsIgnored<TOption>(Property property, TOption options, SharedModelOptions sharedOptions)
732
        where TOption : ModelOptionsBase
733
    {
734
        var name = $"{property.Entity.EntityClass}.{property.PropertyName}";
12✔
735

736
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Properties ?? []);
12!
737
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Properties ?? []);
12!
738

739
        var includeProperties = options?.Include?.Properties ?? [];
12!
740
        foreach (var expression in includeProperties)
24!
741
            includeExpressions.Add(expression);
×
742

743
        var excludeProperties = options?.Exclude?.Properties ?? [];
12!
744
        foreach (var expression in excludeProperties)
24!
745
            excludeExpressions.Add(expression);
×
746

747
        return IsIgnored(name, excludeExpressions, includeExpressions);
12✔
748
    }
749

750
    private static bool IsIgnored<TOption>(Entity entity, TOption options, SharedModelOptions sharedOptions)
751
        where TOption : ModelOptionsBase
752
    {
753
        var name = entity.EntityClass;
6✔
754

755
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Entities ?? []);
6!
756
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Entities ?? []);
6!
757

758
        var includeEntities = options?.Include?.Entities ?? [];
6!
759
        foreach (var expression in includeEntities)
12!
760
            includeExpressions.Add(expression);
×
761

762
        var excludeEntities = options?.Exclude?.Entities ?? [];
6!
763
        foreach (var expression in excludeEntities)
12!
764
            excludeExpressions.Add(expression);
×
765

766
        return IsIgnored(name, excludeExpressions, includeExpressions);
6✔
767
    }
768

769
    private static bool IsIgnored(string name, IEnumerable<MatchOptions> excludeExpressions, IEnumerable<MatchOptions> includeExpressions)
770
    {
771
        foreach (var expression in includeExpressions)
166!
772
        {
773
            if (expression.IsMatch(name))
×
774
                return false;
×
775
        }
776

777
        foreach (var expression in excludeExpressions)
174✔
778
        {
779
            if (expression.IsMatch(name))
5✔
780
                return true;
2✔
781
        }
782

783
        return false;
81✔
784
    }
2✔
785

786

787
    [GeneratedRegex(@"^[^a-zA-Z_]+")]
788
    private static partial Regex LeadingNonAlphaRegex();
789

790
    [GeneratedRegex(@"(_ID|_id|_Id|\.ID|\.id|\.Id|ID|Id)$")]
791
    private static partial Regex IdSuffixRegex();
792

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

© 2025 Coveralls, Inc