[英]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.