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

loresoft / EntityFrameworkCore.Generator / 28022839408

23 Jun 2026 11:27AM UTC coverage: 72.175%. First build
28022839408

Pull #561

github

web-flow
Merge 283ed4c58 into b377f2e8a
Pull Request #561: Closes #560 Add option to delete unused files

999 of 1877 branches covered (53.22%)

Branch coverage included in aggregate %.

4 of 13 new or added lines in 2 files covered. (30.77%)

4233 of 5372 relevant lines covered (78.8%)

2013.67 hits per line

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

63.2
/src/EntityFrameworkCore.Generator.Core/CodeGenerator.cs
1
using EntityFrameworkCore.Generator.Extensions;
2
using EntityFrameworkCore.Generator.Metadata.Generation;
3
using EntityFrameworkCore.Generator.Options;
4
using EntityFrameworkCore.Generator.Parsing;
5
using EntityFrameworkCore.Generator.Scripts;
6
using EntityFrameworkCore.Generator.Templates;
7

8
using Microsoft.Extensions.Logging;
9

10
using SchemaSaurus.Metadata;
11
using SchemaSaurus.Metadata.Provider;
12

13
namespace EntityFrameworkCore.Generator;
14

15
public partial class CodeGenerator : ICodeGenerator
16
{
17
    private readonly ILoggerFactory _loggerFactory;
18
    private readonly ILogger _logger;
19
    private readonly ModelGenerator _modelGenerator;
20
    private readonly SourceSynchronizer _synchronizer;
21

22
    public CodeGenerator(ILoggerFactory loggerFactory)
6✔
23
    {
24
        _loggerFactory = loggerFactory;
6✔
25
        _logger = loggerFactory.CreateLogger<CodeGenerator>();
6✔
26
        _modelGenerator = new ModelGenerator(loggerFactory);
6✔
27
        _synchronizer = new SourceSynchronizer(loggerFactory);
6✔
28
    }
6✔
29

30
    public GeneratorOptions Options { get; set; } = null!;
31

32
    public async Task<bool> GenerateAsync(GeneratorOptions options)
33
    {
34
        Options = options ?? throw new ArgumentNullException(nameof(options));
6!
35

36
        var databaseProvider = GetDatabaseProvider();
6✔
37
        var databaseModel = await GetDatabaseModel(databaseProvider);
6✔
38

39
        if (databaseModel == null)
6!
40
            throw new InvalidOperationException("Failed to create database model");
×
41

42
        LogLoadedDatabaseModel(_logger, databaseModel.DatabaseName);
6✔
43

44
        var context = _modelGenerator.Generate(Options, databaseModel);
6✔
45

46
        _synchronizer.UpdateFromSource(context, options);
6✔
47

48
        GenerateFiles(context);
6✔
49

50
        return true;
6✔
51
    }
6✔
52

53
    private void GenerateFiles(EntityContext entityContext)
54
    {
55
        GenerateDataContext(entityContext);
6✔
56
        GenerateEntityClasses(entityContext);
6✔
57
        GenerateMappingClasses(entityContext);
6✔
58

59
        if (Options.Data.Query.Generate)
6✔
60
            GenerateQueryExtensions(entityContext);
4✔
61

62
        GenerateModelClasses(entityContext);
6✔
63

64
        GenerateScriptTemplates(entityContext);
6✔
65
    }
6✔
66

67
    private void GenerateQueryExtensions(EntityContext entityContext)
68
    {
69
        foreach (var entity in entityContext.Entities)
282✔
70
        {
71
            Options.Variables.Set(entity);
137✔
72

73
            var directory = Options.Data.Query.Directory ?? "Data\\Queries";
137!
74
            var file = entity.EntityClass + "Extensions.cs";
137✔
75
            var path = Path.Combine(directory, file);
137✔
76

77
            LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "query extensions", file);
137!
78

79
            var template = new QueryExtensionTemplate(entity, Options);
137✔
80
            template.WriteCode(path);
137✔
81

82
            Options.Variables.Remove(entity);
137✔
83
        }
84
    }
4✔
85

86
    private void GenerateMappingClasses(EntityContext entityContext)
