繁体   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