< Summary

Information
Class: OzonOrdersWeb.Observability.FirstWithoutOrderByDiagnostics
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Observability/FirstWithoutOrderByDiagnostics.cs
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 61
Line coverage: 100%
Branch coverage
75%
Covered branches: 15
Total branches: 20
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
AddFirstWithoutOrderByDiagnostics(...)83.33%66100%
Report(...)100%11100%
OwnCallSite(...)70%1010100%
Describe(...)75%44100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Observability/FirstWithoutOrderByDiagnostics.cs

#LineLine coverage
 1using System.Diagnostics;
 2using Microsoft.EntityFrameworkCore;
 3using Microsoft.EntityFrameworkCore.Diagnostics;
 4using Serilog;
 5
 6namespace OzonOrdersWeb.Observability;
 7
 8/// <summary>
 9/// Регрессия #857: встроенное предупреждение EF Core FirstWithoutOrderByAndFilterWarning не содержит
 10/// места вызова — фоновое задание, компилирующее такой запрос, найти по нему нельзя.
 11/// Встроенную запись опускаем до Debug, а вместо неё пишем Warning с кадрами стека Studio2:
 12/// компиляция запроса идёт синхронно в стеке вызывающего метода.
 13/// </summary>
 14public static class FirstWithoutOrderByDiagnostics
 15{
 116    private static readonly string[] OwnFramePrefixes = ["OzonOrdersWeb.", "Services.", "OzonRepositories."];
 17
 18    public static TBuilder AddFirstWithoutOrderByDiagnostics<TBuilder>(
 19        this TBuilder options, Serilog.ILogger? logger = null) where TBuilder : DbContextOptionsBuilder
 20    {
 321        options
 322            .ConfigureWarnings(w => w.Log((CoreEventId.FirstWithoutOrderByAndFilterWarning, LogLevel.Debug)))
 323            .LogTo(
 3324                (eventId, _) => eventId == CoreEventId.FirstWithoutOrderByAndFilterWarning,
 425                eventData => Report(eventData, logger ?? Log.Logger));
 326        return options;
 27    }
 28
 29    internal static void Report(EventData eventData, Serilog.ILogger logger) =>
 130        logger.Warning(
 131            "EF Core: First/FirstOrDefault без OrderBy и фильтра даёт непредсказуемую строку. Вызов: {CallSite}",
 132            OwnCallSite(new StackTrace(skipFrames: 1)));
 33
 34    internal static string OwnCallSite(StackTrace stackTrace)
 35    {
 136        var frames = stackTrace.GetFrames()
 8137            .Select(frame => frame.GetMethod())
 8138            .Where(method => method?.DeclaringType?.FullName is { } name
 24039                             && OwnFramePrefixes.Any(prefix => name.StartsWith(prefix, StringComparison.Ordinal))
 8140                             && name != typeof(FirstWithoutOrderByDiagnostics).FullName)
 241            .Select(method => Describe(method!))
 142            .Distinct()
 143            .Take(5)
 144            .ToList();
 145        return frames.Count > 0 ? string.Join(" <- ", frames) : "—";
 46    }
 47
 48    // Async-метод виден в стеке как «Class+<Method>d__5.MoveNext» — показываем «Class.Method».
 49    private static string Describe(System.Reflection.MethodBase method)
 50    {
 251        var typeName = method.DeclaringType!.FullName!;
 252        var nested = typeName.IndexOf("+<", StringComparison.Ordinal);
 253        if (nested < 0)
 154            return $"{typeName}.{method.Name}";
 55
 156        var end = typeName.IndexOf('>', nested);
 157        return end > nested + 2
 158            ? $"{typeName[..nested]}.{typeName[(nested + 2)..end]}"
 159            : typeName[..nested];
 60    }
 61}