87
    {
88
        if (Options.Data.Mapping.DeleteUnusedFiles)
6!
NEW
89
            DeleteUnusedFiles(Options.Data.Mapping.Directory, entityContext.Entities.Select(e => e.MappingClass).ToHashSet(), "mapping");
×
90

91
        foreach (var entity in entityContext.Entities)
330✔
92
        {
93
            Options.Variables.Set(entity);
159✔
94

95
            var directory = Options.Data.Mapping.Directory ?? "Data\\Mapping";
159!
96
            var file = entity.MappingClass + ".cs";
159✔
97
            var path = Path.Combine(directory, file);
159✔
98

99
            LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "mapping", file);
159✔
100

101
            var template = new MappingClassTemplate(entity, Options);
159✔
102
            template.WriteCode(path);
159✔
103

104
            Options.Variables.Remove(entity);
159✔
105
        }
106
    }
6✔
107

108
    private void GenerateEntityClasses(EntityContext entityContext)
109
    {
110
        if (Options.Data.Entity.DeleteUnusedFiles)
6!
NEW
111
            DeleteUnusedFiles(Options.Data.Entity.Directory, entityContext.Entities.Select(e => e.EntityClass).ToHashSet(), "entity");
×
112

113
        foreach (var entity in entityContext.Entities)
330✔
114
        {
115
            Options.Variables.Set(entity);
159✔
116

117
            var directory = Options.Data.Entity.Directory ?? "Data\\Entities";
159!
118
            var file = entity.EntityClass + ".cs";
159✔
119
            var path = Path.Combine(directory, file);
159✔
120

121
            LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "entity", file);
159✔
122

123
            var template = new EntityClassTemplate(entity, Options);
159✔
124
            template.WriteCode(path);
159✔
125

126
            Options.Variables.Remove(entity);
159✔
127
        }
128
    }
6✔
129

130
    private void DeleteUnusedFiles(string directory, HashSet<string> fileNamesToBeGenerated, string fileType)
131
    {
NEW
132
        var existingFiles = Directory.EnumerateFiles(directory, "*.cs");
×
NEW
133
        foreach (var existingFile in existingFiles)
×
134
        {
NEW
135
            var fileName = Path.GetFileNameWithoutExtension(existingFile);
×
NEW
136
            if (!fileNamesToBeGenerated.Contains(fileName))
×
137
            {
NEW
138
                _logger.LogInformation("Deleting {fileType} class: {file}.cs", fileType, fileName);
×
NEW
139
                File.Delete(existingFile);
×
140
            }
141
        }
NEW
142
    }
×
143

144
    private void GenerateDataContext(EntityContext entityContext)
145
    {
146

147
        var directory = Options.Data.Context.Directory ?? "Data";
6!
148
        var file = entityContext.ContextClass + ".cs";
6✔
149
        var path = Path.Combine(directory, file);
6✔
150

151
        LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "data context", file);
6✔
152

153
        var template = new DataContextTemplate(entityContext, Options);
6✔
154
        template.WriteCode(path);
6✔
155
    }
6✔
156

157

158
    private void GenerateModelClasses(EntityContext entityContext)
159
    {
160
        foreach (var entity in entityContext.Entities)
330✔
161
        {
162
            if (entity.Models.Count == 0)
159✔
163
                continue;
164

165
            Options.Variables.Set(entity);
137✔
166

167
            GenerateModelClasses(entity);
137✔
168
            GenerateValidatorClasses(entity);
137✔
169
            GenerateMapperClass(entity);
137✔
170

171
            Options.Variables.Remove(entity);
137✔
172
        }
173
    }
6✔
174

175

176
    private void GenerateModelClasses(Entity entity)
177
    {
178
        foreach (var model in entity.Models)
1,060✔
179
        {
180
            Options.Variables.Set(model);
393✔
181

182
            var directory = GetModelDirectory(model) ?? "Data\\Models";
393!
183
            var file = model.ModelClass + ".cs";
393✔
184
            var path = Path.Combine(directory, file);
393✔
185

186
            LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "model", file);
393!
187

188
            var template = new ModelClassTemplate(model, Options);
393✔
189
            template.WriteCode(path);
393✔
190

191
            Options.Variables.Remove(model);
393✔
192
        }
193

194
    }
137✔
195

196
    private string? GetModelDirectory(Model model)
197
    {
198
        if (model.ModelType == ModelType.Create)
393✔
199
        {
200
            return Options.Model.Create.Directory.HasValue()
128!
201
                ? Options.Model.Create.Directory
128✔
202
                : Options.Model.Shared.Directory;
128✔
203
        }
204

205
        if (model.ModelType == ModelType.Update)
265✔
206
        {
207
            return Options.Model.Update.Directory.HasValue()
128!
208
                ? Options.Model.Update.Directory
128✔
209
                : Options.Model.Shared.Directory;
128✔
210
        }
211

212
        return Options.Model.Read.Directory.HasValue()
137!
213
            ? Options.Model.Read.Directory
137✔
214
            : Options.Model.Shared.Directory;
137✔
215
    }
