如何在 EF Core 中使用 DbFunction 翻译?

ale*_*291 6 c# mysql full-text-search linq-to-sql entity-framework-core

我正在寻找类似EF.Functions.FreeText在 SQL Server 中实现但使用MATCH...AGAINSTMySQL 语法的东西。

这是我当前的工作流程:
AspNetCore 2.1.1
EntityFrameworkCore 2.1.4
Pomelo.EntityFrameworkCore.MySql 2.1.4

问题是 MySQL 使用两个函数,我不知道如何解释它并DbFunction分隔每个函数的参数。有谁知道如何实现这个?

这应该是 Linq 语法:

query.Where(x => DbContext.FullText(new[] { x.Col1, x.Col2, x.Col3 }, "keywords"));
Run Code Online (Sandbox Code Playgroud)

这应该是 SQL 中生成的结果:

SELECT * FROM t WHERE MATCH(`Col1`, `Col2`, `Col3`) AGAINST('keywords');
Run Code Online (Sandbox Code Playgroud)

我尝试使用该HasTranslation函数遵循以下示例: https://github.com/aspnet/EntityFrameworkCore/issues/11295#issuecomment-511440395 https://github.com/aspnet/EntityFrameworkCore/issues/10241#issuecomment- 342989770

注意:我知道可以用 来解决FromSql,但这不是我想要的。

Paw*_*err 7

ROW_NUMBER当我需要EF Core 的支持时,您的用例与我的非常相似。

\n\n

例子:

\n\n
// gets translated to\n// ROW_NUMBER() OVER(PARTITION BY ProductId ORDER BY OrderId, Count)\nDbContext.OrderItems.Select(o => new {\n  RowNumber = EF.Functions.RowNumber(o.ProductId, new {\n    o.OrderId,\n    o.Count\n  })\n})\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用匿名类而不是数组

\n\n

您要做的第一件事就是从使用数组切换到匿名类,即将调用从

\n\n

DbContext.FullText(new[] { x.Col1, x.Col2, x.Col3 }, "keywords")

\n\n

\n\n

DbContext.FullText(new { x.Col1, x.Col2, x.Col3 }, "keywords")

\n\n

参数的排序顺序将保持在查询中定义的那样, \ni.enew { x.Col1, x.Col2 }将被转换为Col1, Col2\nnew { x.Col2, x.Col1 }Col2, Col1

\n\n

您甚至可以执行以下操作:new { x.Col1, _ = x.Col1, Foo = "bar" }that is will be translated to Col1, Col1, \'bar\'.

\n\n

实施定制IMethodCallTranslator

\n\n

如果您需要一些提示,那么您可以查看我在Azure DevOps 上的代码:RowNumber 支持,或者如果您可以等待几天,那么我将提供有关自定义函数实现的博客文章。

\n\n

更新(2019 年 7 月 31 日)

\n\n

博客文章:

\n\n\n\n

更新(2019 年 7 月 27 日)

\n\n

感谢下面的评论,我发现需要进行一些澄清。

\n\n

1)正如下面的评论所指出的,还有另一种方法。使用 EF HasDbFunction,我可以节省一些输入,例如使用 EF 注册翻译器的代码,但我仍然需要 ,RowNumberExpression因为该函数有 2 组参数( forPARTITION BYORDER BY),而现有的SqlFunctionExpression不支持这一点。(或者我错过了什么?)我选择这种方法的原因IMethodCallTranslator是因为我希望在设置过程中完成此功能的配置,DbContextOptionsBuilder而不是在OnModelCreating. 也就是说,它\xe2\x80\x99是我的个人喜好。

\n\n

最后,线程创建者HasDbFunction也可以用来实现所需的功能。就我而言,代码如下所示:

\n\n
// OnModelCreating\n  var methodInfo = typeof(DemoDbContext).GetMethod(nameof(DemoRowNumber));\n\n  modelBuilder.HasDbFunction(methodInfo)\n            .HasTranslation(expressions => {\n                 var partitionBy = (Expression[])((ConstantExpression)expressions.First()).Value;\n                 var orderBy = (Expression[])((ConstantExpression)expressions.Skip(1).First()).Value;\n\n                 return new RowNumberExpression(partitionBy, orderBy);\n});\n\n// the usage with this approach is identical to my current approach\n.Select(c => new {\n    RowNumber = DemoDbContext.DemoRowNumber(\n                                  new { c.Id },\n                                  new { c.RowVersion })\n    })\n
Run Code Online (Sandbox Code Playgroud)\n\n

2)匿名类型可以\xe2\x80\x99t强制其成员的类型,因此如果使用例如而不是调用该函数,您可能会得到运行时integer异常string。尽管如此,它仍然是有效的解决方案。根据您所服务的客户,解决方案可能或多或少可行,最终决定权在于客户。不提供任何替代方案也是一种可能的解决方案,但不是令人满意的解决方案。\n特别是,如果不希望使用 SQL(因为编译器对您的支持更少),那么运行时异常可能是一个很好的折衷方案。

\n\n

但是,如果妥协仍然不可接受,那么我们可以研究如何添加对阵列的支持。\n第一种方法可能是执行自定义IExpressionFragmentTranslator\xe2\x80\x9credirect\xe2\x80\x9d 来对我们进行数组处理。

\n\n
\n

请注意,它只是一个原型,需要更多的调查/测试:-)

\n
\n\n
// to get into EF pipeline\npublic class DemoArrayTranslator : IExpressionFragmentTranslator\n{\n    public Expression Translate(Expression expression)\n    {\n       if (expression?.NodeType == ExpressionType.NewArrayInit)\n       {\n          var arrayInit = (NewArrayExpression)expression;\n          return new DemoArrayInitExpression(arrayInit.Type, arrayInit.Expressions);\n       }\n\n       return null;\n    }\n}\n\n// lets visitors visit the array-elements\npublic class DemoArrayInitExpression : Expression\n{\n   private readonly ReadOnlyCollection<Expression> _expressions;\n\n   public override Type Type { get; }\n   public override ExpressionType NodeType => ExpressionType.Extension;\n\n   public DemoArrayInitExpression(Type type, \n           ReadOnlyCollection<Expression> expressions)\n   {\n      Type = type ?? throw new ArgumentNullException(nameof(type));\n      _expressions = expressions ?? throw new ArgumentNullException(nameof(expressions));\n   }\n\n   protected override Expression Accept(ExpressionVisitor visitor)\n   {\n      var visitedExpression = visitor.Visit(_expressions);\n      return NewArrayInit(Type.GetElementType(), visitedExpression);\n   }\n}\n\n// adds our DemoArrayTranslator to the others\npublic class DemoRelationalCompositeExpressionFragmentTranslator \n      : RelationalCompositeExpressionFragmentTranslator\n{\n    public DemoRelationalCompositeExpressionFragmentTranslator(\n             RelationalCompositeExpressionFragmentTranslatorDependencies dependencies)\n         : base(dependencies)\n      {\n         AddTranslators(new[] { new DemoArrayTranslator() });\n      }\n   }\n\n// Register the translator\nservices\n  .AddDbContext<DemoDbContext>(builder => builder\n       .ReplaceService<IExpressionFragmentTranslator,\n                       DemoRelationalCompositeExpressionFragmentTranslator>());\n\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了测试,我引入了另一个包含Guid[]参数的重载。\n尽管如此,此方法在我的用例中根本没有意义:)

\n\n
public static long RowNumber(this DbFunctions _, Guid[] orderBy) \n
Run Code Online (Sandbox Code Playgroud)\n\n

并调整了方法的使用

\n\n
// Translates to ROW_NUMBER() OVER(ORDER BY Id)\n.Select(c => new { \n                RowNumber = EF.Functions.RowNumber(new Guid[] { c.Id })\n}) \n
Run Code Online (Sandbox Code Playgroud)\n