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

时间:2019-07-25 16:42:57

标签: c# mysql linq-to-sql entity-framework-core full-text-search

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

这是我当前的工作流程:
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"));

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

SELECT * FROM t WHERE MATCH(`Col1`, `Col2`, `Col3`) AGAINST('keywords');

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

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

1 个答案:

答案 0 :(得分:1)

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

示例:

// 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(new[] { x.Col1, x.Col2, x.Col3 }, "keywords")

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

参数的排序顺序将保持查询中定义的顺序, 即new { x.Col1, x.Col2 }将被翻译为Col1, Col2new { x.Col2, x.Col1 }Col2, Col1

您甚至可以执行以下操作:new { x.Col1, _ = x.Col1, Foo = "bar" }将被转换为Col1, Col1, 'bar'

实施自定义IMethodCallTranslator

如果您需要一些提示,则可以在Azure DevOps: RowNumber Support上浏览我的代码,或者如果您可以等待几天,那么我将提供有关自定义功能实现的博客文章。

已更新(2019年7月27日)

感谢下面的评论,我认为需要澄清。

1)如下面的评论所指出的,还有另一种方法。使用HasDbFunction可以为我节省一些输入,例如用于在EF中注册翻译器的代码,但是我仍然需要RowNumberExpression,因为该函数具有2组参数(用于PARTITION BY和{ {1}}和现有的ORDER BY不支持。 (或者我错过了什么吗?)之所以选择SqlFunctionExpression使用该方法,是因为我希望在设置IMethodCallTranslator期间而不是在{{1 }}。也就是说,这是我的个人喜好。

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

OnModelCreating

2)匿名类型不能强制其成员的类型,因此,如果使用例如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 }) }) 中的。尽管如此,它仍然是有效的解决方案。根据客户的不同,您为解决方案工作的可行性或多或少是可行的,最终由客户决定。不提供任何替代方案也是可能的解决方案,但不是令人满意的方案。 特别是,如果不希望使用SQL(因为从编译器获得的支持更少),那么运行时异常可能是一个很好的折衷方案。

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

  

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

string

为了进行测试,我引入了另一个包含IExpressionFragmentTranslator作为参数的重载。 尽管如此,在我的用例中,这种方法完全没有意义:)

// to get into EF pipeline
public class DemoArrayTranslator : IExpressionFragmentTranslator
{
    public Expression Translate(Expression expression)
    {
       if (expression?.NodeType == ExpressionType.NewArrayInit)
       {
          var arrayInit = (NewArrayExpression)expression;
          return new DemoArrayInitExpression(arrayInit.Type, arrayInit.Expressions);
       }

       return null;
    }
}

// lets visitors visit the array-elements
public class DemoArrayInitExpression : Expression
{
   private readonly ReadOnlyCollection<Expression> _expressions;

   public override Type Type { get; }
   public override ExpressionType NodeType => ExpressionType.Extension;

   public DemoArrayInitExpression(Type type, 
           ReadOnlyCollection<Expression> expressions)
   {
      Type = type ?? throw new ArgumentNullException(nameof(type));
      _expressions = expressions ?? throw new ArgumentNullException(nameof(expressions));
   }

   protected override Expression Accept(ExpressionVisitor visitor)
   {
      var visitedExpression = visitor.Visit(_expressions);
      return NewArrayInit(Type.GetElementType(), visitedExpression);
   }
}

// adds our DemoArrayTranslator to the others
public class DemoRelationalCompositeExpressionFragmentTranslator 
      : RelationalCompositeExpressionFragmentTranslator
{
    public DemoRelationalCompositeExpressionFragmentTranslator(
             RelationalCompositeExpressionFragmentTranslatorDependencies dependencies)
         : base(dependencies)
      {
         AddTranslators(new[] { new DemoArrayTranslator() });
      }
   }

// Register the translator
services
  .AddDbContext<DemoDbContext>(builder => builder
       .ReplaceService<IExpressionFragmentTranslator,
                       DemoRelationalCompositeExpressionFragmentTranslator>());

并调整了方法的用法

Guid[]