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

loresoft / EntityFrameworkCore.Generator / 27760980405

18 Jun 2026 12:56PM UTC coverage: 75.418% (+0.7%) from 74.693%
27760980405

push

github

pwelter34
fix build

1007 of 1723 branches covered (58.44%)

Branch coverage included in aggregate %.

4221 of 5209 relevant lines covered (81.03%)

1242.99 hits per line

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

95.04
/src/EntityFrameworkCore.Generator.Core/Templates/QueryExtensionTemplate.cs
1
using EntityFrameworkCore.Generator.Extensions;
2
using EntityFrameworkCore.Generator.Metadata.Generation;
3
using EntityFrameworkCore.Generator.Options;
4

5
namespace EntityFrameworkCore.Generator.Templates;
6

7
public class QueryExtensionTemplate : CodeTemplateBase
8
{
9
    private readonly Entity _entity;
10

11
    public QueryExtensionTemplate(Entity entity, GeneratorOptions options) : base(options)
138✔
12
    {
13
        _entity = entity;
138✔
14
    }
138✔
15

16
    public override string WriteCode()
17
    {
18
        CodeBuilder.Clear();
138✔
19

20
        if (Options.Data.Query.Header.HasValue())
138!
21
            CodeBuilder.AppendLine(Options.Data.Query.Header).AppendLine();
×
22

23
        CodeBuilder.AppendLine("using System;");
138✔
24
        CodeBuilder.AppendLine("using System.Collections.Generic;");
138✔
25
        CodeBuilder.AppendLine("using System.Linq;");
138✔
26
        CodeBuilder.AppendLine("using System.Threading;");
138✔
27
        CodeBuilder.AppendLine("using System.Threading.Tasks;");
138✔
28
        CodeBuilder.AppendLine();
138✔
29
        CodeBuilder.AppendLine("using Microsoft.EntityFrameworkCore;");
138✔
30
        CodeBuilder.AppendLine();
138✔
31

32
        var extensionNamespace = Options.Data.Query.Namespace;
138✔
33

34
        CodeBuilder.Append($"namespace {extensionNamespace}");
138✔
35

36
        if (Options.Project.FileScopedNamespace)
138✔
37
        {
38
            CodeBuilder.AppendLine(";");
137✔
39
            CodeBuilder.AppendLine();
137✔
40
            GenerateClass();
137✔
41
        }
42
        else
43
        {
44
            CodeBuilder.AppendLine();
1✔
45
            CodeBuilder.AppendLine("{");
1✔
46

47
            using (CodeBuilder.Indent())
1✔
48
            {
49
                GenerateClass();
1✔
50
            }
1✔
51

52
            CodeBuilder.AppendLine("}");
1✔
53
        }
54

55
        return CodeBuilder.ToString();
138✔
56
    }
57

58

59
    private void GenerateClass()
60
    {
61
        var entityClass = _entity.EntityClass.ToSafeName();
138✔
62
        string safeName = _entity.EntityNamespace + "." + entityClass;
138✔
63

64
        if (Options.Data.Query.Document)
138!
65
        {
66
            GenerateClassDocumentation(safeName);
138✔
67
        }
68

69
        CodeBuilder.AppendLine($"public static partial class {entityClass}Extensions");
138✔
70
        CodeBuilder.AppendLine("{");
138✔
71

72
        using (CodeBuilder.Indent())
138✔
73
        {
74
            GenerateMethods();
138✔
75
        }
138✔
76

77
        CodeBuilder.AppendLine("}");
138✔
78

79
    }
138✔
80

81
    private void GenerateMethods()
82
    {
83
        CodeBuilder.AppendLine("#region Generated Extensions");
138✔
84
        foreach (var method in _entity.Methods.OrderBy(m => m.NameSuffix))
708✔
85
        {
86
            if (method.IsKey)
216✔
87
            {
88
                GenerateKeyMethod(method);
124✔
89
                GenerateKeyMethod(method, true);
124✔
90
            }
91
            else if (method.IsUnique)
92✔
92
            {
93
                GenerateUniqueMethod(method);
17✔
94
                GenerateUniqueMethod(method, true);
17✔
95
            }
96
            else
97
            {
98
                GenerateMethod(method);
75✔
99
            }
100
        }
101
        CodeBuilder.AppendLine("#endregion");
138✔
102
        CodeBuilder.AppendLine();
138✔
103

104
    }
138✔
105

106
    private void GenerateMethod(Method method)
107
    {
108
        var safeName = _entity.EntityNamespace + "." + _entity.EntityClass.ToSafeName();
75✔
109
        var prefix = Options.Data.Query.IndexPrefix;
75✔
110
        var suffix = method.NameSuffix;
75✔
111

112
        if (Options.Data.Query.Document)
75!
113
        {
114
            GenerateFilterMethodDocumentation(method, safeName);
75✔
115
        }
116

117
        CodeBuilder.Append($"public static IQueryable<{safeName}> {prefix}{suffix}(this IQueryable<{safeName}> queryable, ");
75✔
118
        AppendParameters(method);
75✔
119
        CodeBuilder.AppendLine(")");
75✔
120
        CodeBuilder.AppendLine("{");
75✔
121

122
        using (CodeBuilder.Indent())
75✔
123
        {
124
            CodeBuilder.AppendLine("if (queryable is null)");
75✔
125
            using (CodeBuilder.Indent())
75✔
126
                CodeBuilder.AppendLine("throw new ArgumentNullException(nameof(queryable));");
75✔
127
            CodeBuilder.AppendLine();
75✔
128

129
            CodeBuilder.Append("return queryable.Where(");
75✔
130
            AppendLamba(method);
75✔
131
            CodeBuilder.AppendLine(");");
75✔
132
        }
75✔
133

134
        CodeBuilder.AppendLine("}");
75✔
135
        CodeBuilder.AppendLine();
75✔
136
    }
75✔
137

138
    private void GenerateUniqueMethod(Method method, bool async = false)
139
    {
140
        var safeName = _entity.EntityNamespace + "." + _entity.EntityClass.ToSafeName();
34✔
141
        var uniquePrefix = Options.Data.Query.UniquePrefix;
34✔
142
        var suffix = method.NameSuffix;
34✔
143

144
        var asyncSuffix = async ? "Async" : string.Empty;
34✔
145
        var asyncPrefix = async ? "async " : string.Empty;
34✔
146
        var awaitPrefix = async ? "await " : string.Empty;
34✔
147
        var nullableSuffix = Options.Project.Nullable ? "?" : "";
34✔
148
        var returnType = async ? $"System.Threading.Tasks.Task<{safeName}{nullableSuffix}>" : safeName + nullableSuffix;
34✔
149

150
        if (Options.Data.Query.Document)
34!
151
        {
152
            GenerateUniqueMethodDocumentation(method, safeName);
34✔
153

154
            if (async)
34✔
155
                GenerateCancellationTokenDocumentation();
17✔
156

157
            GenerateSingleEntityReturnDocumentation(safeName, async);
34✔
158
        }
159

160
        CodeBuilder.Append($"public static {asyncPrefix}{returnType} {uniquePrefix}{suffix}{asyncSuffix}(this IQueryable<{safeName}> queryable, ");
34✔
161
        AppendParameters(method);
34✔
162

163
        if (async)
34✔
164
            CodeBuilder.Append(", CancellationToken cancellationToken = default");
17✔
165

166
        CodeBuilder.AppendLine(")");
34✔
167
        CodeBuilder.AppendLine("{");
34✔
168

169
        using (CodeBuilder.Indent())
34✔
170
        {
171
            CodeBuilder.AppendLine("if (queryable is null)");
34✔
172
            using (CodeBuilder.Indent())
34✔
173
                CodeBuilder.AppendLine("throw new ArgumentNullException(nameof(queryable));");
34✔
174
            CodeBuilder.AppendLine();
34✔
175

176

177
            CodeBuilder.Append($"return {awaitPrefix}queryable.FirstOrDefault{asyncSuffix}(");
34✔
178

179
            AppendLamba(method);
34✔
180

181
            if (async)
34✔
182
                CodeBuilder.Append(", cancellationToken");
17✔
183

184
            CodeBuilder.AppendLine(");");
34✔
185
        }
34✔
186

187
        CodeBuilder.AppendLine("}");
34✔
188
        CodeBuilder.AppendLine();
34✔
189
    }
34✔
190

191
    private void GenerateKeyMethod(Method method, bool async = false)
192
    {
193
        var safeName = _entity.EntityNamespace + "." + _entity.EntityClass.ToSafeName();
248✔
194
        var uniquePrefix = Options.Data.Query.UniquePrefix;
248✔
195

196
        var asyncSuffix = async ? "Async" : string.Empty;
248✔
197
        var asyncPrefix = async ? "async " : string.Empty;
248✔
198
        var awaitPrefix = async ? "await " : string.Empty;
248✔
199
        var nullableSuffix = Options.Project.Nullable ? "?" : "";
248!
200
        var returnType = async ? $"System.Threading.Tasks.ValueTask<{safeName}{nullableSuffix}>" : safeName + nullableSuffix;
248✔
201

202
        if (Options.Data.Query.Document)
248!
203
        {
204
            GenerateKeyMethodDocumentation(method, safeName);
248✔
205

206
            if (async)
248✔
207
                GenerateCancellationTokenDocumentation();
124✔
208

209
            GenerateSingleEntityReturnDocumentation(safeName, async);
248✔
210
        }
211

212
        CodeBuilder.Append($"public static {asyncPrefix}{returnType} {uniquePrefix}Key{asyncSuffix}(this IQueryable<{safeName}> queryable, ");
248✔
213
        AppendParameters(method);
248✔
214

215
        if (async)
248✔
216
            CodeBuilder.Append(", CancellationToken cancellationToken = default");
124✔
217

218
        CodeBuilder.AppendLine(")");
248✔
219
        CodeBuilder.AppendLine("{");
248✔
220

221
        using (CodeBuilder.Indent())
248✔
222
        {
223
            CodeBuilder.AppendLine("if (queryable is null)");
248✔
224
            using (CodeBuilder.Indent())
248✔
225
                CodeBuilder.AppendLine("throw new ArgumentNullException(nameof(queryable));");
248✔
226
            CodeBuilder.AppendLine();
248✔
227

228
            CodeBuilder.AppendLine($"if (queryable is DbSet<{safeName}> dbSet)");
248✔
229
            using (CodeBuilder.Indent())
248✔
230
            {
231
                CodeBuilder.Append($"return {awaitPrefix}dbSet.Find{asyncSuffix}(");
248✔
232

233
                if (async)
248✔
234
                    CodeBuilder.Append("new object[] { ");
124✔
235

236
                AppendNames(method);
248✔
237

238
                if (async)
248✔
239
                    CodeBuilder.Append(" }, cancellationToken");
124✔
240

241
                CodeBuilder.AppendLine(");");
248✔
242
            }
248✔
243

244
            CodeBuilder.AppendLine("");
248✔
245
            CodeBuilder.Append($"return {awaitPrefix}queryable.FirstOrDefault{asyncSuffix}(");
248✔
246

247
            AppendLamba(method);
248✔
248

249
            if (async)
248✔
250
                CodeBuilder.Append(", cancellationToken");
124✔
251

252
            CodeBuilder.AppendLine(");");
248✔
253
        }
248✔
254
        CodeBuilder.AppendLine("}");
248✔
255
        CodeBuilder.AppendLine();
248✔
256
    }
248✔
257

258

259
    private void GenerateClassDocumentation(string entityFullName)
260
    {
261
        var sourceName = ToXmlText(GetQualifiedTableName());
138✔
262
        var sourceType = _entity.IsView ? "view" : "table";
138✔
263

264
        CodeBuilder.AppendLine("/// <summary>");
138✔
265

266
        if (sourceName.HasValue())
138!
267
            CodeBuilder.AppendLine($"/// Provides query extension methods for <see cref=\"{entityFullName}\" /> entities mapped to the <c>{sourceName}</c> {sourceType}.");
138✔
268
        else
269
            CodeBuilder.AppendLine($"/// Provides query extension methods for <see cref=\"{entityFullName}\" /> entities.");
×
270

271
        CodeBuilder.AppendLine("/// </summary>");
138✔
272
    }
138✔
273

274
    private void GenerateFilterMethodDocumentation(Method method, string entityFullName)
275
    {
276
        CodeBuilder.AppendLine("/// <summary>");
75✔
277
        CodeBuilder.AppendLine($"/// Filters <see cref=\"{entityFullName}\" /> entities by {QueryExtensionTemplate.FormatPropertyList(method)}.");
75✔
278
        CodeBuilder.AppendLine("/// </summary>");
75✔
279

280
        GenerateQueryableParameterDocumentation(entityFullName);
75✔
281
        GenerateParameterDocumentation(method, entityFullName);
75✔
282

283
        CodeBuilder.AppendLine($"/// <returns>An <see cref=\"IQueryable{{T}}\" /> of <see cref=\"{entityFullName}\" /> entities matching the specified values.</returns>");
75✔
284
    }
75✔
285

286
    private void GenerateUniqueMethodDocumentation(Method method, string entityFullName)
287
    {
288
        var sourceName = ToXmlText(method.SourceName);
34✔
289

290
        CodeBuilder.AppendLine("/// <summary>");
34✔
291

292
        if (sourceName.HasValue())
34!
293
            CodeBuilder.AppendLine($"/// Gets the <see cref=\"{entityFullName}\" /> entity matching the unique index <c>{sourceName}</c>.");
34✔
294
        else
295
            CodeBuilder.AppendLine($"/// Gets the <see cref=\"{entityFullName}\" /> entity matching the unique {QueryExtensionTemplate.FormatPropertyList(method)} values.");
×
296

297
        CodeBuilder.AppendLine("/// </summary>");
34✔
298

299
        GenerateQueryableParameterDocumentation(entityFullName);
34✔
300
        GenerateParameterDocumentation(method, entityFullName);
34✔
301
    }
34✔
302

303
    private void GenerateKeyMethodDocumentation(Method method, string entityFullName)
304
    {
305
        CodeBuilder.AppendLine("/// <summary>");
248✔
306
        CodeBuilder.AppendLine($"/// Gets the <see cref=\"{entityFullName}\" /> entity matching the primary key.");
248✔
307
        CodeBuilder.AppendLine("/// </summary>");
248✔
308

309
        GenerateQueryableParameterDocumentation(entityFullName);
248✔
310
        GenerateParameterDocumentation(method, entityFullName);
248✔
311
    }
248✔
312

313
    private void GenerateQueryableParameterDocumentation(string entityFullName)
314
    {
315
        CodeBuilder.AppendLine($"/// <param name=\"queryable\">The source query for <see cref=\"{entityFullName}\" /> entities.</param>");
357✔
316
    }
357✔
317

318
    private void GenerateCancellationTokenDocumentation()
319
    {
320
        CodeBuilder.AppendLine("/// <param name=\"cancellationToken\">A <see cref=\"CancellationToken\" /> to observe while waiting for the operation to complete.</param>");
141✔
321
    }
141✔
322

323
    private void GenerateSingleEntityReturnDocumentation(string entityFullName, bool async)
324
    {
325
        var asyncPrefix = async ? "A task that represents the asynchronous operation. The task result contains" : "";
282✔
326
        var article = async ? " the" : "The";
282✔
327

328
        CodeBuilder.AppendLine($"/// <returns>{asyncPrefix}{article} matching <see cref=\"{entityFullName}\" /> entity, or <see langword=\"null\" /> if no match is found.</returns>");
282✔
329
    }
282✔
330

331
    private void GenerateParameterDocumentation(Method method, string entityFullName)
332
    {
333
        foreach (var property in method.Properties)
1,518✔
334
        {
335
            string paramName = property.PropertyName
402✔
336
                .ToCamelCase()
402✔
337
                .ToSafeName();
402✔
338

339
            var propertyName = property.PropertyName.ToSafeName();
402✔
340
            var columnName = ToXmlText(property.ColumnName);
402✔
341

342
            if (columnName.HasValue())
402!
343
                CodeBuilder.AppendLine($"/// <param name=\"{paramName}\">The value to match against <see cref=\"{entityFullName}.{propertyName}\" /> mapped to the <c>{columnName}</c> column.</param>");
402✔
344
            else
345
                CodeBuilder.AppendLine($"/// <param name=\"{paramName}\">The value to match against <see cref=\"{entityFullName}.{propertyName}\" />.</param>");
×
346
        }
347
    }
357✔
348

349
    private static string FormatPropertyList(Method method)
350
    {
351
        var values = method.Properties
75✔
352
            .Select(property => ToXmlText(property.PropertyName.ToSafeName()) ?? string.Empty)
75✔
353
            .ToList();
75✔
354

355
        return values.Count switch
75!
356
        {
75✔
357
            0 => "the generated criteria",
×
358
            1 => $"<c>{values[0]}</c>",
70✔
359
            2 => $"<c>{values[0]}</c> and <c>{values[1]}</c>",
1✔
360
            _ => $"{string.Join(", ", values.Take(values.Count - 1).Select(value => $"<c>{value}</c>"))}, and <c>{values[^1]}</c>"
4✔
361
        };
75✔
362
    }
363

364
    private string? GetQualifiedTableName()
365
    {
366
        if (_entity.TableName.IsNullOrEmpty())
138!
367
            return _entity.TableName;
×
368

369
        return _entity.TableSchema.HasValue()
138✔
370
            ? $"{_entity.TableSchema}.{_entity.TableName}"
138✔
371
            : _entity.TableName;
138✔
372
    }
373

374
    private void AppendParameters(Method method)
375
    {
376
        bool wrote = false;
357✔
377

378
        foreach (var property in method.Properties)
1,518✔
379
        {
380
            if (wrote)
402✔
381
                CodeBuilder.Append(", ");
45✔
382

383
            var paramName = property.PropertyName
402✔
384
                .ToCamelCase()
402✔
385
                .ToSafeName();
402✔
386

387
            var paramType = property.SystemType
402✔
388
                .ToNullableType(property.IsNullable == true);
402✔
389

390
            CodeBuilder.Append($"{paramType} {paramName}");
402✔
391

392
            wrote = true;
402✔
393
        }
394
    }
357✔
395

396
    private void AppendNames(Method method)
397
    {
398
        bool wrote = false;
248✔
399
        foreach (var property in method.Properties)
1,056✔
400
        {
401
            if (wrote)
280✔
402
                CodeBuilder.Append(", ");
32✔
403

404
            string paramName = property.PropertyName
280✔
405
                .ToCamelCase()
280✔
406
                .ToSafeName();
280✔
407

408
            CodeBuilder.Append(paramName);
280✔
409
            wrote = true;
280✔
410
        }
411
    }
248✔
412

413
    private void AppendLamba(Method method)
414
    {
415
        bool wrote = false;
357✔
416
        bool indented = false;
357✔
417

418
        foreach (var property in method.Properties)
1,518✔
419
        {
420
            string paramName = property.PropertyName
402✔
421
                .ToCamelCase()
402✔
422
                .ToSafeName();
402✔
423

424
            if (!wrote)
402✔
425
            {
426
                CodeBuilder.Append("q => ");
357✔
427
            }
428
            else
429
            {
430
                CodeBuilder.AppendLine();
45✔
431
                CodeBuilder.IncrementIndent();
45✔
432
                CodeBuilder.Append("&& ");
45✔
433

434
                indented = true;
45✔
435
            }
436

437
            if (property.IsNullable == true)
402✔
438
                CodeBuilder.Append($"(q.{property.PropertyName} == {paramName} || ({paramName} == null && q.{property.PropertyName} == null))");
18✔
439
            else
440
                CodeBuilder.Append($"q.{property.PropertyName} == {paramName}");
384✔
441

442
            wrote = true;
402✔
443
        }
444

445
        if (indented)
357✔
446
            CodeBuilder.DecrementIndent();
37✔
447
    }
357✔
448
}
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