简体   繁体   English

如何在 dbContext OnConfiguring 生成中插入自定义代码?

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

I try to follow this answer Is there a way to scaffold mysql json into custom type?我尝试遵循这个答案有没有办法将 mysql json 脚手架成自定义类型? to make custom json type convert, and it works perfect!使自定义 json 类型转换,它工作完美!

The only thing what bother me is that I should modify Context code manual, to insert builder => builder.UseNewtonsoftJson() .唯一困扰我的是我应该修改Context代码手册,插入builder => builder.UseNewtonsoftJson()

I am wonderring if it could be in the generation process, it would be a life saver.我想知道它是否可以在生成过程中,这将是一个救生员。

I am inspired by the answer which metioned above, and try to make it work.我受到上面提到的答案的启发,并尝试使其发挥作用。

What I want is我想要的是

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?
        }
    }
    ...
}

I add these to my project:我将这些添加到我的项目中:

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;
    }
}

So my question is how to make a MethodCallCodeFragment with method parameter?所以我的问题是如何使用方法参数制作MethodCallCodeFragment I tried google, but can't find anything valuable.我尝试了谷歌,但找不到任何有价值的东西。 And MSDN has no sample code for this feature.并且 MSDN 没有此功能的示例代码。

Injecting the .UseNewtonsoftJson() and .EnableSensitiveDataLogging() calls can simply be done by providing the design time services with your own IProviderCodeGeneratorPlugin implementation:注入.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);
}

Implementing the complex .LogTo(Log, LogFilter, DbContextLoggerOptions.DefaultWithLocalTime) call is not as straitforward, because the translation logic of EF Core for translating a code generation expression tree to C# code is very basic at best.实现复杂的.LogTo(Log, LogFilter, DbContextLoggerOptions.DefaultWithLocalTime)调用并不那么简单,因为 EF Core 用于将代码生成表达式树转换为 C# 代码的转换逻辑充其量只是非常基本的。

Implementing a dummy type mapping to return a complex expression will not work in the end, because EF Core will not be able to translate the lambda expressions of content => LogTo(content) and (id, level) => LogFilter(id, level) .实现虚拟类型映射以返回复杂表达式最终将不起作用,因为 EF Core 将无法翻译 lambda 表达式content => LogTo(content)(id, level) => LogFilter(id, level) . You could try to trick it, but the simplest solution is to just circumvent the whole expression translation mechanism.您可以尝试欺骗它,但最简单的解决方案是绕过整个表达式翻译机制。

To output any string as C# code, just override ICSharpHelper.UnknownLiteral(object value) in your own implementation.对于 output 任何字符串作为 C# 代码,只需在您自己的实现中覆盖ICSharpHelper.UnknownLiteral(object value)

Here is a fully working example:这是一个完整的工作示例:

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()
    {
    }
}

We basically just create our own type called CSharpCodeGenerationExpressionString to hold the C# code string that we want to output and then tell the CustomCSharpHelper.UnknownLiteral() method to return it as is.我们基本上只是创建我们自己的名为CSharpCodeGenerationExpressionString的类型来保存我们想要 output 的 C# 代码字符串,然后告诉CustomCSharpHelper.UnknownLiteral()方法按原样返回它。

The generated OnConfiguring() method looks like this:生成的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.

相关问题 如果所有配置都在 DbContext 的 OnConfiguring 方法中,如何使用 AddDbContextPool - How to use AddDbContextPool if all configuration in OnConfiguring method of DbContext 如何在没有任何硬编码连接字符串的情况下在我的 DbContext 中设置 OnConfiguring 方法? - How do I set the OnConfiguring method in my DbContext without any hardcoded connection string? 可以通过覆盖DbContext.OnConfiguring来配置提供程序 - A provider can be configured by overriding the DbContext.OnConfiguring 何时在 DbContext 构造函数与 OnConfiguring 中提供 DbContextOptions? - when to provide DbContextOptions in DbContext constructor vs OnConfiguring? 尝试使用 onconfiguring dbcontext 选项激活时无法解析服务类型 - Unable to resolve service for type while attempting to activate with onconfiguring dbcontext options Entity Framework Core DbContext OnConfiguring 不再存在 - Entity Framework Core DbContext OnConfiguring does not exist anymore 为什么我们在 DbContext 中使用 DependecyInjection 而不是 OnConfiguring 方法? - Why do we use DependecyInjection instead of OnConfiguring Method in the DbContext? 没有为此 DbContext 配置数据库提供程序。 可以通过覆盖 DbContext.OnConfiguring 来配置提供程序 - No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring 有什么方法可以检测何时从 Package 管理器控制台调用 DbContext.OnConfiguring()? - Any way to detect when DbContext.OnConfiguring() is being called from Package Manager Console? 在DbContext.OnConfiguring和AspCore Startup.ConfigureServices中都定义了optionsBuilder时,预期的结果是什么? - What are expected results when optionsBuilder is defined in both DbContext.OnConfiguring and AspCore Startup.ConfigureServices?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM