C# 如何在EF Core中使用DbFunction转换?

C# 如何在EF Core中使用DbFunction转换?,c#,mysql,linq-to-sql,entity-framework-core,full-text-search,C#,Mysql,Linq To Sql,Entity Framework Core,Full Text Search,我正在寻找类似于EF.Functions.FreeText的东西,它是在SQL Server中实现的,但是使用了MySQL的MATCH…语法 这是我当前的工作流程: AspNetCore 2.1.1 EntityFrameworkCore 2.1.4 Pomelo.EntityFrameworkCore.MySql 2.1.4 问题是MySQL使用了两个函数,我不知道如何用DbFunction解释这一点,并将每个函数的参数分开。有人知道如何实现这一点吗 这应该是Linq语法: query.Whe

我正在寻找类似于
EF.Functions.FreeText的东西,它是在SQL Server中实现的,但是使用了MySQL的
MATCH…语法

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

问题是MySQL使用了两个函数,我不知道如何用
DbFunction
解释这一点,并将每个函数的参数分开。有人知道如何实现这一点吗

这应该是Linq语法:

query.Where(x=>DbContext.FullText(新[]{x.Col1,x.Col2,x.Col3},“关键字”);
这应该是在SQL中生成的结果:

SELECT*fromt,其中将(`Col1`、`Col2`、`Col3`)与('keywords')匹配;
我尝试使用
HasTranslation
函数来遵循以下示例:


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

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

例如:

// gets translated to
// ROW_NUMBER() OVER(PARTITION BY ProductId ORDER BY OrderId, Count)
DbContext.OrderItems.Select(o => new {
  RowNumber = EF.Functions.RowNumber(o.ProductId, new {
    o.OrderId,
    o.Count
  })
})
使用匿名类而不是数组

您必须做的第一件事是从使用数组切换到匿名类,即从

DbContext.FullText(新[]{x.Col1,x.Col2,x.Col3},“关键字”)

DbContext.FullText(新的{x.Col1,x.Col2,x.Col3},“关键字”)

参数的排序顺序将与查询中的定义相同, i、 e
new{x.Col1,x.Col2}
将被翻译成
Col1,Col2
new{x.Col2,x.Col1}
Col2,Col1

您甚至可以访问以下内容:
new{x.Col1,=x.Col1,Foo=“bar”}
,它将被转换为
Col1,Col1,'bar'

实现自定义
IMethodCallTranslator

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

更新(2019年7月31日)

博客帖子:

更新(2019年7月27日)

由于下面的评论,我认为需要一些澄清

1)正如下面的评论所指出的,还有另一种方法。使用
HasDbFunction
我可以保存一些输入,比如用于向EF注册翻译器的代码,但我仍然需要
行数表达式
,因为该函数有两组参数(用于
划分和
排序)而现有的
SqlFunctionExpression
不支持这一点。(或者我错过了什么?)我之所以选择使用
IMethodCallTranslator
的方法,是因为我希望在设置
DbContextOptionsBuilder
时完成此功能的配置,而不是在
OnModelCreating
中。也就是说,这是我个人的偏好

最后,线程创建者还可以使用
HasDbFunction
实现所需的功能。在我的例子中,代码如下所示:

// OnModelCreating
  var methodInfo = typeof(DemoDbContext).GetMethod(nameof(DemoRowNumber));

  modelBuilder.HasDbFunction(methodInfo)
            .HasTranslation(expressions => {
                 var partitionBy = (Expression[])((ConstantExpression)expressions.First()).Value;
                 var orderBy = (Expression[])((ConstantExpression)expressions.Skip(1).First()).Value;

                 return new RowNumberExpression(partitionBy, orderBy);
});

// the usage with this approach is identical to my current approach
.Select(c => new {
    RowNumber = DemoDbContext.DemoRowNumber(
                                  new { c.Id },
                                  new { c.RowVersion })
    })
2)匿名类型无法强制其成员的类型,因此如果使用
integer
而不是
string
调用函数,则可以获得运行时异常。不过,这可能是一个有效的解决方案。根据您为之工作的客户,解决方案可能或多或少可行,最终决定权在于客户。不提供任何替代方案也是一个可能的解决方案,但不是一个令人满意的解决方案。 特别是,如果不希望使用SQL(因为编译器提供的支持更少),那么运行时异常可能是一个很好的折衷方案

但是,如果折衷方案仍然不可接受,那么我们可以研究如何添加对阵列的支持。 第一种方法是实现一个定制的
IExpressionFragmentTranslator
,将数组的处理“重定向”给我们

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

并调整了方法的使用

// Translates to ROW_NUMBER() OVER(ORDER BY Id)
.Select(c => new { 
                RowNumber = EF.Functions.RowNumber(new Guid[] { c.Id })
}) 

有问题的方法的签名是什么?我们可以遵循它们在中使用的相同设计模式,但使用数组来接受多个属性,类似于下面的
公共静态bool全文(string[]propertyReferences,string FullText)
问题是不幸的是,当前
new[]{…}表达式内部不支持
,因此根本不会调用我们的翻译。不幸的是,您无法在查询外部创建数组,因为您需要从
x=>
访问
x
。创建一个带有多个重载的函数,虽然相对容易,但要创建一个函数要有多个重载
(string,string)
(string,string,string)
等等。如果对你有效,请告诉我。因为处理
new[]{…}
参数需要深入EF核心基础设施。这是一个很好的尝试,但是。。。(1) 过于复杂。添加简单方法的管道代码太多。如果该方法不需要自定义表达式,
HasDbFunction
+
HasTranslation
就足够了。(2)
new{x.Col1,x.Col2,x.Col3}
new string[]{x.Col1,x.Col2,x.Col3}
-后者是编译时安全的,而前者不是(不强制所有成员都是
string
类型)。我理解你为什么这么建议,但大多数阅读的人不会这样做——这是一种针对当前EF核心方法参数翻译的类型不安全的解决方法,缺乏
新的t[]{…}
支持。@IvanStoev感谢你的评论,他们给我带来了一些想法!我在我的原始答案中添加了几个部分。添加了博客帖子的链接
public static long RowNumber(this DbFunctions _, Guid[] orderBy) 
// Translates to ROW_NUMBER() OVER(ORDER BY Id)
.Select(c => new { 
                RowNumber = EF.Functions.RowNumber(new Guid[] { c.Id })
})