بررسی روش مشاهده خروجی SQL حاصل از کوئری‌های Entity framework Core
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: سه دقیقه

هنوز تا Entity framework Core 1.1، مفهوم interceptors موجود در EF 6.x پیاده سازی نشده‌است. اما شبیه به مفاهیم «ارتقاء به ASP.NET Core 1.0 - قسمت 17 - بررسی فریم ورک Logging»، در EF Core نیز زیرساختی جهت مشاهده‌ی SQL نهایی تولیدی وجود دارد.


ایجاد یک ثبت کننده‌ی وقایع EF Core

مرحله‌ی اول مشاهده‌ی خروجی‌های نهایی EF Core، پیاده سازی اینترفیس ILoggerProvider است که در آن قرار است وهله‌ی از نوع ILogger بازگشت داده شود. به همین جهت یک کلاس تو در توی خصوصی را در اینجا مشاهده می‌کنید که اینترفیس ILogger را نیز پیاده سازی کرده‌است:
using System;
using Microsoft.Extensions.Logging;

namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        public ILogger CreateLogger(string categoryName)
        {
            return new MyLogger();
        }

        public void Dispose()
        { }

        private class MyLogger : ILogger
        {
            public bool IsEnabled(LogLevel logLevel)
            {
                return true;
            }

            public void Log<TState>(
                        LogLevel logLevel, 
                        EventId eventId, 
                        TState state, 
                        Exception exception, 
                        Func<TState, Exception, string> formatter)
            {
                //File.AppendAllText(@"C:\temp\log.txt", formatter(state, exception));
                Console.WriteLine("");
                Console.WriteLine(formatter(state, exception));
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return null;
            }
        }
    }
}
در اینجا خروجی‌هایی نهایی توسط Console.WriteLine نمایش داده شده‌اند و مناسب برنامه‌های کنسول هستند و یا می‌توان برای مثال توسط File.AppendAllText، اطلاعات رسیده را در یک فایل نیز ذخیره کرد.
در متد Log:
- پارامتر logLevel، سطح اهمیت اطلاعات رسیده را به همراه دارد. برای مثال اطلاعات است یا خطا؟
برای مثال شاید نیاز به ذخیره سازی اطلاعاتی با سطح‌های بحرانی، خطا و یا اخطار در یک بانک اطلاعاتی وجود داشته باشد:
   if (logLevel == LogLevel.Critical || logLevel == LogLevel.Error || logLevel == LogLevel.Warning)
- eventId: نوع رخداد رسیده را مشخص می‌کند.
- state: می‌تواند هر نوع شیءایی، حاوی اطلاعات وضعیت رخداد رسیده باشد.
- exception: بیانگر استثنای احتمالی رخ داده است.
- formatter: کار آن تولید یک رشته‌ی قابل خواندن، توسط اطلاعات حالت و استثناء است.


معرفی Logger تهیه شده به برنامه

پس از تهیه‌ی Logger فوق، جهت معرفی آن به یک برنامه‌ی کنسول، می‌توان به صورت ذیل عمل کرد:
using (var db = new MyContext())
{
   var loggerFactory = (ILoggerFactory)db.GetInfrastructure().GetService(typeof(ILoggerFactory));
   loggerFactory.AddProvider(new MyLoggerProvider());
 }
این ثبت تنها باید یکبار در آغاز برنامه انجام شود و پس از آن تمام وهله‌ی دیگر Context از آن استفاده خواهند کرد.

در برنامه‌های ASP.NET Core، کار معرفی MyLoggerProvider در متد Configure کلاس آغازین برنامه انجام می‌شود:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddProvider(new MyLoggerProvider());


اختصاصی سازی ثبت وقایع رسیده

کلاس MyLoggerProvider، هر نوع اطلاعات داخلی EF Core را نیز لاگ می‌کند. اگر هدف صرفا بررسی خروجی SQL نهایی تولیدی است، می‌توان در متد ذیل:
public ILogger CreateLogger(string categoryName)
بر اساس categoryName رسیده، یا new MyLogger را بازگشت داده و یا یک NullLogger.Instance را که کاری را انجام نمی‌دهد. به این ترتیب می‌توان کار فیلتر کردن اطلاعات رسیده را انجام داد.
برای این منظور، ابتدای Logger تهیه شده چنین شکلی را پیدا می‌کند:
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
using System.Linq;
using System;
 
namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        private static readonly string[] _categories =
            {
                typeof(RelationalCommandBuilderFactory).FullName,
                typeof(SqlServerConnection).FullName
            };
        public ILogger CreateLogger(string categoryName)
        {
            if (_categories.Contains(categoryName))
            {
                return new MyLogger();
            }
 
            return NullLogger.Instance;
        }
  • #
    ‫۶ سال و ۱۰ ماه قبل، چهارشنبه ۱۷ آبان ۱۳۹۶، ساعت ۱۳:۰۴
    به روز رسانی: روش توصیه شده‌ی مخصوص EF Core 2.0 جهت Log خروجی EF

    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Logging.Console;
    
    namespace EFLogging
    {
        public class BloggingContextWithFiltering : DbContext
        {
            // It is very important that applications do not create a new ILoggerFactory instance for each context instance. 
            // Doing so will result in a memory leak and poor performance.
            public static readonly LoggerFactory MyLoggerFactory
                = new LoggerFactory(new[]
                {
                    new ConsoleLoggerProvider((category, level)
                        => category == DbLoggerCategory.Database.Command.Name
                           && level == LogLevel.Information, true)
                });
    
            public DbSet<Blog> Blogs { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
                => optionsBuilder
                    .UseLoggerFactory(MyLoggerFactory) // Warning: Do not create a new ILoggerFactory instance each time
                    .UseSqlServer(
                        @"Server=(localdb)\mssqllocaldb;Database=EFLogging;Trusted_Connection=True;ConnectRetryCount=0");
        }
    }
    UseLoggerFactory روش توصیه شده‌ی EF Core 2.0 است و طول عمر وهله‌ی ارسالی به آن باید singleton باشد تا از بروز نشتی حافظه جلوگیری کند.
    • #
      ‫۵ سال و ۷ ماه قبل، چهارشنبه ۲۴ بهمن ۱۳۹۷، ساعت ۱۷:۲۱
      روش ارتقاء به EF Core 2.2
          public class BloggingContext : DbContext
          {
              public BloggingContext()
              { }
      
              public BloggingContext(DbContextOptions options)
                  : base(options)
              { }
      
              public DbSet<Blog> Blogs { get; set; }
              public DbSet<Post> Posts { get; set; }
      
              protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
              {
                  if (!optionsBuilder.IsConfigured)
                  {
                      optionsBuilder.EnableSensitiveDataLogging();
                      optionsBuilder.UseSqlServer(@"...");
                      optionsBuilder.ConfigureWarnings(warnings =>
                      {
                          warnings.Log(CoreEventId.IncludeIgnoredWarning);
                          warnings.Log(RelationalEventId.QueryClientEvaluationWarning);
                      });
                      optionsBuilder.UseLoggerFactory(GetLoggerFactory());
                  }
              }
      
              private ILoggerFactory GetLoggerFactory()
              {
                  IServiceCollection serviceCollection = new ServiceCollection();
                  serviceCollection.AddLogging(builder =>
                         builder.AddConsole()
                                //.AddFilter(category: DbLoggerCategory.Database.Command.Name, level: LogLevel.Information));
                                .AddFilter(level => true)); // log everything
                  return serviceCollection.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
              }
          }
      در اینجا برای لاگ کردن خروجی EF می‌توان یک ILoggerFactory را که توسط متد AddFilter، سطوح لاگ کردن آن مشخص می‌شود، به UseLoggerFactory ارسال کرد.
      • #
        ‫۵ سال و ۶ ماه قبل، دوشنبه ۲۰ اسفند ۱۳۹۷، ساعت ۱۱:۵۳
        یک نکته‌ی تکمیلی: روش مشاهده‌ی مقدار پارامترها در لاگ‌های SQL
        در حالت معمولی، خروجی SQL لاگ شده‌ی توسط EF Core به صورت زیر است:
        Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (41ms) 
        [Parameters=[@__id_0='?' (DbType = Int32)], 
        CommandType='Text', CommandTimeout='30']
         SELECT TOP(2) [m].[Id], [m].[Address], [m].[City], [m].[Email], [m].[Name], [m].[Phone], [m].[PostalCode], [m].[State] FROM [Contact] AS [m] WHERE [m].[Id] = @__id_0
        برای مشاهده‌ی مقدار پارامترها نیاز است SensitiveDataLogging را فعال کرد:
        services.AddDbContext<ContactsContext>(options => { 
          options.UseSqlServer(Configuration["Data:ContactsContext:ConnectionString"]); 
          options.EnableSensitiveDataLogging(); 
        });
        اینبار خروجی لاگ شده، مقدار پارامترها را نیز به همراه دارد:
        Microsoft.EntityFrameworkCore.Database.Command[20100] Executing DbCommand
         [Parameters=[@__id_0='1' (Nullable = true)], 
        CommandType='Text', CommandTimeout='30'] 
        SELECT TOP(2) [m].[Id], [m].[Address], [m].[City], [m].[Email], [m].[Name], [m].[Phone], [m].[PostalCode], [m].[State] FROM [Contact] AS [m] WHERE [m].[Id] = @__id_0
  • #
    ‫۵ سال و ۴ ماه قبل، پنجشنبه ۵ اردیبهشت ۱۳۹۸، ساعت ۱۳:۵۱
    یک نکته‌ی تکمیلی: یافتن ساده‌تر کوئری‌ها در لاگ نهایی

    پس از فعالسازی لاگ کردن عملیات در EF Core، مشکلی که در بین انبوهی از اطلاعات لاگ شده رخ می‌دهد این است که این کوئری خاص دقیقا مرتبط به کدام قسمت برنامه است؟ برای یافتن ساده‌تر کوئری‌ها و ارتباط دادن آن‌ها با کدهای قسمت‌های مختلف برنامه، در EF Core 2.2، قابلیتی تحت عنوان TagWith افزوده شده‌است:
    var orders = 
          (from b in context.Orders.TagWith(@"List of top 10 orders") 
          orderby b.Price descending 
          select b).Take(10).ToList();
    با این خروجی
    -- List of top 10 orders
     
    SELECT TOP(@__p_1) [b].[Id], [b].[Name], [b].[Price]
     FROM [Orders] AS [b] 
    ORDER BY [b].[Price] DESC
    که در آن عبارت TagWith در ابتدای لاگ درج شده‌است و به این ترتیب ارتباط دادن کدهای برنامه با خروجی لاگ شده و یافتن معادل‌ها، ساده‌تر می‌شود.
  • #
    ‫۵ سال و ۳ ماه قبل، دوشنبه ۲۰ خرداد ۱۳۹۸، ساعت ۱۶:۰۹
    ارتقاء به EF Core 3.0
    در EF Core 3.0، لاگ خروجی SQL تولیدی، از سطح Info به سطح Debug تغییر کرده‌است تا نویز سطح Info کاهش پیدا کند. اگر می‌خواهید به همان حالت قبلی برگردید، از تنظیم زیر استفاده کنید:
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(connectionString)
            .ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuting, LogLevel.Info)));
  • #
    ‫۳ سال و ۷ ماه قبل، شنبه ۴ بهمن ۱۳۹۹، ساعت ۱۳:۱۳
    تغییرات Logging در EF Core 5x

    1- متد استاندارد ToQueryString، امکان دسترسی به SQL متناظر با یک کوئری را میسر کرده‌است:
    var sql = ctx.Artists.Where(a => a.Name == "name 1").ToQueryString();
    2- متد LogTo، دریافت خروجی از لاگ عملیات صورت گرفته‌ی توسط EF Core را ساده می‌کند:
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.LogTo(Console.WriteLine);
    که در حقیقت خلاصه شده‌ی کدهای زیر است:
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.LogTo(message => Console.WriteLine(message));
    اطلاعات بیشتر