c# - इकाई फ़्रेमवर्क कोर: एकल डीबी संदर्भ आवृत्ति के लिए लॉग क्वेरी




unit-testing logging (2)

आप एक सीमित संदर्भ का उपयोग कर सकते हैं। मैंने पहले दो अलग-अलग संदर्भों को बनाने के लिए ईएफ कोइद का उपयोग किया था

ग्राहक बाध्य संदर्भ कोई भी प्रश्न लॉग नहीं करेगा

public class CustomerModelDataContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    public DbSet<PostalCode> PostalCodes { get; set; }

    public CustomerModelDataContext()
        : base("ConnectionName")
    {
        Configuration.LazyLoadingEnabled = true;
        Configuration.ProxyCreationEnabled = true;
        Database.SetInitializer<CustomerModelDataContext>(new Initializer<CustomerModelDataContext>());
        //Database.Log = message => DBLog.WriteLine(message);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

एपीआई बाध्य प्रसंग प्रश्नों को लॉग करेगा

public class ApiModelDataContext : DbContext
{
    public DbSet<ApiToken> ApiTokens { get; set; }

    public DbSet<ApiClient> ApiClients { get; set; }

    public DbSet<ApiApplication> ApiApplications { get; set; }

    public ApiModelDataContext() 
        : base("ConnectionName")
    {
        Configuration.LazyLoadingEnabled = true;
        Configuration.ProxyCreationEnabled = true;
        Database.SetInitializer<ApiModelDataContext>(new Initializer<ApiModelDataContext>());
        Database.Log = message => DBLog.WriteLine(message);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

यह वीएस में आउटपुट विंडो डीबग करने के लिए क्वेरी को लॉग करेगा

public static class DBLog
{
    public static void WriteLine(string message)
    {
        Debug.WriteLine(message);
    }
}

ईएफ कोर (या उस बात के लिए किसी भी ओआरएम का उपयोग करना) मैं अपने सॉफ़्टवेयर में कुछ ऑपरेशन के दौरान डेटाबेस को ORM के लिए किए जाने वाले प्रश्नों की संख्या का ट्रैक रखना चाहता हूं।

मैंने पहले पायथन के तहत एसक्यूएललेमी का उपयोग किया है, और उस स्टैक पर यह सेट अप करने के लिए आसान आसान है। मुझे आम तौर पर यूनिट परीक्षण होते हैं जो एक परिदृश्य के लिए किए गए क्वेरी की संख्या पर निर्भर करते हैं, इन-मेमरी SQLite डेटाबेस के खिलाफ

अब मैं ईएफ कोर का उपयोग करके एक ही चीज़ करना चाहता हूं, और लॉगिंग दस्तावेज़ीकरण को देखा है।

मेरे परीक्षण सेटअप कोड में मैं दस्तावेज़ीकरण कहता हूं:

using (var db = new BloggingContext())
{
    var serviceProvider = db.GetInfrastructure<IServiceProvider>();
    var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
    loggerFactory.AddProvider(new MyLoggerProvider());
}

लेकिन मुझे लगता है कि मुझे संदेह है कि निम्नलिखित के परिणाम भी हैं (डॉक्स से भी):

आपको केवल एक संदर्भ प्रसंग के साथ लॉगगर को पंजीकृत करना होगा। एक बार जब आप इसे पंजीकृत कर लेते हैं, तो इसका उपयोग उसी ऐप डॉक्स में संदर्भ के सभी अन्य उदाहरणों के लिए किया जाएगा।

मेरे परीक्षणों में जो समस्याएं दिखाई देती हैं I इंगित करता है कि मेरे लॉगर कार्यान्वयन को कई संदर्भों में साझा किया गया है (यह डॉक्स के अनुसार है जैसा मैंने पढ़ा है)। और चूंकि ए) मेरा परीक्षण धावक समानांतर में परीक्षण चलाता है और बी) मेरी पूरी परीक्षा सूट डीबी के सैकड़ों संदर्भ बनाता है - यह बहुत अच्छी तरह से काम नहीं करता है।

प्रश्न / मुद्दों:

  • क्या मैं संभव करना चाहता हूं?
  • Ie मैं एक db संदर्भ के साथ एक लकड़हारा रजिस्टर कर सकते हैं कि केवल उस डीबी संदर्भ आवृत्ति के लिए प्रयोग किया जाता है?
  • क्या मैं क्या करने की कोशिश कर रहा हूं पूरा करने के अन्य तरीके हैं?

इसे पढ़ें: docs.microsoft.com/en-us/ef/core/miscellaneous/logging

यह बहुत महत्वपूर्ण है कि अनुप्रयोग प्रत्येक संदर्भ आवृत्ति के लिए एक नया इलोगजरफैक्चरल उदाहरण नहीं बनाते। ऐसा करने से मेमोरी रिसाव और खराब प्रदर्शन का परिणाम होगा .1

अगर आप स्थिर विस्थापन (उदाहरण के लिए कंसोल) में लॉग इन करना चाहते हैं, तो ईलाजा का उत्तर काम करता है, लेकिन अगर आप पहले कस्टम बफ़र्स पर लॉग इन करना चाहते हैं, तो प्रत्येक डीबीसीओन्टेन्स्ट अपने स्वयं के बफ़र को लॉग संदेश एकत्र करता है (और यह कि आप बहुउसेसर सेवा में क्या करना चाहते हैं) , तो यूपीएसएसएस ... जहां ईएफ 6 को एक पंक्ति में एक लॉग इवेंट में सदस्यता लेने के लिए सरल समाधान था, अब इस तरह अपने लॉगिंग को इंजेक्ट करने के लिए:

        var messages = new List<string>();
        Action<string> verbose = (text) => {
            messages.Add(text);
        }; // add logging message to buffer

        using (var dbContext = new MyDbContext(BuildOptionsBuilder(connectionString, inMemory), verbose))
        {
             //..
        };

आपको पूलिंग राक्षस लिखना चाहिए

पी एस कोई व्यक्ति एएफ कोर आर्किटेक्ट को बताता है कि उन्हें डी की गलत समझ है और उन फैंसी सर्विस लोकेटर जिन्हें वे एएसपी से "कंटेनर" और धाराप्रवाह उपयोग XXX कहते हैं। कॉयर, कन्स्ट्रक्टर से "अशिष्ट" डि की जगह नहीं ले सकता कम से कम लॉग फ़ंक्शन सामान्य रूप से इंजेक्शन होना चाहिए।

पी पी एस यह भी पढ़ें https://github.com/aspnet/EntityFrameworkCore/issues/9613 इसका अर्थ है कि लकड़हारा ब्रोकर को इनममेरी डेटा प्रदाता तक पहुंचने में जोड़ा गया। यह एक अमूर्त लीक है जैसा कि यह है। ईएफ कोर भयानक वास्तुकला है।

आईलागजरफ्रिटर पूलिंग कोड:

public class StatefullLoggerFactoryPool
{
    public static readonly StatefullLoggerFactoryPool Instance = new StatefullLoggerFactoryPool(()=> new StatefullLoggerFactory());
    private readonly Func<StatefullLoggerFactory> construct;
    private readonly ConcurrentBag<StatefullLoggerFactory> bag = new ConcurrentBag<StatefullLoggerFactory>();

    private StatefullLoggerFactoryPool(Func<StatefullLoggerFactory> construct) =>
        this.construct = construct;

    public StatefullLoggerFactory Get(Action<string> verbose, LoggerProviderConfiguration loggerProviderConfiguration)
    {
        if (!bag.TryTake(out StatefullLoggerFactory statefullLoggerFactory))
            statefullLoggerFactory = construct();
        statefullLoggerFactory.LoggerProvider.Set(verbose, loggerProviderConfiguration);
        return statefullLoggerFactory;
    }

    public void Return(StatefullLoggerFactory statefullLoggerFactory)
    {
        statefullLoggerFactory.LoggerProvider.Set(null, null);
        bag.Add(statefullLoggerFactory);
    }
}

 public class StatefullLoggerFactory : LoggerFactory
{
    public readonly StatefullLoggerProvider LoggerProvider;
    internal StatefullLoggerFactory() : this(new StatefullLoggerProvider()){}

    private StatefullLoggerFactory(StatefullLoggerProvider loggerProvider) : base(new[] { loggerProvider }) =>
        LoggerProvider = loggerProvider;
}

public class StatefullLoggerProvider : ILoggerProvider
{
    internal LoggerProviderConfiguration loggerProviderConfiguration;
    internal Action<string> verbose;
    internal StatefullLoggerProvider() {}

    internal void Set(Action<string> verbose, LoggerProviderConfiguration loggerProviderConfiguration)
    {
        this.verbose = verbose;
        this.loggerProviderConfiguration = loggerProviderConfiguration;
    }

    public ILogger CreateLogger(string categoryName) =>
        new Logger(categoryName, this);

    void IDisposable.Dispose(){}
}

public class MyDbContext : DbContext
{
    readonly Action<DbContextOptionsBuilder> buildOptionsBuilder;
    readonly Action<string> verbose;
    public MyDbContext(Action<DbContextOptionsBuilder> buildOptionsBuilder, Action<string> verbose=null): base()
    {
        this.buildOptionsBuilder = buildOptionsBuilder;
        this.verbose = verbose;
    }

     private Action returnLoggerFactory;
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (verbose != null)
        {
            var loggerFactory = StatefullLoggerFactoryPool.Instance.Get(verbose, new LoggerProviderConfiguration { Enabled = true, CommandBuilderOnly = false });
            returnLoggerFactory = () => StatefullLoggerFactoryPool.Instance.Return(loggerFactory);
            optionsBuilder.UseLoggerFactory(loggerFactory);
        }
        buildOptionsBuilder(optionsBuilder);
    }

    // NOTE: not threadsafe way of disposing
    public override void Dispose()
    {
        returnLoggerFactory?.Invoke();
        returnLoggerFactory = null;
        base.Dispose();
    }
}

    private static Action<DbContextOptionsBuilder> BuildOptionsBuilder(string connectionString, bool inMemory)
    {
        return (optionsBuilder) =>
        {
            if (inMemory)
                optionsBuilder.UseInMemoryDatabase(
                  "EfCore_NETFramework_Sandbox"
                );
            else
                //Assembly.GetAssembly(typeof(Program))
                optionsBuilder.UseSqlServer(
                        connectionString,
                        sqlServerDbContextOptionsBuilder => sqlServerDbContextOptionsBuilder.MigrationsAssembly("EfCore.NETFramework.Sandbox")
                        );
        };
    }

