簡體   English   中英

如何在 dbContext OnConfiguring 生成中插入自定義代碼?

[英]How to insert custom codes in dbContext OnConfiguring generation?

我嘗試遵循這個答案有沒有辦法將 mysql json 腳手架成自定義類型? 使自定義 json 類型轉換,它工作完美!

唯一困擾我的是我應該修改Context代碼手冊,插入builder => builder.UseNewtonsoftJson()

我想知道它是否可以在生成過程中,這將是一個救生員。

我受到上面提到的答案的啟發,並嘗試使其發揮作用。

我想要的是

public partial class spckContext : DbContext
{
    ...
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
            optionsBuilder
                .UseMySql("server=localhost;port=3306;database=spck;user=root;password=;treattinyasboolean=true", Microsoft.EntityFrameworkCore.ServerVersion.Parse("8.0.29-mysql"), builder => builder .UseNewtonsoftJson())
                .EnableSensitiveDataLogging()
                .LogTo(Log, LogFilter, DbContextLoggerOptions.DefaultWithLocalTime); // <= stucked here, how to pass method as parameter?
        }
    }
    ...
}

我將這些添加到我的項目中:

using System.Drawing;
using Microsoft.Extensions.Logging;
using Console = Colorful.Console;

public partial class spckContext
{
    public static void Log(string content)
    {
        Console.WriteLineFormatted(content, Color.Aqua);
    }
    
    public static bool LogFilter(Microsoft.Extensions.Logging.EventId id, LogLevel level)
    {
        switch (level)
        {
            case LogLevel.Trace:
            case LogLevel.Debug:
            case LogLevel.Warning:
            case LogLevel.None:
                return false;
            case LogLevel.Error:
            case LogLevel.Critical:
            case LogLevel.Information:
                return true;
            default:
                return false;
        }
    }
}
public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection services)
    {
        ...
        //Type Mapping
        services.AddSingleton<IRelationalTypeMappingSource, CustomTypeMappingSource>();    // <= add this line

        //Option Generator
        services.AddSingleton<IProviderConfigurationCodeGenerator, ProviderConfigurationCodeGenerator>();    // <= and this line
        ...
    }
}
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Scaffolding.Internal;
using Pomelo.EntityFrameworkCore.MySql.Storage.Internal;

public class ProviderConfigurationCodeGenerator : MySqlCodeGenerator 
{
    private static readonly MethodInfo _enableSensitiveDataLoggingMethodInfo = typeof(DbContextOptionsBuilder).GetRequiredRuntimeMethod(
        nameof(DbContextOptionsBuilder.EnableSensitiveDataLogging),
        typeof(bool));
    
    private static readonly MethodInfo _useNewtonJsonMethodInfo = typeof(MySqlJsonNewtonsoftDbContextOptionsBuilderExtensions).GetRequiredRuntimeMethod(
        nameof(MySqlJsonNewtonsoftDbContextOptionsBuilderExtensions.UseNewtonsoftJson),
        typeof(MySqlDbContextOptionsBuilder),
        typeof(MySqlCommonJsonChangeTrackingOptions));
    
    private static readonly MethodInfo _logToMethodInfo = typeof(DbContextOptionsBuilder).GetRequiredRuntimeMethod(
        nameof(DbContextOptionsBuilder.LogTo),
        typeof(Action<string>),
        typeof(Func<EventId, LogLevel, bool>),
        typeof(DbContextLoggerOptions?));
    
    private static readonly MethodInfo _logMethodInfo = typeof(spckContext).GetRequiredRuntimeMethod(
        nameof(spckContext.Log),
        typeof(string));
    
    private static readonly MethodInfo _logFilterMethodInfo = typeof(spckContext).GetRequiredRuntimeMethod(
        nameof(spckContext.LogFilter),
        typeof(EventId),
        typeof(LogLevel));

    private readonly ProviderCodeGeneratorDependencies _dependencies;
    private readonly IMySqlOptions _options;
    
    public ProviderConfigurationCodeGenerator(ProviderCodeGeneratorDependencies dependencies, IMySqlOptions options) : base(dependencies, options)
    {
        _dependencies = dependencies;
        _options = options;
    }
    