216

217

218
    private void GenerateValidatorClasses(Entity entity)
219
    {
220
        if (!Options.Model.Validator.Generate)
137!
221
            return;
×
222

223
        foreach (var model in entity.Models)
1,060✔
224
        {
225
            Options.Variables.Set(model);
393✔
226

227
            // don't validate read models
228
            if (model.ModelType == ModelType.Read)
393✔
229
                continue;
230

231
            var directory = Options.Model.Validator.Directory ?? "Data\\Validation";
256!
232
            var file = model.ValidatorClass + ".cs";
256✔
233
            var path = Path.Combine(directory, file);
256✔
234

235
            LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "validation", file);
256!
236

237
            var template = new ValidatorClassTemplate(model, Options);
256✔
238
            template.WriteCode(path);
256✔
239

240
            Options.Variables.Remove(model);
256✔
241
        }
242
    }
137✔
243

244

245
    private void GenerateMapperClass(Entity entity)
246
    {
247
        if (!Options.Model.Mapper.Generate)
137!
248
            return;
×
249

250
        var directory = Options.Model.Mapper.Directory ?? "Data\\Mapper";
137!
251
        var file = entity.MapperClass + ".cs";
137✔
252
        var path = Path.Combine(directory, file);
137✔
253

254
        LogClassFileAction(_logger, File.Exists(path) ? "Updating" : "Creating", "mapper", file);
137!
255

256
        var template = new MapperClassTemplate(entity, Options);
137✔
257
        template.WriteCode(path);
137✔
258
    }
137✔
259

260

261
    private void GenerateScriptTemplates(EntityContext entityContext)
262
    {
263
        GenerateContextScriptTemplates(entityContext);
6✔
264
        GenerateEntityScriptTemplates(entityContext);
6✔
265
        GenerateModelScriptTemplates(entityContext);
6✔
266
    }
6✔
267

268
    private void GenerateModelScriptTemplates(EntityContext entityContext)
269
    {
270
        if (Options?.Script?.Model == null || Options.Script.Model.Count == 0)
6!
271
            return;
6✔
272

273
        foreach (var templateOption in Options.Script.Model)
×
274
        {
275
            if (!VerifyScriptTemplate(templateOption))
×
276
                continue;
277

278
            try
279
            {
280
                var template = new ModelScriptTemplate(_loggerFactory, Options, templateOption);
×
281

282
                foreach (var entity in entityContext.Entities)
×
283
                {
284
                    Options.Variables.Set(entity);
×
285

286
                    foreach (var model in entity.Models)
×
287
                    {
288
                        Options.Variables.Set(model);
×
289

290
                        template.RunScript(model);
×
291

292
                        Options.Variables.Remove(model);
×
293
                    }
294

295
                    Options.Variables.Remove(entity);
×
296
                }
297
            }
×
298
            catch (Exception ex)
×
299
            {
300
                LogErrorRunningTemplate(_logger, ex, "Model", ex.Message);
×
301
            }
×
302
        }
303
    }
×
304

305
    private void GenerateEntityScriptTemplates(EntityContext entityContext)
306
    {
307
        if (Options?.Script?.Entity == null || Options.Script.Entity.Count == 0)
6!
308
            return;
6✔
309

310
        foreach (var templateOption in Options.Script.Entity)
×
311
        {
312
            if (!VerifyScriptTemplate(templateOption))
×
313
                continue;
314

315
            try
316
            {
317
                var template = new EntityScriptTemplate(_loggerFactory, Options, templateOption);
×
318

319
                foreach (var entity in entityContext.Entities)
×
320
                {
321
                    Options.Variables.Set(entity);
×
322

323
                    template.RunScript(entity);
×
324

325
                    Options.Variables.Remove(entity);
×
326
                }
327
            }
×
328
            catch (Exception ex)
×
329
            {
330
                LogErrorRunningTemplate(_logger, ex, "Entity", ex.Message);
×
331
            }
×
332
        }
333
    }
×
334

335
    private void GenerateContextScriptTemplates(EntityContext entityContext)
