Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/entity-framework/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-core/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Entity framework 使用EF核心记录查询持续时间_Entity Framework_Asp.net Core_Logging_Asp.net Core Mvc_Entity Framework Core - Fatal编程技术网

Entity framework 使用EF核心记录查询持续时间

Entity framework 使用EF核心记录查询持续时间,entity-framework,asp.net-core,logging,asp.net-core-mvc,entity-framework-core,Entity Framework,Asp.net Core,Logging,Asp.net Core Mvc,Entity Framework Core,为了记录ASP.NET核心web应用程序中SQL Server查询所花费的时间,我一直在使用以下代码,订阅了一些中间件中的所有DiagnosticListeners,并使用下面的Observable 我不确定这是性能方面最理想的解决方案,我想知道是否有更好的方法通过直接从EFCore捕获详细的日志对象来使用ASP.NET Core日志API?理想情况下,我希望保留通过请求执行的所有查询的总持续时间,并在请求结束时以毫秒为单位记录中间件可以使用的总持续时间 public class QueryTi

为了记录ASP.NET核心web应用程序中SQL Server查询所花费的时间,我一直在使用以下代码,订阅了一些中间件中的所有
DiagnosticListeners
,并使用下面的
Observable

我不确定这是性能方面最理想的解决方案,我想知道是否有更好的方法通过直接从EFCore捕获详细的日志对象来使用ASP.NET Core日志API?理想情况下,我希望保留通过请求执行的所有查询的总持续时间,并在请求结束时以毫秒为单位记录中间件可以使用的总持续时间

public class QueryTimingObserver : IObserver<DiagnosticListener>
{
    private readonly List<IDisposable> subscriptions = new List<IDisposable>();
    private readonly AsyncLocal<Stopwatch> stopwatch = new AsyncLocal<Stopwatch>();
    private double milliseconds = 0;

    void IObserver<DiagnosticListener>.OnNext(DiagnosticListener diagnosticListener)
    {
        if (diagnosticListener.Name == "SqlClientDiagnosticListener")
        {
            IDisposable subscription = diagnosticListener.SubscribeWithAdapter(this);
            subscriptions.Add(subscription);
        }
    }

    void IObserver<DiagnosticListener>.OnError(Exception error)
    {
    }

    void IObserver<DiagnosticListener>.OnCompleted()
    {
        subscriptions.ForEach(x => x.Dispose());
        subscriptions.Clear();
    }

    [DiagnosticName("System.Data.SqlClient.WriteCommandBefore")]
    public void OnCommandBefore()
    {
        stopwatch.Value = Stopwatch.StartNew();
    }

    [DiagnosticName("System.Data.SqlClient.WriteCommandAfter")]
    public void OnCommandAfter(DbCommand command)
    {
        stopwatch.Value.Stop();
        milliseconds += stopwatch.Value.Elapsed.TotalMilliseconds;
    }

    public double Milliseconds
    {
        get
        {
            return milliseconds;
        }
    }
}
公共类查询观察者:IObserver
{
私有只读列表订阅=新列表();
私有只读AsyncLocal秒表=新建AsyncLocal();
专用双毫秒=0;
void IObserver.OnNext(DiagnosticListener DiagnosticListener)
{
if(diagnosticstener.Name==“sqlclientdiagnosticstener”)
{
IDisposable subscription=diagnosticListener.SubscripteWizAdapter(此);
订阅。添加(订阅);
}
}
void IObserver.OnError(异常错误)
{
}
void IObserver.OnCompleted()
{
subscriptions.ForEach(x=>x.Dispose());
订阅。清除();
}
[DiagnosticName(“System.Data.SqlClient.WriteCommand”)]
CommandBefore()上的公共无效
{
stopwatch.Value=stopwatch.StartNew();
}
[DiagnosticName(“System.Data.SqlClient.WriteCommandAfter”)]
public void OnCommandAfter(DbCommand命令)
{
stopwatch.Value.Stop();
毫秒+=stopwatch.Value.appeased.total毫秒;
}
公共双毫秒
{
得到
{
返回毫秒;
}
}
}

衡量性能的方法有很多,但在计时查询方面,我从SQL Profiler之类的数据库服务器工具开始。对于SQL Server,SQL Server探查器和SQL Server Management Studio工具提供了大量的详细信息

现在,我不能具体评论你的代码,但我可以给你一些值得考虑的建议。如果我想在代码中记录查询执行时间,那么我通常会做以下两件事之一

1) 久经考验的秒表从不失灵。就像你在做的一样


2) 现在,我最喜欢的工具。如果可以,我会在我的项目中使用。它来自Stack Exchange,运行这个网站的人,我们现在正在讨论评测。它向您显示SQL查询并警告您常见错误。您可以将它与EF Core一起使用,它有一个漂亮的小部件,您可以轻松地将其集成到网站的页面中,随时查看正在发生的事情。

分享我在项目中所做的工作

  • 为每个传入请求创建ApiRequest对象
  • 从请求中收集基本信息,如ip地址、用户代理、, 用户id、推荐人、路径、搜索类型等
  • 现在,我可以在控制器/服务层和 启动/停止计时器并计算每个步骤的时间(db (电话)
  • 将APIRESQUEST对象保存到首选日志数据库 在做出回应之前
  • API请求类:

    public class ApiRequest : IDisposable
    {
        [BsonId]
        public string Id { get; set; }
        // public string RequestId { get; set; }
        public string ClientSessionId { get; set; }
        public DateTime RequestStartTime { get; set; }
        public string LogLevel { get; set; }
        public string AccessProfile { get; set; }
        public string ApiClientIpAddress { get; set; }
        public GeoData ApiClientGeoData { get; set; }
        public string OriginatingIpAddress { get; set; }
        public GeoData OriginatingGeoData { get; set; }
        public int StatusCode { get; set; }
        public bool IsError { get; set; }
        // public string ErrorMessage { get; set; }
        public ConcurrentBag<string> Errors { get; set; }
        public ConcurrentBag<string> Warnings { get; set; }
        public long TotalExecutionTimeInMs { get; set; }
        public long? ResponseSizeInBytes { get; set; }
        public long TotalMySqlSpCalls { get; set; }
        public long DbConnectionTimeInMs { get; set; }
        public long DbCallTimeInMs { get; set; }
        public long OverheadTimeInMs { get; set; }
        public string UriHost { get; set; }
        public string UriPath { get; set; }
        public string SearchRequest { get; set; }
        public string Referrer { get; set; }
        public string UserAgent { get; set; }
        public string SearchType { get; set; }
        public ConcurrentQueue<RequestExecutionStatistics> ExecutionHistory { get; set; }
        public DateTime CreatedDate { get; set; }
        public string CreatedBy { get; set; }
    
        [BsonIgnore]
        public Stopwatch Timer { get; set; }
    
        public ApiRequest(Stopwatch stopwatch)
        {
            Id = Guid.NewGuid().ToString();
            // RequestId = Guid.NewGuid().ToString();
            Timer = stopwatch;
            RequestStartTime = DateTime.Now.Subtract(Timer.Elapsed);
            ExecutionHistory = new ConcurrentQueue<RequestExecutionStatistics>();
            ExecutionHistory.Enqueue(new RequestExecutionStatistics
            {
                Description = "HTTP Request Start",
                Status = RequestExecutionStatus.Started,
                Index = 1,
                StartTime = Timer.ElapsedMilliseconds,
                ExecutionTimeInMs = 0
            });
            Errors = new ConcurrentBag<string>();
            Warnings = new ConcurrentBag<string>();
        }
    
        public Task AddExecutionTimeStats(string description, RequestExecutionStatus status, long startTime, long executionTimeInMs)
        {
            int count = ExecutionHistory.Count;
    
            return Task.Run(() =>
                ExecutionHistory.Enqueue(new RequestExecutionStatistics
                {
                    Description = description,
                    Status = status,
                    Index = count + 1,
                    StartTime = startTime,
                    ExecutionTimeInMs = executionTimeInMs
                }));
        }
    
        public Task UpdateExecutionTimeStats(string description, long executionTimeInMs)
        {
            return Task.Run(() =>
            {
                var stats = ExecutionHistory.FirstOrDefault(e => e.Description.ToLower() == description.ToLower());
                if (stats != null) stats.ExecutionTimeInMs = executionTimeInMs;
            });
        }
    
        #region IDisposable implementation
    
        private bool _disposed;
    
        // Public implementation of Dispose pattern callable by consumers.
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        // Protected implementation of Dispose pattern.
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) { return; }
    
            if (disposing)
            {
                // Free managed objects here.
                Timer.Stop(); // Probably doesn't matter, but anyways...
            }
    
            // Free any unmanaged objects here.
    
    
            // Mark this object as disposed.
            _disposed = true;
        }
    
        #endregion
    }
    
    日志输出示例:

    {
    "_id" : "75a90cc9-5d80-4eb8-aae1-70a3611fe8ff",
    "RequestId" : "98be1d85-9941-4a17-a73a-5f0a38bd7703",
    "ClientSessionId" : null,
    "RequestStartTime" : ISODate("2019-09-11T15:22:05.802-07:00"),
    "LogLevel" : "Information",
    "AccessProfile" : "Sample",
    "ApiClientIpAddress" : "0.0.0.0",
    "ApiClientGeoData" : null,
    "OriginatingIpAddress" : "0.0.0.0",
    "OriginatingGeoData" : null,
    "StatusCode" : 200,
    "IsError" : false,
    "Errors" : [],
    "Warnings" : [],
    "TotalExecutionTimeInMs" : NumberLong(115),
    "ResponseSizeInBytes" : NumberLong(0),
    "TotalMySqlSpCalss" : NumberLong(0),
    "DbConnectionTimeInMs" : NumberLong(3),
    "DbCallTimeInMs" : NumberLong(0),
    "OverheadTimeInMs" : NumberLong(0),
    "UriHost" : "www.sampleapi.com",
    "UriPath" : "/api/Samples",
    "SearchRequest" : null,
    "Referrer" : null,
    "UserAgent" : null,
    "SearchType" : "Sample",
    "ExecutionHistory" : [ 
        {
            "Description" : "HTTP Request Start",
            "Index" : 1,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(0)
        }, 
        {
            "Description" : "Sample Search Controller",
            "Index" : 2,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(115)
        }, 
        {
            "Description" : "Sample Search Service",
            "Index" : 3,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(115)
        }, 
        {
            "Description" : "DB Connection",
            "Index" : 4,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(3)
        }, 
        {
            "Description" : "Sample DB Call",
            "Index" : 5,
            "StartTime" : NumberLong(3),
            "ExecutionTimeInMs" : NumberLong(112)
        }
    ],
    "CreatedDate" : ISODate("2019-09-11T15:22:05.918-07:00"),
    "CreatedBy" : "Server"}
    
    公共类查询interceptor
    {
    专用只读ILogger\u记录器;
    私有字符串查询;
    私有日期时间偏移量_startTime;
    公共查询接收器(ILogger记录器)
    {
    _记录器=记录器;
    }
    [DiagnosticName(“Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting”)]
    public void OnCommandExecuting(DbCommand命令、DbCommandMethod executeMethod、Guid commandId、Guid connectionId、bool async、DateTimeOffset startTime)
    {
    _query=command.CommandText;
    _开始时间=开始时间;
    }
    [DiagnosticName(“Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted”)]
    CommandExecuted上的公共void(对象结果,bool异步)
    {
    var endTime=DateTimeOffset.Now;
    变量querytime=(endTime-_startTime).TotalSeconds;
    _logger.LogInformation(“\n”+”在“+queryming+”秒\n”内执行“+”\n”+\u query+“\n”+”;
    }
    [DiagnosticName(“Microsoft.EntityFrameworkCore.Database.Command.CommandError”)]
    CommandError上的公共void(异常,bool异步)
    {
    _logger.LogError(异常消息);
    }
    }
    
    在Program.cs中

    var loggerQueryInterceptor = services.GetService<ILogger<QueryInterceptor>>();
    var listener = context.GetService<DiagnosticSource>();
    (listener as DiagnosticListener).SubscribeWithAdapter(new QueryInterceptor(loggerQueryInterceptor));
    
    var loggerQueryInterceptor=services.GetService();
    var listener=context.GetService();
    (作为诊断侦听器的侦听器)。SubscribeWizThadapter(新的QueryInterceptor(loggerQueryInterceptor));
    
    查询性能应在两个级别上完成,控制器/服务操作的计时和数据库级别的分析。性能问题的出现有多种原因,可能仅在控制器或数据库中检测到,或者两者都检测到。在代码级别记录性能指标应该始终是可配置的,以便能够轻松地将其关闭和打开,因为任何性能捕获都表示性能命中,加上记录结果所需的资源空间

    冒着有点离题的风险,我概述了我遇到的典型性能陷阱,以及我如何度量/检测它们。本文的目的只是概述为什么SQL端分析与基于代码的计时器一起对检测潜在的性能问题很有价值,以及确定解决这些问题的步骤

  • 恶意延迟加载调用。SQL分析检测到。Web请求查询数据初始查询快速完成时,Web请求在调试器中似乎快速结束,但客户端在一段时间内没有收到响应。初始查询和请求完成后,探查器将跟踪许多“按ID”查询。这些都是由s触发的延迟加载调用
    public class ApiRequestsMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IApiRequestService _apiRequestService;
    
        public ApiRequestsMiddleware(RequestDelegate next, IApiRequestService apiRequestService)
        {
            _next = next;
            _apiRequestService = apiRequestService;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            var stopwatch = Stopwatch.StartNew();
            var accessProfileName = context.User.Claims != null && context.User.Claims.Any()
                ? context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value
                : null;
    
            try
            {
                var request = new ApiRequest(stopwatch)
                {
                    AccessProfile = accessProfileName,
                    ClientSessionId = ContextHelper.GetHeadersValue(context, Constants.Headers.ClientSessionId),
                    ApiClientIpAddress = context.Connection.RemoteIpAddress.ToString()
                };
    
                var originatingIpAddress = ContextHelper.GetHeadersValue(context, Constants.Headers.OriginatingIpAddress);
                request.OriginatingIpAddress = !string.IsNullOrWhiteSpace(originatingIpAddress)
                    ? originatingIpAddress
                    : context.Connection.RemoteIpAddress.ToString();
    
                request.UriHost = context.Request.Host.ToString();
                request.UriPath = context.Request.Path;
    
                var referrer = ContextHelper.GetHeadersValue(context, Constants.Headers.Referrer);
                request.Referrer = !string.IsNullOrWhiteSpace(referrer) ? referrer : null;
    
                var userAgent = ContextHelper.GetHeadersValue(context, Constants.Headers.UserAgent);
                request.UserAgent = !string.IsNullOrWhiteSpace(userAgent) ? userAgent : null;
    
                request.SearchType = SearchHelper.GetSearchType(request.UriPath).ToString();
    
                context.Items.Add(Constants.CacheKeys.SampleApiRequest, request);
    
                await _next(context);
                stopwatch.Stop();
    
                request.StatusCode = context.Response.StatusCode;
                request.LogLevel = LogLevel.Information.ToString();
                request.TotalExecutionTimeInMs = stopwatch.ElapsedMilliseconds;
    
                if (request.IsError)
                    request.LogLevel = LogLevel.Error.ToString();
    
                if (_apiRequestService != null)
                    Task.Run(() => _apiRequestService.Create(request));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }
    
    public static class ApiRequestsMiddlewareExtensions
    {
        public static IApplicationBuilder UseApiRequests(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ApiRequestsMiddleware>();
        }
    }
    
    public class ApiRequestService : IApiRequestService
    {
        private readonly IMongoDbContext _context;
        private readonly IIpLocatorService _ipLocatorService;
    
        public ApiRequestService(IMongoDbContext context, IIpLocatorService ipLocatorService)
        {
            _context = context;
            _ipLocatorService = ipLocatorService;
        }
    
        public async Task<IEnumerable<ApiRequest>> Get()
        {
            return await _context.ApiRequests.Find(_ => true).ToListAsync();
        }
    
        public async Task<ApiRequest> Get(string id)
        {
            var filter = Builders<ApiRequest>.Filter.Eq("Id", id);
    
            try
            {
                return await _context.ApiRequests.Find(filter).FirstOrDefaultAsync();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
        public async Task Create(ApiRequest request)
        {
            try
            {
                request.OriginatingGeoData = null; // await _ipLocatorService.Get(request.OriginatingIpAddress);
    
                var finalHistory = new ConcurrentQueue<RequestExecutionStatistics>();
    
                foreach (var history in request.ExecutionHistory.ToList())
                {
                    if (history.Status == RequestExecutionStatus.Started)
                    {
                        var findPartner = request.ExecutionHistory.FirstOrDefault(e =>
                            e.Description.ToLower() == history.Description.ToLower() &&
                            e.Status == RequestExecutionStatus.Complted);
    
                        if (findPartner != null)
                        {
                            var temp = history.Clone();
                            temp.Status = RequestExecutionStatus.Complted;
                            temp.ExecutionTimeInMs = findPartner.ExecutionTimeInMs - history.StartTime;
                            finalHistory.Enqueue(temp);
                        }
                        else
                            finalHistory.Enqueue(history);
                    }
                    else if (history.Status == RequestExecutionStatus.Complted)
                    {
                        var findPartner = request.ExecutionHistory.FirstOrDefault(e =>
                            e.Description.ToLower() == history.Description.ToLower() &&
                            e.Status == RequestExecutionStatus.Started);
    
                        if (findPartner == null)
                            finalHistory.Enqueue(history);
                    }
                }
    
                request.ExecutionHistory = finalHistory;
                request.CreatedDate = DateTime.Now;
                request.CreatedBy = Environment.MachineName;
                await _context.ApiRequests.InsertOneAsync(request);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            // Other code        
    
            app.UseApiRequests();
            app.UseResponseWrapper();
            app.UseMvc();
        }
    
    {
    "_id" : "75a90cc9-5d80-4eb8-aae1-70a3611fe8ff",
    "RequestId" : "98be1d85-9941-4a17-a73a-5f0a38bd7703",
    "ClientSessionId" : null,
    "RequestStartTime" : ISODate("2019-09-11T15:22:05.802-07:00"),
    "LogLevel" : "Information",
    "AccessProfile" : "Sample",
    "ApiClientIpAddress" : "0.0.0.0",
    "ApiClientGeoData" : null,
    "OriginatingIpAddress" : "0.0.0.0",
    "OriginatingGeoData" : null,
    "StatusCode" : 200,
    "IsError" : false,
    "Errors" : [],
    "Warnings" : [],
    "TotalExecutionTimeInMs" : NumberLong(115),
    "ResponseSizeInBytes" : NumberLong(0),
    "TotalMySqlSpCalss" : NumberLong(0),
    "DbConnectionTimeInMs" : NumberLong(3),
    "DbCallTimeInMs" : NumberLong(0),
    "OverheadTimeInMs" : NumberLong(0),
    "UriHost" : "www.sampleapi.com",
    "UriPath" : "/api/Samples",
    "SearchRequest" : null,
    "Referrer" : null,
    "UserAgent" : null,
    "SearchType" : "Sample",
    "ExecutionHistory" : [ 
        {
            "Description" : "HTTP Request Start",
            "Index" : 1,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(0)
        }, 
        {
            "Description" : "Sample Search Controller",
            "Index" : 2,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(115)
        }, 
        {
            "Description" : "Sample Search Service",
            "Index" : 3,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(115)
        }, 
        {
            "Description" : "DB Connection",
            "Index" : 4,
            "StartTime" : NumberLong(0),
            "ExecutionTimeInMs" : NumberLong(3)
        }, 
        {
            "Description" : "Sample DB Call",
            "Index" : 5,
            "StartTime" : NumberLong(3),
            "ExecutionTimeInMs" : NumberLong(112)
        }
    ],
    "CreatedDate" : ISODate("2019-09-11T15:22:05.918-07:00"),
    "CreatedBy" : "Server"}
    
    public class QueryInterceptor
        {
            private readonly ILogger<QueryInterceptor> _logger;
            private string _query;
            private DateTimeOffset _startTime;
            public QueryInterceptor(ILogger<QueryInterceptor> logger)
            {
                _logger = logger;
            }
    
            [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
            public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
            {
                _query = command.CommandText;
                _startTime = startTime;
            }
    
            [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted")]
            public void OnCommandExecuted(object result, bool async)
            {
                var endTime = DateTimeOffset.Now;
                var queryTiming = (endTime - _startTime).TotalSeconds;
                _logger.LogInformation("\n" + "Executes " + "\n" + _query + "\n" + "in " + queryTiming + " seconds\n");
            }
    
            [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandError")]
            public void OnCommandError(Exception exception, bool async)
            {
                _logger.LogError(exception.Message);
            }
        }
    
    var loggerQueryInterceptor = services.GetService<ILogger<QueryInterceptor>>();
    var listener = context.GetService<DiagnosticSource>();
    (listener as DiagnosticListener).SubscribeWithAdapter(new QueryInterceptor(loggerQueryInterceptor));