    public override MethodCallCodeFragment GenerateUseProvider(string connectionString, MethodCallCodeFragment? providerOptions)
    {
        if (providerOptions == null)
        {
            providerOptions = new MethodCallCodeFragment(_useNewtonJsonMethodInfo);
        }
        else
        {
            providerOptions = providerOptions.Chain(new MethodCallCodeFragment(_useNewtonJsonMethodInfo));
        }
        var fragment = base.GenerateUseProvider(connectionString, providerOptions); //works
        fragment = fragment.Chain(_enableSensitiveDataLoggingMethodInfo); //works
        fragment = fragment.Chain(_logToMethodInfo, 
            new NestedClosureCodeFragment("str", new MethodCallCodeFragment(_logMethodInfo)), // <= try and failed! it convert into `str => str.Log()`
            new MethodCall(_logFilterMethodInfo), // <= try and failed! error reported
            DbContextLoggerOptions.DefaultWithLocalTime);

        return fragment;
    }
}

public static class TypeExtensions
{
    public static MethodInfo GetRequiredRuntimeMethod(this Type type, string name, params Type[] parameters)
        => type.GetTypeInfo().GetRuntimeMethod(name, parameters)
           ?? throw new InvalidOperationException($"Could not find method '{name}' on type '{type}'");
}
using Microsoft.EntityFrameworkCore.Storage;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Storage.Internal;

public class CustomTypeMappingSource : MySqlTypeMappingSource
{
    public CustomTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies, IMySqlOptions options) : base(dependencies, relationalDependencies, options)
    {
    }

    protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
    {
        if (mappingInfo.ClrType == typeof(MethodCall))
        {
            return new MethodCallTypeMapping();
        }

        return base.FindMapping(mappingInfo);
    }
}
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Storage;

public class MethodCall
{
    public MethodInfo Method;

    public MethodCall(MethodInfo info)
    {
        Method = info;
    }
}

public class MethodCallTypeMapping : RelationalTypeMapping
{
    private const string DummyStoreType = "clrOnly";

    public MethodCallTypeMapping()
        : base(new RelationalTypeMappingParameters(new CoreTypeMappingParameters(typeof(MethodCall)), DummyStoreType))
    {
    }

    protected MethodCallTypeMapping(RelationalTypeMappingParameters parameters)
        : base(parameters)
    {
    }

    protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
        => new MethodCallTypeMapping(parameters);

    public override string GenerateSqlLiteral(object value)
        => throw new InvalidOperationException("This type mapping exists for code generation only.");

    public override Expression GenerateCodeLiteral(object value)
    {
        return value is MethodCall methodCall
            ? Expression.Call(methodCall.Method) // <= not working, how to fix this?
            : null;
    }
}

所以我的問題是如何使用方法參數制作MethodCallCodeFragment 我嘗試了谷歌,但找不到任何有價值的東西。 並且 MSDN 沒有此功能的示例代碼。

注入.UseNewtonsoftJson().EnableSensitiveDataLogging()調用可以簡單地通過為設計時服務提供您自己的IProviderCodeGeneratorPlugin實現來完成:

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection services)
    {
        services.AddSingleton<IProviderCodeGeneratorPlugin, CustomProviderCodeGeneratorPlugin>();
        services.AddEntityFrameworkMySqlJsonNewtonsoft();
    }
}

public class CustomProviderCodeGeneratorPlugin : IProviderCodeGeneratorPlugin
{
    private static readonly MethodInfo EnableSensitiveDataLoggingMethodInfo = typeof(DbContextOptionsBuilder).GetRequiredRuntimeMethod(
        nameof(DbContextOptionsBuilder.EnableSensitiveDataLogging),
        typeof(bool));

    private static readonly MethodInfo UseNewtonJsonMethodInfo = typeof(MySqlJsonNewtonsoftDbContextOptionsBuilderExtensions).GetRequiredRuntimeMethod(
        nameof(MySqlJsonNewtonsoftDbContextOptionsBuilderExtensions.UseNewtonsoftJson),
        typeof(MySqlDbContextOptionsBuilder),
        typeof(MySqlCommonJsonChangeTrackingOptions));

    public MethodCallCodeFragment GenerateProviderOptions()
        => new MethodCallCodeFragment(UseNewtonJsonMethodInfo);

    public MethodCallCodeFragment GenerateContextOptions()
        => new MethodCallCodeFragment(EnableSensitiveDataLoggingMethodInfo);
}

實現復雜的.LogTo(Log, LogFilter, DbContextLoggerOptions.DefaultWithLocalTime)調用並不那么簡單,因為 EF Core 用於將代碼生成表達式樹轉換為 C# 代碼的轉換邏輯充其量只是非常基本的。

實現虛擬類型映射以返回復雜表達式最終將不起作用,因為 EF Core 將無法翻譯 lambda 表達式content => LogTo(content)(id, level) => LogFilter(id, level) . 您可以嘗試欺騙它,但最簡單的解決方案是繞過整個表達式翻譯機制。