336
    {
337
        if (Options?.Script?.Context == null || Options.Script.Context.Count! < 0)
6!
338
            return;
×
339

340
        foreach (var templateOption in Options.Script.Context)
12!
341
        {
342
            if (!VerifyScriptTemplate(templateOption))
×
343
                continue;
344

345
            try
346
            {
347
                var template = new ContextScriptTemplate(_loggerFactory, Options, templateOption);
×
348
                template.RunScript(entityContext);
×
349
            }
×
350
            catch (Exception ex)
×
351
            {
352
                LogErrorRunningTemplate(_logger, ex, "Context", ex.Message);
×
353
            }
×
354
        }
355
    }
6✔
356

357
    private bool VerifyScriptTemplate(TemplateOptions templateOption)
358
    {
359
        var templatePath = templateOption.TemplatePath;
×
360

361
        if (File.Exists(templatePath))
×
362
            return true;
×
363

364
        LogTemplateNotFound(_logger, templatePath);
×
365
        return false;
×
366
    }
367

368

369
    private async Task<DatabaseModel> GetDatabaseModel(IDatabaseSchemaReader factory)
370
    {
371
        LogLoadingDatabaseModel(_logger);
6✔
372

373
        var database = Options.Database;
6✔
374

375
        var connectionString = ResolveConnectionString(database);
6✔
376
        if (string.IsNullOrEmpty(connectionString))
6!
377
            throw new InvalidOperationException("Could not find connection string.");
×
378

379
        var options = new SchemaReaderOptions
6✔
380
        {
6✔
381
            Schemas = Options.Database.Schemas,
6✔
382
            Tables = Options.Database.Tables
6✔
383
        };
6✔
384

385
        return await factory.ReadAsync(connectionString, options);
6✔
386
    }
6✔
387

388
    private static string? ResolveConnectionString(DatabaseOptions database)
389
    {
390
        if (database.ConnectionString.HasValue())
6!
391
            return database.ConnectionString;
6✔
392

393
        if (database.UserSecretsId.HasValue() && database.ConnectionName.HasValue())
×
394
        {
395
            var secretsStore = new SecretsStore(database.UserSecretsId);
×
396
            if (secretsStore.ContainsKey(database.ConnectionName))
×
397
                return secretsStore[database.ConnectionName];
×
398
        }
399

400
        throw new InvalidOperationException("Could not find connection string.");
×
401
    }
402

403

404
    private IDatabaseSchemaReader GetDatabaseProvider()
405
    {
406
        var provider = Options.Database.Provider;
6✔
407

408
        LogCreatingDatabaseModelFactory(_logger, provider);
6✔
409

410
        return provider switch
6!
411
        {
6✔
412
            DatabaseProviders.SqlServer => new SchemaSaurus.SqlServer.SqlServerSchemaReader(),
3✔
413
            DatabaseProviders.PostgreSQL => new SchemaSaurus.PostgreSql.PostgreSqlSchemaReader(),
1✔
414
            DatabaseProviders.MySQL => new SchemaSaurus.MySql.MySqlSchemaReader(),
1✔
415
            DatabaseProviders.Sqlite => new SchemaSaurus.Sqlite.SqliteSchemaReader(),
1✔
416
            DatabaseProviders.Oracle => new SchemaSaurus.Oracle.OracleSchemaReader(),
×
417
            _ => throw new NotSupportedException($"The specified provider '{provider}' is not supported."),
×
418
        };
6✔
419
    }
420

421

422
    [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Loaded database model for: {databaseName}")]
423
    private static partial void LogLoadedDatabaseModel(ILogger logger, string? databaseName);
424

425
    [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "{action} {kind} class: {file}")]
426
    private static partial void LogClassFileAction(ILogger logger, string action, string kind, string file);
427

428
    [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Error Running {templateType} Template: {errorMessage}")]
429
    private static partial void LogErrorRunningTemplate(ILogger logger, Exception exception, string templateType, string errorMessage);
430

431
    [LoggerMessage(EventId = 4, Level = LogLevel.Warning, Message = "Template '{template}' could not be found.")]
432
    private static partial void LogTemplateNotFound(ILogger logger, string? template);
433

434
    [LoggerMessage(EventId = 5, Level = LogLevel.Information, Message = "Loading database model ...")]
435
    private static partial void LogLoadingDatabaseModel(ILogger logger);
436

437
    [LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = "Creating database model factory for: {provider}")]
438
    private static partial void LogCreatingDatabaseModelFactory(ILogger logger, DatabaseProviders provider);
439
}
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