class Logger : ILogger
{
    readonly string categoryName;
    readonly StatefullLoggerProvider statefullLoggerProvider;
    public Logger(string categoryName, StatefullLoggerProvider statefullLoggerProvider)
    {
        this.categoryName = categoryName;
        this.statefullLoggerProvider = statefullLoggerProvider;
    }

    public IDisposable BeginScope<TState>(TState state) =>
        null;

    public bool IsEnabled(LogLevel logLevel) =>
        statefullLoggerProvider?.verbose != null;

    static readonly List<string> events = new List<string> {
            "Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosing",
            "Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosed",
            "Microsoft.EntityFrameworkCore.Database.Command.DataReaderDisposing",
            "Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpened",
            "Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpening",
            "Microsoft.EntityFrameworkCore.Infrastructure.ServiceProviderCreated",
            "Microsoft.EntityFrameworkCore.Infrastructure.ContextInitialized"
        };

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (statefullLoggerProvider?.verbose != null)
        {
            if (!statefullLoggerProvider.loggerProviderConfiguration.CommandBuilderOnly ||
                (statefullLoggerProvider.loggerProviderConfiguration.CommandBuilderOnly && events.Contains(eventId.Name) ))
            {
                var text = formatter(state, exception);
                statefullLoggerProvider.verbose($"MESSAGE; categoryName={categoryName} eventId={eventId} logLevel={logLevel}" + Environment.NewLine + text);
            }
        }
    }
}




xunit