對於 output 任何字符串作為 C# 代碼,只需在您自己的實現中覆蓋ICSharpHelper.UnknownLiteral(object value)

這是一個完整的工作示例:

using System;
using System.Diagnostics;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate;

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection services)
    {
        services.AddSingleton<IProviderCodeGeneratorPlugin, CustomProviderCodeGeneratorPlugin>();
        services.AddSingleton<ICSharpHelper, CustomCSharpHelper>();
        services.AddEntityFrameworkMySqlJsonNewtonsoft();
    }
}

public static class TypeExtensions
{
    public static MethodInfo GetRequiredRuntimeMethod(this Type type, string name, params Type[] parameters)
        => type.GetTypeInfo().GetRuntimeMethod(name, parameters)
           ?? throw new InvalidOperationException($"Could not find method '{name}' on type '{type}'");
}

public class CustomProviderCodeGeneratorPlugin : IProviderCodeGeneratorPlugin
{
    private static readonly MethodInfo EnableSensitiveDataLoggingMethodInfo = typeof(DbContextOptionsBuilder).GetRequiredRuntimeMethod(
        nameof(DbContextOptionsBuilder.EnableSensitiveDataLogging),
        typeof(bool));

    private static readonly MethodInfo UseNewtonJsonMethodInfo = typeof(MySqlJsonNewtonsoftDbContextOptionsBuilderExtensions).GetRequiredRuntimeMethod(
        nameof(MySqlJsonNewtonsoftDbContextOptionsBuilderExtensions.UseNewtonsoftJson),
        typeof(MySqlDbContextOptionsBuilder),
        typeof(MySqlCommonJsonChangeTrackingOptions));
    
    private static readonly MethodInfo LogToMethodInfo = typeof(DbContextOptionsBuilder).GetRequiredRuntimeMethod(
        nameof(DbContextOptionsBuilder.LogTo),
        typeof(Action<string>),
        typeof(Func<EventId, LogLevel, bool>),
        typeof(DbContextLoggerOptions?));

    public MethodCallCodeFragment GenerateProviderOptions()
        => new MethodCallCodeFragment(UseNewtonJsonMethodInfo);

    public MethodCallCodeFragment GenerateContextOptions()
        => new MethodCallCodeFragment(EnableSensitiveDataLoggingMethodInfo)
            .Chain(GenerateLogToMethodCallCodeFragment());

    private MethodCallCodeFragment GenerateLogToMethodCallCodeFragment()
        => new MethodCallCodeFragment(
            LogToMethodInfo,
            new CSharpCodeGenerationExpressionString("Log"),
            new CSharpCodeGenerationExpressionString("LogFilter"),
            new CSharpCodeGenerationExpressionString("Microsoft.EntityFrameworkCore.Diagnostics.DbContextLoggerOptions.DefaultWithLocalTime"));
}

public class CSharpCodeGenerationExpressionString
{
    public string ExpressionString { get; }

    public CSharpCodeGenerationExpressionString(string expressionString)
        => ExpressionString = expressionString;
}

public class CustomCSharpHelper : CSharpHelper
{
    public CustomCSharpHelper(ITypeMappingSource typeMappingSource)
        : base(typeMappingSource)
    {
    }

    public override string UnknownLiteral(object value)
        => value is CSharpCodeGenerationExpressionString codeGenerationExpressionString
            ? codeGenerationExpressionString.ExpressionString
            : base.UnknownLiteral(value);
}

public partial class Context
{
    public static void Log(string content)
        => Console.Write(content);

    public static bool LogFilter(EventId id, LogLevel level)
        => level >= LogLevel.Information;
}

internal static class Program
{
    private static void Main()
    {
    }
}

我們基本上只是創建我們自己的名為CSharpCodeGenerationExpressionString的類型來保存我們想要 output 的 C# 代碼字符串,然后告訴CustomCSharpHelper.UnknownLiteral()方法按原樣返回它。

生成的OnConfiguring()方法如下所示:

public partial class Context : DbContext
{
    // ...

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
            optionsBuilder
                .UseMySql("server=127.0.0.1;port=3306;user=root;database=So73163124_01", Microsoft.EntityFrameworkCore.ServerVersion.Parse("8.0.29-mysql"), x => x.UseNewtonsoftJson())
                .EnableSensitiveDataLogging()
                .LogTo(Log, LogFilter, Microsoft.EntityFrameworkCore.Diagnostics.DbContextLoggerOptions.DefaultWithLocalTime);
        }
    }

    // ...
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM