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

loresoft / EntityFrameworkCore.Generator / 15098211199

18 May 2025 05:11PM UTC coverage: 54.862% (-0.07%) from 54.934%
15098211199

push

github

pwelter34
fix tests

620 of 1291 branches covered (48.02%)

Branch coverage included in aggregate %.

1840 of 3193 relevant lines covered (57.63%)

61.91 hits per line

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

75.38
/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)
10✔
33
    {
34
        _logger = logger.CreateLogger<ModelGenerator>();
10✔
35
        _namer = new UniqueNamer();
10✔
36
    }
10✔
37

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

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

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

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

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

55

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

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

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

66
        var tables = databaseModel.Tables;
10✔
67

68
        foreach (var table in tables)
86✔
69
        {
70
            if (IsIgnored(table, _options.Database.Exclude.Tables))
33✔
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);
31✔
77

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

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

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

88
        return entityContext;
10✔
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)
45✔
95
            ?? CreateEntity(entityContext, tableSchema);
45✔
96

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

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

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

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

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

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

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

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

128

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

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

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

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

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

145
        entity.ContextProperty = contextName;
31✔
146

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

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

168
        return entity;
31✔
169
    }
170

171

172
    private void CreateProperties(Entity entity, DatabaseTable tableSchema)
173
    {
174
        var columns = tableSchema.Columns;
31✔
175
        foreach (var column in columns)
516✔
176
        {
177
            var table = column.Table;
227✔
178
            if (IsIgnored(column, _options.Database.Exclude.Columns))
227!
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;
227!
185
            if (mapping == null)
227!
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);
227✔
192

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

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

206
            foreach (var selection in _options.Data.Entity.Renaming.Properties.Where(p => p.Expression.HasValue()))
454!
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())
227!
216
                propertyName = name;
×
217

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

220
            property.PropertyName = propertyName;
227✔
221

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

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

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

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

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

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

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

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

247
            // overwrite row version type
248
            if (property.IsRowVersion == true && _options.Data.Mapping.RowVersion != RowVersionMapping.ByteArray && property.SystemType == typeof(byte[]))
227!
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;
227✔
260
        }
261

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

264
        bool? isTemporal = tableSchema[SqlServerAnnotationNames.IsTemporal] as bool?;
31✔
265
        if (isTemporal != true || _options.Data.Mapping.Temporal)
31!
266
            return;
31✔
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)
90✔
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
            CreateRelationship(entityContext, entity, foreignKey);
14✔
324
        }
325

326
        entity.Relationships.IsProcessed = true;
31✔
327
    }
31✔
328

329
    private void CreateRelationship(EntityContext entityContext, Entity foreignEntity, DatabaseForeignKey tableKeySchema)
