gpt4 book ai didi

c# - 如何将自定义函数转换为 Entity Framework Core 3.1 的 sql 表达式

转载 作者:行者123 更新时间:2023-12-05 02:06:38 28 4
gpt4 key购买 nike

我正在寻找一些关于如何解决我的问题的指导/帮助,因为我的想法已经用完了。我正在尝试采用自定义扩展函数并使用 Linq to Entities 通过 Entity Framework (EF Core 3.1) 将其传递给数据库,但无论我做什么,我都会收到“{method} 无法翻译”错误。我尝试使用此链接将 HasDbFunction 与 HasTranslation 一起使用 Thinktecture - Entity Framework Core - Custom Functions (using HasDbFunction)无济于事。我还尝试使用此链接注册自定义 DbCommandInterceptor Medium - Interception in Entity Framework Core而且它永远不会遇到断点或记录我的任何调试语句。我不确定接下来要尝试什么,但我正在寻求有关我做错了什么的帮助或有关接下来要研究什么的指导。

对于我的问题的某些上下文,我使用下面的类设置:

namespace Rebates.Models
{
public class Rebate
{
public int Id { get; set; }
public string ProductName { get; set; }
public ActiveDateRange ActiveDateRange { get; set; }

public decimal Discount { get; set; }
}

public class ActiveDateRange
{
public int StartMonth { get; set; }
public int EndMonth { get; set; }
}
}

数据库上下文:

namespace Rebates.DB
{
public class RebateContext : DbContext
{
public DbSet<Rebate> Rebates { get; set; }
}

protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("Server=(local);Database=Rebates;Trusted_Connection=True;");
}
}

扩展方法我想变成一条SQL语句:

namespace Rebates.ExtensionMethods
{
public static class Extensions
{
public static bool IsActive(this Rebate rebate, DateTime date)
{
return date.Month >= rebate.ActiveDateRange.StartMonth && date.Month <= rebate.ActiveDateRange.EndMonth;
}
}
}

我试图在我的程序中调用:

using (var db = new RebateContext())
{
var rebates = db.Rebates.Where(x => x.IsActive(DateTime.Now));
}

收到错误:

System.InvalidOperationException: 'The LINQ expression 'DbSet.Where(c => EF.Property(c, "ActiveDateRange").IsActive(DateTime.Now))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

我删除了所有失败的代码,希望能使这个更清晰,但如果有帮助,我也可以发布我失败的尝试,我只是不想让这个已经很长的请求变得困惑。我已经阅读了一些关于如何构建表达式树以及它如何在 EF Core 中进行转换的内容,但老实说,我不知道我什至会在哪里拦截所述表达式树以修改它以实现我的目标。此处的任何帮助将不胜感激。

最佳答案

一个有趣的问题,我想解决它以便我也可以使用它。

首先我们需要在查询编译管道的早期添加一个钩子(Hook)。请注意,如果 EF Core 的内部结构发生变化,此位将来可能会中断;

public class QueryCompilationFactory : IQueryCompilationContextFactory
{
private readonly QueryCompilationContextDependencies dependencies;

public QueryCompilationFactory(QueryCompilationContextDependencies dependencies)
{
this.dependencies = dependencies;
}

public QueryCompilationContext Create(bool async) => new QueryCompilation(dependencies, async);
}

public class QueryCompilation : QueryCompilationContext
{
public QueryCompilation(QueryCompilationContextDependencies dependencies, bool async) : base(dependencies, async)
{
}

public override Func<QueryContext, TResult> CreateQueryExecutor<TResult>(Expression query)
{
// TODO, modify the query here
return base.CreateQueryExecutor<TResult>(query);
}
}

// in startup...
services.AddDbContextPool<..>(o =>
{
o.ReplaceService<IQueryCompilationContextFactory, QueryCompilationFactory>();
// ...
}

好的,现在我们需要一种通用的方法来用等效表达式替换函数调用。这样,查询管道的其余部分就可以为我们将其转换为 sql。让我们编写一个 ExpressionVisitor,它通过内联另一个 LambdaExpression 来替换方法调用。在出现的任何地方用调用参数替换 lambda 参数。那么我们就可以用visitor来代替上面的TODO了。

private static Dictionary<MethodInfo, LambdaExpression> replacementExpressions = new Dictionary<MethodInfo, LambdaExpression>();

public static void ReplaceMethod<T>(T method, Expression<T> replacement) where T : Delegate =>
replacementExpressions.Add(method.Method, replacement);

public class ArgumentVisitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> parameters;
public ArgumentVisitor(Dictionary<ParameterExpression, Expression> parameters)
{
this.parameters = parameters;
}

protected override Expression VisitParameter(ParameterExpression node)
{
if (parameters.TryGetValue(node, out var replacement))
return replacement;
return base.VisitParameter(node);
}
}

public class MethodVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (replacementExpressions.TryGetValue(node.Method, out var lambda))
{
var args = new Dictionary<ParameterExpression, Expression>();
for (var i = 0; i < lambda.Parameters.Count; i++)
args[lambda.Parameters[i]] = node.Arguments[i];
return new ArgumentVisitor(args).Visit(lambda.Body);
}
return base.VisitMethodCall(node);
}
}

public override Func<QueryContext, TResult> CreateQueryExecutor<TResult>(Expression query)
{
query = new MethodVisitor().Visit(query);
return base.CreateQueryExecutor<TResult>(query);
}

现在我们可以定义我们想要的任何静态方法,并定义一个替换表达式以在将该方法调用转换为 sql 时使用。

public static bool IsActive(this Rebate rebate, DateTime date) =>
date.Month >= rebate.ActiveDateRange.StartMonth && date.Month <= rebate.ActiveDateRange.EndMonth;

static Extensions(){
QueryCompilation.ReplaceMethod<Func<Rebate,DateTime,bool>>(IsActive, (Rebate rebate, DateTime date) =>
date.Month >= rebate.ActiveDateRange.StartMonth && date.Month <= rebate.ActiveDateRange.EndMonth);
}

关于c# - 如何将自定义函数转换为 Entity Framework Core 3.1 的 sql 表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62687811/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com