330
    {
331
        Entity primaryEntity = GetEntity(entityContext, tableKeySchema.PrincipalTable, false, false);
14✔
332

333
        var primaryName = primaryEntity.EntityClass;
14✔
334
        var foreignName = foreignEntity.EntityClass;
14✔
335

336
        var foreignMembers = GetKeyMembers(foreignEntity, tableKeySchema.Columns, tableKeySchema.Name);
14✔
337
        bool foreignMembersRequired = foreignMembers.Any(c => c.IsRequired);
28✔
338

339
        var primaryMembers = GetKeyMembers(primaryEntity, tableKeySchema.PrincipalColumns, tableKeySchema.Name);
14✔
340
        bool primaryMembersRequired = primaryMembers.Any(c => c.IsRequired);
28✔
341

342
        // skip invalid fkeys
343
        if (foreignMembers.Count == 0 || primaryMembers.Count == 0)
14!
344
            return;
×
345

346
        var relationshipName = tableKeySchema.Name;
14✔
347

348
        // ensure relationship name for sync support
349
        if (relationshipName.IsNullOrEmpty())
14!
350
            relationshipName = $"FK_{foreignName}_{primaryName}_{primaryMembers.Select(p => p.PropertyName).ToDelimitedString("_")}";
×
351

352
        relationshipName = _namer.UniqueRelationshipName(relationshipName);
14✔
353

354
        var foreignRelationship = foreignEntity.Relationships
14✔
355
            .FirstOrDefault(r => r.RelationshipName == relationshipName && r.IsForeignKey);
22!
356

357
        if (foreignRelationship == null)
14✔
358
        {
359
            foreignRelationship = new Relationship { RelationshipName = relationshipName };
14✔
360
            foreignEntity.Relationships.Add(foreignRelationship);
14✔
361
        }
362
        foreignRelationship.IsMapped = true;
14✔
363
        foreignRelationship.IsForeignKey = true;
14✔
364
        foreignRelationship.Cardinality = foreignMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
14✔
365

366
        foreignRelationship.PrimaryEntity = primaryEntity;
14✔
367
        foreignRelationship.PrimaryProperties = [.. primaryMembers];
14✔
368

369
        foreignRelationship.Entity = foreignEntity;
14✔
370
        foreignRelationship.Properties = [.. foreignMembers];
14✔
371

372
        string prefix = GetMemberPrefix(foreignRelationship, primaryName, foreignName);
14✔
373

374
        string foreignPropertyName = ToPropertyName(foreignEntity.EntityClass, prefix + primaryName);
14✔
375
        foreignPropertyName = _namer.UniqueName(foreignEntity.EntityClass, foreignPropertyName);
14✔
376
        foreignRelationship.PropertyName = foreignPropertyName;
14✔
377

378
        // add reverse
379
        var primaryRelationship = primaryEntity.Relationships
14✔
380
            .FirstOrDefault(r => r.RelationshipName == relationshipName && !r.IsForeignKey);
26!
381

382
        if (primaryRelationship == null)
14✔
383
        {
384
            primaryRelationship = new Relationship { RelationshipName = relationshipName };
14✔
385
            primaryEntity.Relationships.Add(primaryRelationship);
14✔
386
        }
387

388
        primaryRelationship.IsMapped = false;
14✔
389
        primaryRelationship.IsForeignKey = false;
14✔
390

391
        primaryRelationship.PrimaryEntity = foreignEntity;
14✔
392
        primaryRelationship.PrimaryProperties = [.. foreignMembers];
14✔
393

394
        primaryRelationship.Entity = primaryEntity;
14✔
395
        primaryRelationship.Properties = [.. primaryMembers];
14✔
396

397
        bool isOneToOne = IsOneToOne(tableKeySchema, foreignRelationship);
14✔
398
        if (isOneToOne)
14✔
399
            primaryRelationship.Cardinality = primaryMembersRequired ? Cardinality.One : Cardinality.ZeroOrOne;
2!
400
        else
401
            primaryRelationship.Cardinality = Cardinality.Many;
12✔
402

403
        string primaryPropertyName = prefix + foreignName;
14✔
404
        if (!isOneToOne)
14✔
405
            primaryPropertyName = RelationshipName(primaryPropertyName);
12✔
406

407
        primaryPropertyName = ToPropertyName(primaryEntity.EntityClass, primaryPropertyName);
14✔
408
        primaryPropertyName = _namer.UniqueName(primaryEntity.EntityClass, primaryPropertyName);
14✔
409

410
        primaryRelationship.PropertyName = primaryPropertyName;
14✔
411

412
        foreignRelationship.PrimaryPropertyName = primaryRelationship.PropertyName;
14✔
413
        foreignRelationship.PrimaryCardinality = primaryRelationship.Cardinality;
14✔
414

415
        primaryRelationship.PrimaryPropertyName = foreignRelationship.PropertyName;
14✔
416
        primaryRelationship.PrimaryCardinality = foreignRelationship.Cardinality;
14✔
417

418
        foreignRelationship.IsProcessed = true;
14✔
419
        primaryRelationship.IsProcessed = true;
14✔
420
    }
14✔
421

422

423
    private static void CreateMethods(Entity entity, DatabaseTable tableSchema)
424
    {
425
        if (tableSchema.PrimaryKey != null)
31✔
426
        {
427
            var method = GetMethodFromColumns(entity, tableSchema.PrimaryKey.Columns);
22✔
428
            if (method != null)
22✔
429
            {
430
                method.IsKey = true;
22✔
431
                method.SourceName = tableSchema.PrimaryKey.Name;
22✔
432

433
                if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
22✔
434
                    entity.Methods.Add(method);
22✔
435
            }
436
        }
437

438
        GetIndexMethods(entity, tableSchema);
31✔
439
        GetForeignKeyMethods(entity, tableSchema);
31✔
440

441
        entity.Methods.IsProcessed = true;
31✔
442
    }
31✔
443

444
    private static void GetForeignKeyMethods(Entity entity, DatabaseTable table)
445
    {
446
        var columns = new List<DatabaseColumn>();
31✔
447

448
        foreach (var column in table.ForeignKeys.SelectMany(c => c.Columns))
104✔
449
        {
450
            columns.Add(column);
14✔
451

452
            var method = GetMethodFromColumns(entity, columns);
14✔
453
            if (method != null && entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
46✔
454
                entity.Methods.Add(method);
4✔
455

456
            columns.Clear();
14✔
457
        }
458
    }
31✔
459

460
    private static void GetIndexMethods(Entity entity, DatabaseTable table)
461
    {
462
        foreach (var index in table.Indexes)
90✔
463
        {
464
            var method = GetMethodFromColumns(entity, index.Columns);
14✔
465
            if (method == null)
14✔
466
                continue;
467

468
            method.SourceName = index.Name;
14✔
469
            method.IsUnique = index.IsUnique;
14✔
470
            method.IsIndex = true;
14✔
471

472
            if (entity.Methods.All(m => m.NameSuffix != method.NameSuffix))
36✔
473
                entity.Methods.Add(method);
14✔
474
        }
475
    }
31✔
476

477
    private static Method? GetMethodFromColumns(Entity entity, IEnumerable<DatabaseColumn> columns)
478
    {
479
        var method = new Method { Entity = entity };
50✔
480
        var methodName = new StringBuilder();
50✔
481

482
        foreach (var column in columns)
204✔
483
        {
484
            var property = entity.Properties.ByColumn(column.Name);
52✔
485
            if (property == null)
52✔
486
                continue;
487

488
            method.Properties.Add(property);
52✔
489
            methodName.Append(property.PropertyName);
52✔
490
        }
491

492
        if (method.Properties.Count == 0)
50!
493
            return null;
×
494

495
        method.NameSuffix = methodName.ToString();
50✔
496
        return method;
50✔
497
    }
498

499

500
    private void GetModels(Entity? entity)
501
    {
502
        if (entity == null || entity.Models.IsProcessed)
31!
503
            return;
×
504

505
        _options.Variables.Set(entity);
31✔
506

507
        if (_options.Model.Read.Generate)
31✔
508
            CreateModel(entity, _options.Model.Read, ModelType.Read);
2✔
509
        if (_options.Model.Create.Generate)
31✔
510
            CreateModel(entity, _options.Model.Create, ModelType.Create);
2✔
511
        if (_options.Model.Update.Generate)
31✔
512
            CreateModel(entity, _options.Model.Update, ModelType.Update);
2✔
513

514
        if (entity.Models.Count > 0)
31✔
515
        {
516
            var mapperNamespace = _options.Model.Mapper.Namespace ?? "Data.Mapper";
2!
517

518
            var mapperClass = ToLegalName(_options.Model.Mapper.Name);
2✔
519
            mapperClass = _namer.UniqueModelName(mapperNamespace, mapperClass);
2✔
520

521
            entity.MapperClass = mapperClass;
2✔
522
            entity.MapperNamespace = mapperNamespace;
2✔
523
            entity.MapperBaseClass = _options.Model.Mapper.BaseClass;
2✔
524
        }
525

526
        _options.Variables.Remove(entity);
31✔
527

528
        entity.Models.IsProcessed = true;
31✔
529
    }
31✔
530

531
    private void CreateModel<TOption>(Entity entity, TOption options, ModelType modelType)
532
        where TOption : ModelOptionsBase
533
    {
534
        if (IsIgnored(entity, options, _options.Model.Shared))
6!
535
            return;
×
536

537
        var modelNamespace = options.Namespace.HasValue()
6!
538
            ? options.Namespace
6✔
539
            : _options.Model.Shared.Namespace;
6✔
540

541
        modelNamespace ??= "Data.Models";
6!
542

543
        var modelClass = ToLegalName(options.Name);
6✔
544
        modelClass = _namer.UniqueModelName(modelNamespace, modelClass);
6✔
545

546
        var model = new Model
6✔
547
        {
6✔
548
            Entity = entity,
6✔
549
            ModelType = modelType,
6✔
550
            ModelBaseClass = options.BaseClass,
6✔
551
            ModelNamespace = modelNamespace,
6✔
552
            ModelClass = modelClass,
6✔
553
            ModelAttributes = options.Attributes,
6✔
554
        };
6✔
555

556
        foreach (var property in entity.Properties)
36✔
557
        {
558
            if (IsIgnored(property, options, _options.Model.Shared))
12✔
559
                continue;
560

561
            model.Properties.Add(property);
12✔
562
        }
563

564
        _options.Variables.Set(model);
6✔
565

566
        var validatorNamespace = _options.Model.Validator.Namespace ?? "Data.Validation";
6!
567
        var validatorClass = ToLegalName(_options.Model.Validator.Name);
6✔
568
        validatorClass = _namer.UniqueModelName(validatorNamespace, validatorClass);
6✔
569

570
        model.ValidatorBaseClass = _options.Model.Validator.BaseClass;
6✔
571
        model.ValidatorClass = validatorClass;
6✔
572
        model.ValidatorNamespace = validatorNamespace;
6✔
573

574
        entity.Models.Add(model);
6✔
575

576
        _options.Variables.Remove(model);
6✔
577
    }
6✔
578

579

580
    private List<Property> GetKeyMembers(Entity entity, IEnumerable<DatabaseColumn> members, string? relationshipName)
581
    {
582
        var keyMembers = new List<Property>();
28✔
583

584
        foreach (var member in members)
112✔
585
        {
586
            var property = entity.Properties.ByColumn(member.Name);
28✔
587

588
            if (property == null)
28!
589
                _logger.LogWarning("Could not find column {columnName} for relationship {relationshipName}.", member.Name, relationshipName);
×
590
            else
591
                keyMembers.Add(property);
28✔
592
        }
593

594
        return keyMembers;
28✔
595
    }
596

597
    private static string GetMemberPrefix(Relationship relationship, string primaryClass, string foreignClass)
598
    {
599
        string thisKey = relationship.Properties
14!
600
            .Select(p => p.PropertyName)
14✔
601
            .FirstOrDefault() ?? string.Empty;
14✔
602

603
        string otherKey = relationship.PrimaryProperties
14!
604
            .Select(p => p.PropertyName)
14✔
605
            .FirstOrDefault() ?? string.Empty;
14✔
606

607
        bool isSameName = thisKey.Equals(otherKey, StringComparison.OrdinalIgnoreCase);
14✔
608
        isSameName = (isSameName || thisKey.Equals(primaryClass + otherKey, StringComparison.OrdinalIgnoreCase));
14!
609

610
        string prefix = string.Empty;
14✔
611
        if (isSameName)
14✔
612
            return prefix;
12✔
613

614
        prefix = thisKey.Replace(otherKey, "");
2✔
615
        prefix = prefix.Replace(primaryClass, "");
2✔
616
        prefix = prefix.Replace(foreignClass, "");
2✔
617
        prefix = IdSuffixRegex().Replace(prefix, "");
2✔
618
        prefix = DigitPrefixRegex().Replace(prefix, "");
2✔
619

620
        return prefix;
2✔
621
    }
622

623
    private static bool IsOneToOne(DatabaseForeignKey tableKeySchema, Relationship foreignRelationship)
624
    {
625
        var foreignColumn = foreignRelationship.Properties
14✔
626
            .Select(p => p.ColumnName)
14✔
627
            .FirstOrDefault();
14✔
628

629
        return tableKeySchema.PrincipalTable.PrimaryKey != null
14✔
630
            && tableKeySchema.Table.PrimaryKey != null
14✔
631
            && tableKeySchema.Table.PrimaryKey.Columns.Count == 1
14✔
632
            && tableKeySchema.Table.PrimaryKey.Columns.Any(c => c.Name == foreignColumn);
24✔
633

634
        // if f.key is unique
635
        //return tableKeySchema.ForeignKeyMemberColumns.All(column => column.IsUnique);
636
    }
637

638

639
    private string RelationshipName(string name)
640
    {
641
        var naming = _options.Data.Entity.RelationshipNaming;
12✔
642
        if (naming == RelationshipNaming.Preserve)
12!
643
            return name;
×
644

645
        if (naming == RelationshipNaming.Suffix)
12!
646
            return name + "List";
×
647

648
        return name.Pluralize(false);
12✔
649
    }
650

651
    private string ContextName(string name)
652
    {
653
        var naming = _options.Data.Context.PropertyNaming;
31✔
654
        if (naming == ContextNaming.Preserve)
31!
655
            return name;
×
656

657
        if (naming == ContextNaming.Suffix)
31!
658
            return name + "DataSet";
×
659

660
        return name.Pluralize(false);
31✔
661
    }
662

663
    private string EntityName(string name)
664
    {
665
        var tableNaming = _options.Database.TableNaming;
31✔
666
        var entityNaming = _options.Data.Entity.EntityNaming;
31✔
667

668
        if (tableNaming != TableNaming.Plural && entityNaming == EntityNaming.Plural)
31!
669
            name = name.Pluralize(false);
×
670
        else if (tableNaming != TableNaming.Singular && entityNaming == EntityNaming.Singular)
31!
671
            name = name.Singularize(false);
×
672

673
        var rename = name;
31✔
674
        foreach (var selection in _options.Data.Entity.Renaming.Entities.Where(p => p.Expression.HasValue()))
62!
675
        {
676
            if (selection.Expression.IsNullOrEmpty())
×
677
                continue;
678

679
            rename = Regex.Replace(rename, selection.Expression, string.Empty);
×
680
        }
681

682
        // make sure regex doesn't remove everything
683
        return rename.HasValue() ? rename : name;
31!
684
    }
685

686

687
    private string ToClassName(string tableName, string? tableSchema)
688
    {
689
        tableName = EntityName(tableName);
31✔
690
        var className = tableName;
31✔
691

692
        if (_options.Data.Entity.PrefixWithSchemaName && tableSchema != null)
31✔
693
            className = $"{tableSchema}{tableName}";
2✔
694

695
        return ToLegalName(className);
31✔
696
    }
697

698
    private string ToPropertyName(string className, string name)
699
    {
700
        string propertyName = ToLegalName(name);
286✔
701
        if (className.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
286!
702
            propertyName += "Member";
×
703

704
        return propertyName;
286✔
705
    }
706

707
    private static string ToLegalName(string? name)
708
    {
709
        if (string.IsNullOrWhiteSpace(name))
403!
710
            return string.Empty;
×
711

712
        string legalName = name;
403✔
713

714
        // remove invalid leading
715
        var expression = LeadingNonAlphaRegex();
403✔
716
        if (expression.IsMatch(name))
403✔
717
            legalName = expression.Replace(legalName, string.Empty);
1✔
718

719
        // prefix with column when all characters removed
720
        if (legalName.IsNullOrWhiteSpace())
403✔
721
            legalName = "Number" + name;
1✔
722

723
        return legalName.ToPascalCase();
403✔
724
    }
725

726

727
    private static bool IsIgnored(DatabaseTable table, IEnumerable<MatchOptions> exclude)
728
    {
729
        var name = $"{table.Schema}.{table.Name}";
47✔
730
        var includeExpressions = Enumerable.Empty<MatchOptions>();
47✔
731
        var excludeExpressions = exclude ?? [];
47!
732

733
        return IsIgnored(name, excludeExpressions, includeExpressions);
47✔
734
    }
735

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

743
        return IsIgnored(name, excludeExpressions, includeExpressions);
227✔
744
    }
745

746
    private static bool IsIgnored<TOption>(Property property, TOption options, SharedModelOptions sharedOptions)
747
        where TOption : ModelOptionsBase
748
    {
749
        var name = $"{property.Entity.EntityClass}.{property.PropertyName}";
12✔
750

751
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Properties ?? []);
12!
752
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Properties ?? []);
12!
753

754
        var includeProperties = options?.Include?.Properties ?? [];
12!
755
        foreach (var expression in includeProperties)
24!
756
            includeExpressions.Add(expression);
×
757

758
        var excludeProperties = options?.Exclude?.Properties ?? [];
12!
759
        foreach (var expression in excludeProperties)
24!
760
            excludeExpressions.Add(expression);
×
761

762
        return IsIgnored(name, excludeExpressions, includeExpressions);
12✔
763
    }
764

765
    private static bool IsIgnored<TOption>(Entity entity, TOption options, SharedModelOptions sharedOptions)
766
        where TOption : ModelOptionsBase
767
    {
768
        var name = entity.EntityClass;
6✔
769

770
        var includeExpressions = new HashSet<MatchOptions>(sharedOptions?.Include?.Entities ?? []);
6!
771
        var excludeExpressions = new HashSet<MatchOptions>(sharedOptions?.Exclude?.Entities ?? []);
6!
772

773
        var includeEntities = options?.Include?.Entities ?? [];
6!
774
        foreach (var expression in includeEntities)
12!
775
            includeExpressions.Add(expression);
×
776

777
        var excludeEntities = options?.Exclude?.Entities ?? [];
6!
778
        foreach (var expression in excludeEntities)
12!
779
            excludeExpressions.Add(expression);
×
780

781
        return IsIgnored(name, excludeExpressions, includeExpressions);
6✔
782
    }
783

784
    private static bool IsIgnored(string name, IEnumerable<MatchOptions> excludeExpressions, IEnumerable<MatchOptions> includeExpressions)
785
    {
786
        foreach (var expression in includeExpressions)
584!
787
        {
788
            if (expression.IsMatch(name))
×
789
                return false;
×
790
        }
791

792
        foreach (var expression in excludeExpressions)
592✔
793
        {
794
            if (expression.IsMatch(name))
5✔
795
                return true;
2✔
796
        }
797

798
        return false;
290✔
799
    }
2✔
800

801

802
    [GeneratedRegex(@"^[^a-zA-Z_]+")]
803
    private static partial Regex LeadingNonAlphaRegex();
804

805
    [GeneratedRegex(@"(_ID|_id|_Id|\.ID|\.id|\.Id|ID|Id)$")]
806
    private static partial Regex IdSuffixRegex();
807

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